この記事はドメイン駆動設計 Advent Calendar 2020 7日目の記事です。
DDDで紹介される戦術パターンの一つの仕様(Specification)パターンのGoでの実装を(自分へのメモ用と整理も兼ねて)2つ紹介します。
interfaceを利用
DDDはJavaで紹介されることが多いのですが、1つ目はこのJavaで紹介されるインターフェースを利用したものを素直に書いたパターンです。
例で見てみましょう。
// 仕様はRuleの集合で表現する
type Specification struct {
rules []rule
}
// 検証を表すインターフェース
type rule interface {
IsValid() bool
}
// 名前の長さのルール
type nameLengthRule struct {
name string
}
func (n nameLengthRule) IsValid() bool {
return len(n.name) < 20
}
// 有効な年齢のルール
type ageRangeRule struct {
age int
}
func (a ageRangeRule) IsValid() bool {
return 0 <= a.age && a.age < 200
}
func NewSpecification(name string, age int) Specification {
return Specification{
rules: []rule{
nameLengthRule{name},
ageRangeRule{age},
},
}
}
func (s Specification) Satisfied() bool {
for _, rule := range s.rules {
if !rule.IsValid() {
return false
}
}
return true
}
検証内容をruleとしてinterfaceに切り出すことで、今後検証内容が増えても既存の影響はSpecificationの生成だけになります。
ビジネスルールは比較的頻繁に変わるので、変更に強いプログラムになると言われています。
(とはいえ、このくらいの検証でやるのは過剰ですが。。)
typeを利用
もう一つはruleの関数自体を型にしてしまうものです。
これも例をみてみましょう。
type Specification struct {
rules []rule
}
type rule func() bool
func nameLengthRule(name string) rule {
return func() bool {
return len(name) < 20
}
}
func ageRangeRule(age int) rule {
return func() bool {
return 0 <= age && age < 200
}
}
func NewSpecification(name string, age int) Specification {
return Specification{
rules: []rule{
nameLengthRule(name),
ageRangeRule(age),
},
}
}
func (s Specification) Satisfied() bool {
for _, rule := range s.rules {
if !rule() {
return false
}
}
return true
}
検証の内容は変わりませんが、structの宣言がなくなった分コードがスッキリします。
ポイントは関数のfunc() bool
を型にしてしまうところです。
これによって関数宣言だけでルールが作れるので、シンプルな形になっています。
Goでは関数も型にできるので、うまく利用するとインターフェースを使うよりもシンプルな形で実装できるので、参考になれば幸いです。