古典ドメインモデリングパターンの解脱 - 大吉祥寺.pm
2024年7月13日の大吉祥寺.pmで発表した「古典ドメインモデル(パターン)の解脱」のスライドログです。
https://gyazo.com/fce2f8b532200c2cc9608b305c1c0258
https://gyazo.com/6f20499806c178988bc0b3ab33010cbe
https://gyazo.com/a754d32eb0d5d8c6736794364a9013df
https://gyazo.com/9d91e55d26168ba0f8f46cb8eb798944
この2冊で書かれているドメインモデルパターンを「古典」の対象にします。
https://gyazo.com/8a43b4e2228b7a4d71cddf5bf2ca9174
https://gyazo.com/2dc5fe43ab30a0e46392555d36aace43
ドメインモデルパターンは「複雑さに対処するため」と述べています。が、古典では次の2点が課題となっていると考えます。
https://gyazo.com/073fad7335e08d2ecdd536f2e998e92c
https://gyazo.com/7a0b462440b8046d05981ccb50e2fef0
これら2点について個別に見ていきます。
https://gyazo.com/e6ef717386db6ca7bec42470178f209d
まずドメインモデルパターンから。
https://gyazo.com/5dada086dd713ba7a2d0b649ff4883d8
Patterns of Enterprise Application Architecture(以降PofEAA)ではこのように定義されています。
https://gyazo.com/440f3ad56a168d3cf78c26de429405d4
PofEAAのドメインロジックの章で使われている「収益認識」の例を取り上げます。
https://gyazo.com/d9ed78bf890bc9d265b85a633905c591
ContractやProduct, RecognitionStrategyなどといったクラスが作られて、これらのインタラクションでビジネスロジックが実現されると説明されています。
https://gyazo.com/fb4e1bc04bcef564790f3233920d5bf7
では、これらのドメインモデルをどう作れば良いのか、という話になると「振る舞いを自然に適したオブジェクトに置く」というかなり感覚に頼った言い回しになります。なので、みんな「良いドメインモデルとは?」を求めて彷徨うことになるのです。
https://gyazo.com/0f184cdbdd9a22c36012979b127d436d
良く設計されたドメインロジックの基準に完全であることや純粋であることがあります。良く設計されたドメインロジックは皆この基準を満たすかもしれませんが、だからといってこの基準を満たしたドメインロジックが複雑さに対処できたよく設計されたものであるとは限りません。 https://gyazo.com/8baba4d2b65af0531225c3dcafb59f95
本来、複雑さに対処するために導入されたパターンのため、複雑さをまず見極めるのが先決なのではないでしょうか。
https://gyazo.com/4fce8347e0c95c3e8ea06fc3ce9b5352
複雑さは『銀の弾丸はない』によると、本質的なものと偶有的なものがあるとされています。このうちドメインモデルを用いてManageするたぐいの複雑さは、本質的な複雑さでしょう。
https://gyazo.com/82068272c0aff958215e013ce84974ad
本質的複雑さは、ソフトウェア開発に「Unknow Unknowns(分からないことが分からない)」や「認知負荷が高い」といった影響を及ぼします。
https://gyazo.com/399529d44ff6f770c16ef5eec844abdf
本質的な複雑さが何から生み出されるのか? これはソフトウェアのコンポーネント(いろんな粒度がある)とその間のインタラクションから生み出されると言われています。
https://gyazo.com/77abd23fbdf4fd55e30759cdbb0d3258
そして、コンポーネントが増えていけば、そのインタラクションも増え、一度に把握しておくべきものが増えるので、認知負荷が高まることになります。
https://gyazo.com/f18150e87f0fb20e5f1b3a9da7badce0
一度に把握すべきものを減らすために、グルーピングして分割統治することがよくやられるが、この境界いかんではUnknown Unknownsが発生します。
https://gyazo.com/a1ff7ef98acec0917a7e1dc1abcb3e44
たとえば注文というエンティティを例にとると、複数の業務イベントで同じ注文に書き込むようなものを作ってしまうと、Unknow Unknowsが発生します。実際、この注文エンティティを見ただけでは、どういうタイミングでどういうデータを書き込むのかが分からないでしょう。
https://gyazo.com/759447b32c8d28cccd4ff7b521e7d9af
コンポーネントのグルーピング、境界を作ると境界内で発生するインタラクションと境界を跨いだインタラクションそれぞれから生み出される類の複雑さとして見ることができます。これをPerrowの『Normal Accidents』では、ローカルな複雑さとグローバルな複雑さとして区別しています。
https://gyazo.com/52428b87524bacc6394893ec163a8842
ここで大事な法則があります。境界をどこで切るかは分割統治としては大事なポイントですが、全体の本質的な複雑さの総量はどう境界付けしても変わらないということです。ローカルな複雑さとグローバルな複雑さが交換されるだけです。
https://gyazo.com/b701cdd6a007f0bbb827ae01a33c5d5c
先ほどの注文エンティティを例にとると、①にはローカルな複雑さがありますが、これを分解して②のようなグローバルな複雑さに転換させても、注文に関わるデータと振る舞い、そのインタラクションは変わりません。これが本質的複雑さ保存則です。
https://gyazo.com/d04a1041b95cc02d9fa5a72aa31b2aa4
ただし、ローカルな複雑さはその中にUnknown Unknownsまでも閉じ込めてしまうので、理解可能性が損なわれます。
※注: これはあくまでもその業務に含まれる複雑さを明らかにするフェーズでの話であって、明らかになった複雑さをどう関心の分離を行い適切な抽象バリアを作るかのフェーズとは別の話です。
https://gyazo.com/d20e2ff79f76fc6e150302f61dc8b379
したがって、本質的複雑さの正体を明らかにする前から、ローカルな複雑さを放置してはなりません。結果として凝集度の低いモデルが出来上がる原因になります。
https://gyazo.com/cb27e4dc1ccbe3a2ce1435c103562b64
さて、「複雑さ」が何なのか、まず本質的複雑さを明らかにすることが重要だということが理解できたところで、実際にその意図に沿ったドメインモデルを書いてみます。
https://gyazo.com/47ae8ebb792040dfb7c90fa35e1295cc
このままではどれだけの複雑さがあるのか分からないので、ドメインモデルを洗練させることによって明らかにします。
https://gyazo.com/dc17eac5e7b44147375bab45b336cbc4
基本手順は「スタンプ結合を解消する」ことと「振る舞いの名前に着目し、振る舞いを分割する」ことを繰り返すことです。
https://gyazo.com/7a2f078299f212f6d07b45382123ebe4
スタンプ結合とは構造化設計の結合強度の1つであり、「振る舞いの入力となるデータの全てをその振る舞いが使っていない」場合に、それをスタンプ結合がある状態と言います。
スタンプ結合を解消するためには、入力すべてが使われる粒度にデータを分割することになります。
https://gyazo.com/f079e446bb3a7482c0b7b0415ebfa305
https://gyazo.com/a89585b5c0c2d394f2acc2907fff5a52
元の大きな「契約」から振る舞いで使わない項目を分離し、スタンプ結合がない状態にしました。
https://gyazo.com/ef6822404f608026f129893e4a67bce7
次に振る舞いの名前に着目します。これを命名のプロセスの要領で名前をHonest&Completeなものにします。https://gyazo.com/986ddda3a4b888b10bfe144cde5f5a74 Honest&Completeな命名をしたときに、2つ以上の文から構成されていれば、複数の責務が混じっていることが示唆さます。
https://gyazo.com/5c2c8e3a31d0164b0e7f8a3ed33c3ab7
そこで、これを分割して振る舞いが1つの責務になるようにします。これを繰り返していくとローカルな複雑さの内容が明らかになっていきます。
https://gyazo.com/a80d194f9d5757e5e610bbdb7df6b1d3
このプロセスは、モデリングの重要な側面を明らかにしています。データだけ眺めて正規化や抽象レベルを議論しても、その良し悪しは一意に決まらないということです。振る舞いを考えてこそ、その文脈において適切なデータの粒度や抽象度が見えてくる、ということです。
https://gyazo.com/91a536bb8ddc0ae2cb3bc910b94ed2c8
ここに示したドメインモデルの洗練のための「基本手順」は、実は疎結合と高凝集のためのステップになっています。
https://gyazo.com/3bf0a2fd269ed1245d162614bce1be6a
ここまでのドメインモデルの記述は実装には依存しない世界の話です。当然ながらソフトウェアとして実装する上では、実装言語の制約や効率などを考慮し、1対1でクラスやメソッドに「エンコーディング」する必要はありません。見つけた「複雑さ」を適切な境界を実装し、カプセル化・関心の分離をはかることになるでしょう。
ここで重要なのはビジネスドメインからコードにいくまでのプロセスは、本質的複雑さにどう対処するかを考える「モデリング」と偶有的複雑さにどう対処するかを考える「エンコーディング」が存在するということです。
ドメイン駆動設計は、ここでのドメインモデルとコードモデルをできるだけ一致させるのだ、を理念として掲げているわけですが、モデリングとエンコーディングの技術が混同して語られたり、実装言語の表現力や制約などによって一致させるのが難しい面も多くあったり、混乱が見られることもあります。
https://gyazo.com/25e795256c648a35bea35d6c982eb767
『関数型ドメインモデリング』は、実装に依存しないモデリングのアプローチを示している素晴らしい書籍です。そしてドメイン駆動設計の夢であったドメインモデルとコードモデルの一致はF#で実装すれば可能だ。ということも後半では述べています。この点、他の言語で「エンコーディング」する必要のある方は注意して読まなければなりません。
https://gyazo.com/b66f332e762b56290aec1497b7de40d2
https://gyazo.com/22c8628efa55164d60b0d58c91d3d410
次にビジネスロジックについて、です。
https://gyazo.com/cc906261ed099602a036f46e028d7427
「ビジネスロジック」という用語には、明確な定義がなく、まず明らかにビジネスロジックとは言えないようなものを挙げ、それ以外のもの、すなわちビジネスロジックではないものではないものをビジネスロジックであるという、アポーハ論的な説明をよく見かけます。 すなわちその用語指し示す集合の境界が曖昧で、あるロジックがビジネスロジックか否かのような議論を産みます。
https://gyazo.com/43a2d8b5c8854a6391e9980aba978a82
そこで別の視点を導入すると良いのではないか、という話をします。それがAlways-Valid Domain Modelです。これはドメインモデル(の実装)は、必ずValidな状態でなければならない、Validな状態としてしか生成できないようにしようというものです。 すなわちビジネスロジック層みたいなものは、業務上Validなデータのみを扱うAlways-Valid Layerとして考えよう、というものです。
https://gyazo.com/d97ce6a4d9701ab798fe1c78fb9a4750
このAlways-valid Layerを設けることは、Shotgun parsingの対策にもなります。データが望む形であるかどうか信頼できない場合は、必ず値の検証をしてから使う防御的プログラミングをすることになります。これが散らばって存在していると、検証の漏れを防ぎにくくなってしまうというのがShotgun parsingアンチパターンです。
Always-valid Layerを設け、(これはエンコーディングの技術にはなりますが…) 検証済みのデータはValidな型としてパースしてしまえば、型の不変条件で正当性を保証できるので、以降の防御的プログラミングは無用です。
https://gyazo.com/0aa872a8090a2e13b99c3a47d2cb53ff
また別の視点で、フロントエンドにドメイン層(ビジネスロジック層)は必要か? のような議論を見かけることもあります。これもビジネスロジックという視点ではなく、Always-validが必要かどうか?で考えれば答えを出しやすくなるのではないでしょうか。
https://gyazo.com/e7ad1a7eaadbb7c978011ee22dac221f
それからPofEAAのトランザクションスクリプトパターンかドメインモデルパターンかの議論も、Always-validを軸に考えると良いかと思います。PofEAAの2つのパターンのコード例は、公平な比較とは言いがたく、余計な論点を多く持ち込んでしまっています(ドメインモデル参照)。結局その差は何なのかよく分かりません。 https://gyazo.com/3277113611b6bd2fe64391238357e7af
が、このAlways-valid Layerを設けるかどうかで考えると、整理しやすいかと思います。