👤
"WriteModel をクエリ時に使う"と"ReadModel をつくる"との折衷案
前提
以下のようなWriteModelがあるとする。
type Product struct{
NetPrice int64
Cost int64
}
Computed な以下のような値がある(丸め誤差などは一旦考慮しない)
func (p *Product) SalesPrice() float64 {
return (p.NetPrice + p.Cost) * 0.8
}
この SalesPrice
メソッドは WriteModel でも利用するため以下のような選択肢が生まれる
一番楽: WriteModel をクエリ時にも使う
WriteModel を用いれば特段新しい実装をせずにクエリできる。
しかし以下の点が引っかかる。
-
ORMの構造体 → ドメインモデル → DTO
の変換が必要になる。ORMの構造体 → DTO
にしたい - WriteModel にはクエリに不要なメソッドまで含まれており用途がわかりにくい
一番リッチ: ReadModelをつくる
type ProductReac struct{
NetPrice int64
Cost int64
}
func (p *ProductRead) SalesPrice() float64 {
return (p.NetPrice + p.Cost) * 0.8
}
今回の提案: WriteModel が関数化し、クエリ時には関数単体で使う
type Product struct{
NetPrice int64
Cost int64
}
func (p *Product) SalesPrice() float64 {
return CalculateSalesPrice(p.NetPrice, p.Cost)
}
// [ココ] ドメインロジックを関数にカプセル化する
func CalculateSalesPrice(netPrice, cost int64) float64 {
return (netPrice + cost) * 0.8
}
CalculateSalesPrice
関数をクエリ時にも使う。
これにより "ReadModelをつくる" ほどの記述をせず、 "WriteModelをクエリ時にも使う" ほど用途外に利用することもなく済む。
Discussion
このほうがテスタビリティも上がる気がしている(なんとなく)