モデリングに対して課題意識があったので DDD Quickly を読みました。
本記事ではそのまとめと考察をアウトプットしていきます。間違った理解などがあったらコメントなどいただけると嬉しいです。
ドメイン駆動設計(DDD)とは
DDDはソフトウェア開発の基本原則。
ソフトウェア開発は業務の自動化や、ビジネスの問題解決が目的。
複雑な業務システムを構築するには、そのドメイン(業務)の知識が必要不可欠でありソフトウェアはドメインをモデル化しなければならない。ドメインモデルはソフトウェアの本質であり、他人に伝えられなければならない。
ドメイン知識の構築
アーキテクトや技術者はドメインの専門家と議論し、共にモデルを作り上げていく。
アーキテクトや技術者からのフィードバックは良いモデルを作るのに役立つ。
ユビキタス言語
ドメインについて議論する際は共通の言語を共有しなければならない。
共通言語の拠り所としてドメインモデルを使う。
共通言語でコミュニケーションし、コードにも使うようにする。
この言語をユビキタス言語という。
モデル駆動設計
ドメインモデリングはソフトウェアの設計を考慮にいれるべきなので開発者もドメインに関する議論に参加し、ドメインとそのモデルがコードとして表現可能か見通しを立てる。
コードの変更に責任があるものはコードを通してモデルを表現する方法を学ばなければならない。ドメインの専門家はコードを触る技術者とユビキタス言語を通じて活発に意見を交換しなければならない。
ドメインモデルを正しく設計に反映させるために、ソフトウェアを部分ごとに設計する。
モデル設計の基本要素
レイヤーアーキテクチャ
ドメインに関するコードの可読性を上げ、検討に集中できるように、他のレイヤを分割する。分割したレイヤの役割が絡み合わないようにする。
一般的なソリューションでは4つの抽象的なレイヤを使う。
名前 | 説明 |
---|---|
ユーザインターフェイス(プレゼンテーションレイヤ) | ユーザーに対して情報を開示し、ユーザーの命令を解釈する |
アプリケーションレイヤ | アプリケーションの活動を調整するレイヤ。ビジネスロジックを含まず、ビジネスオブジェクトの状態を保持しない。しかし、アプリケーションの処理の進み具合を保持する場合がある。 |
ドメインレイヤ | ドメインについての情報を含むレイヤ。ビジネスオブジェクトの状態を保持し、ビジネスオブジェクトの永続化をインフラストラクチャレイヤに委託する。まれに、ビジネスオブジェクトの状態もインフラストラクチャ層に委託することがある。 |
インフラストラクチャレイヤ | 他のすべてのレイヤを補助するライブラリとして働く。レイヤ間の情報のやり取りを制御し、ビジネスオブジェクトの永続化を実装する。また、ユーザーインターフェイス等を補助するライブラリを含む。 |
モデリング
ドメインを表現するための概念があり、それぞれ役割がある。
名前 | 説明 |
---|---|
Entity | 一意性があり探索可能なオブジェクト。一意性はIDなどの識別子のほか、オブジェクトの振る舞いで保証されることもある。 |
ValueObject | 一意性を保証する必要のないオブジェクト。 |
Service | オブジェクトに対応づけられないような振る舞いを表現する概念。 |
Module | ドメインモデルの関連する概念やタスクを組織して複雑性を軽減する方法。 |
Aggregate | オブジェクトの所有権と境界を表現するパターン。関連するオブジェクトが集約されている。1つのAggregateは1つのEntityを持ち、このEntityはRootして機能する。ルートは外部からアクセスできる唯一のオブジェクトであり、Aggregate内のオブジェクトの参照・更新はすべてRootを通す必要がある。 |
Factory | 複雑なAggregate,Entityの生成を担う。オブジェクト生成に必要な知識をカプセル化するのに役立つ。 |
Repository | ドメインオブジェクトの参照を取得したり、永続化する責務を担う。リポジトリに直接アクセスできるのはAggregateのRootのみ。 |
リファクタリングのための更に深い洞察
設計と実装では、ときどき作業を止めてファクタリングする時間が必要。
リファクタリングを行うことでドメインの要素が明確になったり、2つの要素の間に新しい関係が見つかるなど新しいが洞察が得られることがある。
初期のモデリングでは仕様書を読んで名前と動詞を見つけ、おおまかなモデルを作り上げていくがどんなモデルも深みがかけているもの。より深い洞察へ向けてリファクタリングを行う。
ドメインの専門家と話しているうちに見つかる概念や知識のから、潜在している概念を明確にしモデルへ反映する。モデルへ取り入れるべき概念を掘り出すために書籍を使うことも有効。
制約、プロセス、仕様の表現
制約
オブジェクトの不変性を考慮するために制約を使う。
制約をドメインオブジェクトの振る舞いとして表現する
例えば、本棚の容量の制約などは Boolshelf.isSpaceAvailable()
のように表現できる。
プロセス
プロセスを表現するための最も良い方法はサービスを使うこと。
仕様
ドメインオブジェクトが条件を満たすかどうかを検証するために仕様をつかう。
エンティティやバリューオブジェクトに適用するためのビジネスルールがある。
このようなルールは大抵、ルールが適用されるオブジェクトに組み込まれる。
例えば、顧客に一定の信用があるかどうかなどは Customer.isEigible()
というメソッドで表現できる。
仕様が複雑で、1メソッドに収まらないような場合は専用のオブジェクトにカプセル化する。このオブジェクトはドメインレイヤに配置する。
ひとつの仕様は単純なルールが守られているかどうかを判断し、複数の仕様が一つにまとめられ、下記のように複雑なルールを表現する。
下記は、仕様を表現したモデルの例。
- 顧客の信用の仕様
- 過去の借入れを返済済み
- 外部の金融機関から借入れが無い
モデルの完全性を維持する
複数のチームで開発を進める大規模なプロジェクトの場合はモデルの完全性を維持するためにいくつかのモデルに分割する。巨大なモデルをチームを横断して保守してはならない。
コンテキスト境界
モデルは一つのチームに割り当てられるくらい小さくするべき。そうすることでチーム内の協調性やコミュニケーションは柔軟で完全になる。
モデルの分割の際は、コンテキストの境界を描きモデルの範囲を確定する。チームの体制や、作成するアプリケーションの使い方、コードベースやデータベースのスキーマのような実際に構築するものを考慮にいれながら境界を決めていく。
継続的な統合
モデルは最初から完璧には定義されておらず変更を伴う。そのため、コンテキスト境界を扱うときは継続的な統合が必要。変更は素早くマージし、統合結果は自動ビルド及び、テストされる必要がある。
共有カーネル
機能的な統合が不十分だと、継続的統合でのオーバーヘッドが課題になる。これに対し、ドメインモデルのある部分を2つのチームで共有する。定期的にシステムの統合の必要はあるが、チーム内の継続的な統合の頻度よりも少なくて済む。
共有カーネルの目的は重複をなくして、2つのコンテキストを分離すること。
共有カーネルは双方のチームが変更可能だが、統合も同時におこなわなければならない。
カスタマーサプライヤ
二つのサブシステムがあり、片方が一方的に依存している場合、この関係をカスタマーサプライヤ関係にあるという。依存する側がカスタマで、サブシステムの提供側がサプライヤとなる。
サブシステムの更新がカスタマの開発のボトルネックにならないように、定期的に顔をあわせてサブシステムのインターフェイス、作業スケジュールなどについて話す。
カスタマーサプライヤ関係は、両方のチームがこの関係に注意を払っているとうまくいく。
順応者
サプライヤチームが非協力的な場合の対策として、カスタマーチームはサプライヤチームから離れて、独立して自分たちの作業を完了させるか、自分たちのシステムを保護する変換レイヤを実装するなどがある。
防腐レイヤ
外部のアプリケーションと連携する場合、防腐レイヤを実装することでクライアントのモデルが、外部のモデルの悪影響を受けることがなくなる。
別々の道
サブシステムの統合にはそれだけのために作業時間を取られたりモデルの変更が伴う。
サブシステム同士を統合する価値を評価して問題のほうが大きい場合は、別々の道を歩んだほうが良い。
コンテキスト境界ごとに分割してGUIによって統合する。
公開ホストサービス
ひとつのサブシステムが多くのサブシステムと連携が必要な場合、個別に変換レイヤを実装するのはコストなので、一群のサービスで包んで他のサブシステムはそのサービスを使わせる。
この方式はクライアントサブシステム毎に独自の方法でアクセスを行う場合は問題になる。また、特定のクライアントサブシステムへ向けてのみ拡張するべきではなく、共通の方式かつ単純で矛盾のない状態を保たなければならない。
蒸留
蒸留とはドメインの中核となるコアドメインを定義すること。
蒸留をするのはドメインを表現した大きなモデルを磨き上げ、リファクタリングを繰り返しても小さくできないようなとき。
コアドメインを担当するのはチームのなかで最も優秀な人物、またはそれに変わる適切な人物。
コアドメインの作成は1回では完了せず、効果的なリファクタリングにより明らかになる。コアドメインとその他の境界を確定する。コアドメインとの関係を基にして他のモデルを見直し、リファクタリングを行う。
サブドメインを別のモジュールとして配置した後、コアドメインの開発に優先して取り組む。これはコアドメインの開発をすることがドメイン知識の理解につながるため。
考察
Aggregateは変更の一貫性を保証するためのドメインオブジェクト、Aggregateには一つのRootとなるEntityが存在し、Aggregate内部のモデルの参照・更新は常にRootを通じてのみ行われる等々、理解が浅かったなと猛省...。
Aggregateの設計に関しては読了後も課題感がある。例えば、RootとなるEntity、もしくはAggregateに含めるべきでないEntityの選定(別のAggregateになる???)は難しそう。
また、そのモデリングや境界の正しさはどうやって証明されるのだろう。
判断基準は変更の一貫性が保証できているかどうかを、ドメインエキスパートやチーム内でのコンセンサスがとれていればいいのだろうか?
蒸留によるコアドメインの定義や、リファクタリングを継続的に行うことでドメインモデルが洗礼されていくと理解しているが、機能開発に時間を吸われがちなのが現状。プロダクトオーナーやステークホルダーなど、技術者以外の関係者がDDDを理解し、一緒に取り組んでくこと一番いいと思うが、政治力が必要になりそう。
政治力欲しい...。