こちらのイベントに参加してきたので、会の様子と感想を書いていこうと思います。
会の概要
以下、connpassページからの引用です。
部門に社員を配属するとか、カートに商品追加するとか、コレクションを集約としてアイテムを追加する訳だが、件数多くいちいちコレクション全体をメモリにロードしてられないこともある(というかそういうケースの方が多いのでは?) 。そういう時にどういう設計パターンが考えうるか、まで論じて欲しい。
— kawasima (@kawasima) 2023年1月13日
をテーマに、
- どういう実装パターンがあるのか?
- IDDDの本では「集約」を小さくして対応しろ、と言っているが、「集約」ってそんな実装都合で変える概念でいいんだっけ?
- 「純粋」なものだけをドメイン層としようと頑張れば頑張るほど、ドメイン層が骨粗鬆症にならない?
- イミュータブルデータモデル視点で見れば、もう少し場合わけして考えれそう。
みたいな、ことを議論したいと思います。
会の様子
導入〜よくあるアイテム追加処理〜
何かしらボタンを押すと商品がAPI EndPointに飛び、認証情報を読んだ上で何かしら処理をすることが多いよね、という話からスタートしました。
この時、よくあるユースケースだと、
- ユーザのカートが存在するか確認
- 商品のバリデーション(Idが実在する商品でなければユーザに通知して終了)
- カートに商品と数量を追加(上限を超える値ならばユーザーに通知して終了)
- カートの内容を保存
というようなケースをたどることが多いものの、上記3の上限値が数十万件だった場合*1、メモリにロードすることは非現実的で、この場合どういう設計をするのがよいのか?というのが問題提起として挙がっていました。
ドメインモデルのトリレンマ
続いて、Khorikovさんの記事を参考に、ドメインモデルのトリレンマの説明がありました。
の記事に書いてある通り、
の3つすべてを数十万件のアイテム追加処理で満たすことは難しいという話で、上記の中から何かしら一つは犠牲にしないといけないよね、ということでした。*2
トリレンマの具体的な実現方法
トリレンマが具体的にどのように起こるかについて説明がありました。
なお、具体的なコードはkawashimaさんのGithub上にあるので、そちらを参照くださいとのことです。(ただし急いで作ったコードなので直す必要がある部分がちょこちょこあるとのこと)
完全性+性能を取る場合
完全性と性能を取る場合は、性能の観点からアイテムを追加するために全部のアイテムをロードすることができないため、Read/Writeのモデルを分けてしまうやり方や遅延ロードで実現するやり方*3が考えられるという話が出ていました。
この場合、簡単なものに複雑な仕掛けを作ることになり、アンチドメインモデル貧血症の観点でよろしくないコードになるということです。
純粋性+性能を取る場合
ドメインオブジェクトにDBアクセスを含めないようにする場合は、ドメイン層が外界とやり取りしなくなるため、ドメインロジックがユースケース層に染み出すというお話がありました。
この場合、テスタビリティが上がったように見えてユースケースからテストしないと実際の品質保証観点では無意味になってしまうということです。
完全性+純粋性を取る場合
こちらは具体的な実現方法の説明はありませんでした。(理想論でしかなく業務では使い物にならないため)
トリレンマから分かる教訓
結局、完全性と純粋性の両方を完全に満たすことはできず*4、美しい完全な解決策はないということがわかるという話がありました。
ただ、ここは品質特性上、顧客の利益に直接影響するところではないため、そこまで気にしすぎないほうがよいということです。
高凝集
ここまでの話を前提として、性能を保った状態で少しでも完全性と純粋性のメリットを享受する方法はないのか?という話に移りました。
完全性を取りに行ってドメインモデルが貧血症起こさないように設計すると高凝集になるかと言われるとそれは怪しいということが、kawashimaさんのscrapboxの記事をもとに説明がありました。
上記のことから、高凝集を目指すなら複雑さを紐解く必要が出てくる(異なる振る舞いをするものは異なるものとしてみなすべき)と言えるよね、という話がありました。
ドメイン貧血症の解決策で勘違いしがちなこと
ドメイン貧血症の解決策を考える時、どこで定義するかの議論になることが多い*5ですが、同一かどうかの区別*6が重要ではないか?という話がありました。
このことから、何かしらの型があったときに、その型自身に#isValid()を持たせるのはその型がvalidでないことを暗に示しているため、型Aとは別にVaildな型Aの概念を持たせる方がいいだろうということです。(Type Safety Back and Forthでも失敗可能性を後ろに持っていくように話がされている)
Domain Modeling Made Functional
「複雑さ」が異なるものを別の型で表現するという話から、関数型プログラミングでドメインモデリングをするというDomain Modeling Made Functionalの話がありました。
「カードの山札」がシャッフルされているかどうかなどで、同じ構造(山札)でも異なる型として表現されているという例が説明されていきました。
併せて、冒頭で説明があったアイテムをカートに追加する処理のコード例でも、具体的にどのように実装をしていくといいのかの説明がありました。
martin fowlerのドメインモデル貧血症
kawashimaさん的には、martin fowlerが言っているドメインモデル貧血症の話だけだと、ふるまいが違うクラスが同じクラスとして実装される傾向にあり、複雑さを解消できていないため、そういった部分はバグの温床になるリスクがkawashimaさんの経験上は多いという話がありました。
会全体を通した感想
実際のコード例を組み合わせながらドメインモデルとか集約の話を聞くことができて面白かったです。
前回の複雑さの話とのつながりを感じられて、楽しい時間をすごすことができました。