天の月

ソフトウェア開発をしていく上での悩み, 考えたこと, 学びを書いてきます(たまに関係ない雑記も)

強いて言えば「集約どう実装するのかな、を考える」会に参加してきた

architect-club.connpass.com

こちらのイベントに参加してきたので、会の様子と感想を書いていこうと思います。

会の概要

以下、connpassページからの引用です。

をテーマに、

  • どういう実装パターンがあるのか?
  • IDDDの本では「集約」を小さくして対応しろ、と言っているが、「集約」ってそんな実装都合で変える概念でいいんだっけ?
  • 「純粋」なものだけをドメイン層としようと頑張れば頑張るほど、ドメイン層が骨粗鬆症にならない?
  • イミュータブルデータモデル視点で見れば、もう少し場合わけして考えれそう。

みたいな、ことを議論したいと思います。

会の様子

導入〜よくあるアイテム追加処理〜

何かしらボタンを押すと商品がAPI EndPointに飛び、認証情報を読んだ上で何かしら処理をすることが多いよね、という話からスタートしました。

この時、よくあるユースケースだと、

  1. ユーザのカートが存在するか確認
  2. 商品のバリデーション(Idが実在する商品でなければユーザに通知して終了)
  3. カートに商品と数量を追加(上限を超える値ならばユーザーに通知して終了)
  4. カートの内容を保存

というようなケースをたどることが多いものの、上記3の上限値が数十万件だった場合*1、メモリにロードすることは非現実的で、この場合どういう設計をするのがよいのか?というのが問題提起として挙がっていました。

ドメインモデルのトリレンマ

続いて、Khorikovさんの記事を参考に、ドメインモデルのトリレンマの説明がありました。

enterprisecraftsmanship.com

の記事に書いてある通り、

の3つすべてを数十万件のアイテム追加処理で満たすことは難しいという話で、上記の中から何かしら一つは犠牲にしないといけないよね、ということでした。*2

トリレンマの具体的な実現方法

トリレンマが具体的にどのように起こるかについて説明がありました。

なお、具体的なコードはkawashimaさんのGithub上にあるので、そちらを参照くださいとのことです。(ただし急いで作ったコードなので直す必要がある部分がちょこちょこあるとのこと)

github.com

完全性+性能を取る場合

完全性と性能を取る場合は、性能の観点からアイテムを追加するために全部のアイテムをロードすることができないため、Read/Writeのモデルを分けてしまうやり方や遅延ロードで実現するやり方*3が考えられるという話が出ていました。

この場合、簡単なものに複雑な仕掛けを作ることになり、アンチドメインモデル貧血症の観点でよろしくないコードになるということです。

純粋性+性能を取る場合

ドメインオブジェクトにDBアクセスを含めないようにする場合は、ドメイン層が外界とやり取りしなくなるため、ドメインロジックがユースケース層に染み出すというお話がありました。

この場合、テスタビリティが上がったように見えてユースケースからテストしないと実際の品質保証観点では無意味になってしまうということです。

完全性+純粋性を取る場合

こちらは具体的な実現方法の説明はありませんでした。(理想論でしかなく業務では使い物にならないため)

トリレンマから分かる教訓

結局、完全性と純粋性の両方を完全に満たすことはできず*4、美しい完全な解決策はないということがわかるという話がありました。

ただ、ここは品質特性上、顧客の利益に直接影響するところではないため、そこまで気にしすぎないほうがよいということです。

高凝集

ここまでの話を前提として、性能を保った状態で少しでも完全性と純粋性のメリットを享受する方法はないのか?という話に移りました。

完全性を取りに行ってドメインモデルが貧血症起こさないように設計すると高凝集になるかと言われるとそれは怪しいということが、kawashimaさんのscrapboxの記事をもとに説明がありました。

scrapbox.io

上記のことから、高凝集を目指すなら複雑さを紐解く必要が出てくる(異なる振る舞いをするものは異なるものとしてみなすべき)と言えるよね、という話がありました。

gihyo.jp

architect-club.connpass.com

aki-m.hatenadiary.com

ドメイン貧血症の解決策で勘違いしがちなこと

ドメイン貧血症の解決策を考える時、どこで定義するかの議論になることが多い*5ですが、同一かどうかの区別*6が重要ではないか?という話がありました。

このことから、何かしらの型があったときに、その型自身に#isValid()を持たせるのはその型がvalidでないことを暗に示しているため、型Aとは別にVaildな型Aの概念を持たせる方がいいだろうということです。(Type Safety Back and Forthでも失敗可能性を後ろに持っていくように話がされている)

Domain Modeling Made Functional

「複雑さ」が異なるものを別の型で表現するという話から、関数型プログラミングドメインモデリングをするというDomain Modeling Made Functionalの話がありました。

「カードの山札」がシャッフルされているかどうかなどで、同じ構造(山札)でも異なる型として表現されているという例が説明されていきました。

www.amazon.co.jp

併せて、冒頭で説明があったアイテムをカートに追加する処理のコード例でも、具体的にどのように実装をしていくといいのかの説明がありました。

martin fowlerのドメインモデル貧血症

kawashimaさん的には、martin fowlerが言っているドメインモデル貧血症の話だけだと、ふるまいが違うクラスが同じクラスとして実装される傾向にあり、複雑さを解消できていないため、そういった部分はバグの温床になるリスクがkawashimaさんの経験上は多いという話がありました。

会全体を通した感想

実際のコード例を組み合わせながらドメインモデルとか集約の話を聞くことができて面白かったです。
前回の複雑さの話とのつながりを感じられて、楽しい時間をすごすことができました。

*1:例えばスタジアムの座席数を管理するようなユースケースの場合

*2:純粋性と完全性をとると性能が悪化する。純粋性と性能を取るとコードの変更容易性が低下する。完全性と性能を取るとテストがしにくくなる

*3:ただし、意図しない場所で遅延ロードされて実行が遅くなるといった事故が起きやすいという欠点があるのでkawashimaさん的にはおすすめできない

*4:性能に課題があるシステムは使いもににならないため

*5:サービス層に定義しすぎるとNG...

*6:「複雑さ」が異なるものを型で表現する。重複が許されるユーザーと重複が許されないユーザーは別の概念として扱う