DDD本を読む前に概略をつかむ。
読んだPDFは下記から無料ダウンロードできる
1. ドメイン駆動とは何か
ソフトウェアは現実領域の問題を取り扱う。その現実領域のことをドメインという。
ドメインに対する十分な知識を持たなければ複雑な現実の業務をソフトウェアで取り扱うことはできない。
- ex)銀行業務のドメイン, 車の製造ライン
- ドメインのモデルのコードを読むだけで銀行業務の多くを理解することができる。
ドメインの抽象を作る
現場のドメインの専門家からドメインに関する多くの知識を得られるが、そこから抽象を作る必要がある。
そうでなければソフトウェアの構造に変換するのは難しい。
抽象とはモデルのことだ。ドメインモデル。
ドメインモデルはドメイン専門家の単なる知識ではない。それらの知識を取捨選択しながら作成する抽象(Abstract)のことだ。
図だけでなく注意深く書かれたコードや文章でも表すことができる。
モデルは他人に伝えることができなければならない。UMLなどを使って伝えることもできる。
代表的なソフトウェア設計の方法
- ウォーターフォール
- フィードバックサイクルがない
- 実装過剰になる
- XP(アジャイル)
- 実装過剰を恐れ、設計過剰になる。
1.1. ドメインの知識を構築する
ドメインの専門家は高度な専門知識を持ち、独特の方法で組み立ててその知識を使っている。
ただしその組み立て方はソフトウェアシステムとして実装するのに最適ではない場合が多い。
ソフトウェアの専門家(アーキテクトと開発者)とドメインの専門家はドメインモデルを一緒に考える。
ドメインモデルは2つの専門領域が出会う場所なので多くをこのモデリングに費やす意味がある。
2. ユビキタス言語
ソフトウェアの専門家とドメインの専門家の間には壁がある。ソフトウェアの専門家はプログラミングの言葉を使う。
ドメインについての共通の言語を作る。
ドメインモデル(=ドメインの抽象)に基づく言語を使う。
コードにもドメインモデルの言葉を使うようにチームメンバーに求める。
共通言語として使える言葉はいつでも簡単に現れるわけではない。
2.1. ユビキタス言語を創造する
開発者はドメインモデルの主要な概念をコードへ実装するべき。ドメインモデルとコードを対応づけることができる。
UMLはユビキタス言語の代替となりえるか。不足はある。以下2点がUMLだけでは伝えきれない。
- モデルが表す概念の意味
- オブジェクトがするべきこと
故に別の道具が必要となるが手書きの文章でも図でも問題ない。ドメインモデルは安定するまで何度も変更される。
ただし大きな図や長い文章はアンチパターンかもしれない。
設計チームに必要なのは作業を統一し、モデルの作成とコーディングを助ける共通言語
3. モデル駆動設計
簡単かつ正確にコードに落とし込めるモデルを選ぶ
分析モデルを使う方法は良し悪しがある。
- 業務ドメインを分析した結果であり、ソフトウェアとして実装することを考慮していない
- コードの設計担当に渡され、コーディングが始まった後は捨てられるかもしれない。
アナリストはドメインのすべての情報が圧縮されているモデルを作成し、開発者は渡された文書からそのすべてを学ばなければならない。
開発者がアナリストの会議に一緒に参加できるならなおのこと良い。
良い方法はドメインモデリングと設計を密接にすること。
分析と設計の間に乖離が生まれるのは、作業を通じて各自が得た知見なりを他のメンバに共有しないから。
ドメインモデルを正しく設計に反映させるには、ソフトウェアシステムを部分ごとに設計する。設計との対応関係が明確なモデルであること。
3.1. モデル駆動設計の基本要素
3.2. レイヤーアーキテクチャ
ドメイン駆動設計のために下記の4つの抽象レイヤを使う
- User Interface
- ユーザの命令を解釈する
- Application
- アプリケーション活動を調整する薄いレイヤ
- ビジネスロジックを含まない
- ビジネスオブジェクトの状態を維持しない
- アプリケーションの処理の進捗の状態を保持することがある。
- Domain
- ドメインについての情報を含むレイヤ
- ビジネスオブジェクトの状態を保持
- まれにインフラ層に委託することも
- 永続化処理をインフラ層に委託する
- Infrastructure
- 他の全てのレイヤを補助する
- レイヤ間の情報のやり取りを制御
- ビジネスオブジェクトの永続化
- UI等を補助するライブラリ
アプリケーション内でドメインに関係する部分は残りの部分と比べて小さくなるかもしれないがそれで問題ない。
(現時点でアプリケーション内と言っているのが列挙したAppのことを言っているのかアプリ全体のこと言っているのかがいまいちわからない)
ドメインと関係するコードが他のレイヤにまざると良くない(機能が密着するとテストの自動化しにくい等のデメリットが出てくる)。
そのためにプログラムをレイヤに分割する。レイヤの凝集度を高める。
ドメインオブジェクトはドメインモデルを表現することに集中する。
フライト予約の例
- app
- service
- flight_reserve
- call_domain_check_method and
- persistent to infra
- domain
- check_double_reserve
- check_hogehoge
3.3. エンティティ
一意性(Unique)を持つものとして分類できるオブジェクト。
オブジェクトをメモリ上に保持したり、シリアライズされてネットワーク経由で送信された先で再作成されることもある。
銀行口座エンティティがあったとして、一意性を識別するにはその口座番号を使うことができる。
人物エンティティならば名前、誕生日、住所等の組み合わせになるかもしれない。
モジュール内で自動的に生成されるIDや、データベースの主キーが一意性を保証するものになったりもする。
エンティティはドメインモデルにおいて重要なオブジェクト。オブジェクトをエンティティにするべきかそうでないかを決めるのも重要。
3.4. バリューオブジェクト
一意性を保証する必要のないオブジェクトをバリューオブジェクトという。
全てのオブジェクトをエンティティにするとシステムパフォーマンスに影響が出てくることもある。
バリューオブジェクトはコンストラクタで作成された後、破棄されるまで変更できないようにするのが望ましい。
不変(immutable)であり一意性を持たないバリューオブジェクトは共有できる。
変更できるオブジェクト(mutable)を共有するとどうなるかを考える。
航空予約システムで「フライトを表すオブジェクト」を作成し、顧客Aがあるフライトを予約した。顧客Bも同じフライトを予約した。
この時点で顧客Aと顧客Bが保持している「フライトを表すオブジェクト」は共有されているため、
顧客Aがフライトを変更すると顧客Bのフライトまで変更されてしまう。
ゆえにバリューオブジェクトが共有可能ならば変更不可能にすることが必須となる。他のオブジェクトが
バリューオブジェクトを必要とする時は値を渡すかバリューオブジェクトそのものをコピーして渡してやること。
一意性を保証する必要がないのでいくらでもコピーが可能
バリューオブジェクトは
- 他のバリューオブジェクトを含むことができる
- エンティティの参照を含むことができる
- ドメインオブジェクトの属性を含むために使われる
例えば
Customer-Class
- customerID
- name
- street
- city
- state
を リファクタして
Customer-Class
- customerID
- name
- address
Adress-Class
- street
- city
- state
とした方が優れた設計になる。
3.5. サービス
ドメインのある側面はオブジェクトに対応づけられない。
どのドメインにも属さないような振る舞い、つまり動詞、がいくつかある。
特定のオブジェクトにそういう振る舞いを加えると破綻のきっかけになったりする。そういう場合はサービスとして定義すればよい。
ex)ある銀行口座から他の口座へ送金する場合、送金機能は送金元と送金先のどちらの口座にあるべきか。(どちらにあっても間違っている)
サービスとして定義できる振る舞いをドメインオブジェクトには含めない。
ドメインオブジェクトに属するような操作をサービスにしてはいけない。必要な操作をすべてサービスとして作成するのもよくない。
操作がドメインの概念の中で重要ならばサービスとして作成すべき。
各レイヤにサービスは存在する。
アプリレイヤはコントローラーのようなもの?UIレイヤとドメイン、インフラレイヤの間にある。
3.6. モジュール
大規模なアプリケーションになる場合、ドメインモデルも大きくなるので、ドメインモデルを複数のモジュールの集まりとして構築する。
コードに高い凝集度と低い結合度を持たせられるようになる。
凝集度にはいくつかの種類があるが最も使われている「通信的凝集」と「機能的凝集」がある。
- モジュールの各部分が同じデータを操作する場合は通信的凝集
- モジュールの全体にまたがってひとつのうまく定義されたタスクを実行する場合は機能的凝集
クラスのリファクタよりもモジュールのリファクタの方がコストがかかるが、もし設計ミスが見つかった場合は回避するよりも修正する方が良い。
3.7. アグリゲート
ドメインオブジェクトのライフサイクルの管理には難題がある。
アグリゲートはオブジェクトの所有権と境界を定義するのに使うパターン
ファクトリとレポジトリはオブジェクトの作成と保管に役立つパターン
アグリゲートはデータの完全性を保証し、不変性を維持する。
それぞれのアグリゲートはそれぞれ一つのルートを持ち、ルートになるのはエンティティ
アグリゲートの外部にあるオブジェクトがルートの参照しか保持できないようにすることでそれが可能。
3.8 ファクトリ
3.9. リポジトリ
ファクトリはドメインの要素でありドメイン以外とは関係を持っていない。
リポジトリはインフラ、例えばデータベースと結びつく。
4 リファクタリングのためのさらに深い洞察
4.1. 継続的なリファクタリング
コードのリファクタリングの目的はコードを改善することであって悪くすることではない。
コードだけでなく、ドメインとモデルに対するリファクタリングも存在する。
「コードを読めばわかる」というのは正しい設計が存在していることを前提とする。
その場合にコードを読めば何をしているのかがわかるだけでなく、なぜそれをしているのかがわかる。
4.2. 鍵となる概念を明らかにする
設計のある部分があまり明確でない時は足りない概念がないかを調べ、設計をリファクタする。
ドメインの専門家が行ったことが他の人の主張と矛盾しているように感じる時もある。
そういう場合は本当に矛盾しているのではなく同じものを違う見方で見ているだけだったりする。
制約、プロセス、仕様といった概念を明確にできるとよい。
「仕様オブジェクトにまとめる」
これはCustomerオブジェクトに様々なビジネスルールを含めていくとすぐに肥大化していくのを抑えるのにやくだつ。
一つの仕様(Specification)は単純なルールが守られているか判断し、複数のルールを組み合わせることで複雑なルールを表現する。
5 モデルの完全性を維持する
巨大なプロジェクトでもドメインモデルに基づいて設計をするべき。
複数チームでコーディングが並行して進む場合、モデルの特定の部分が割り当てられている。
ここで起こりうることは、各人が知らないうちにモデルの変更をコーディングしてしまっていること。
モデルの完全性を保つために一つの巨大な統一モデルを保守しようと努力してもうまくいかない。
一つの巨大なモデルは意識的にいくつかのモデルに分割するのが解決方法となる。
モデルの完全性を維持するための方法は次に挙げていく通り。
5.1. コンテキスト境界
モデルは一つのチームに割り当てることができるくらい小さい方が良い。
巨大プロジェクトでは複数のモデルが活動する。個々のモデルに基づくコードが結合されたとき、
バグが埋め込まれ、理解するのが難しくなる。モデルがどんなコンテキストに適用されるか明らかでないためだ。
モデルのコンテキストとは、そのモデルに適用される一群の条件のこと。
それらの条件はモデル内で使われる用語が特定の意味を確実に持つようにするために必要。
コンテキスト境界はモジュールではない。コンテキスト境界はモジュールを包み込む枠組み。
5.2. 継続的な統合
チームメンバ全員がモデルの各要素の役割を確実に理解する必要がある。
さもないと誤ったリファクタリングをする可能性が残る。
ドメインモデルは毎日継続的に成長していくため、コンテキスト境界を扱うには継続的な統合が必要だ。
文字通りCIでマージからの自動ビルドを実行してコードを適切に統合していく必要がある。
5.3. コンテキストマップ
コンテキストマップは異なるコンテキスト境界と、それらの関係についての概略を示すための文書
関係する誰もがコンテキスト境界や、コンテキストとコードの対応関係を知っているべき。
どのモジュールがどのコンテキストに属するかわかるようにする。
コンテキストにおいて密接な関係がある場合に「共有カーネル」「カスタマ-プライヤ」パターンを使うことになる。
「公開ホストサービス」「防腐レイヤ」はレガシーシステムやが言うシステムと連携するために使うパターン
5.4. 共有カーネル
チームに割り当てられたモデルとそのコンテキスト境界には重複する所が出てくる。
共有カーネルの目的はその重複を少なくして2つのコンテキストを分離しておくことにある。
共有カーネルに加えられて変更は他のチームにすぐに連絡をする必要がある。
週単位で変更はマージし、自動テストされる必要がある。
5.5. カスタマ-サプライヤ
ふたつのサブシステムが特別な関係を持っている場合がある。
一方の処理結果がもう一方への入力になるケース。
2つのサブシステムの間に共有カーネルはない。そういう場合、この2つはカスタマ-サプライヤ関係にあるという。
片方のサブシステムの変更はデータベースのスキーマの変更を要することが多くあり、もう片方に影響を及ぼす。
その時におこる問題を回避するにはそれぞれのサブシステムを担当するチームがコミュニケーションする必要がある。
5.6. 順応者
前述のカスタマ-サプライヤ関係は両方のチームがこの関係に注意を払っている場合にうまくいく。
ただしカスタマチームはサプライヤチームに深く依存する。
チームの関係性や管理がなされていない場合には次第にサプライヤはカスタマに注意を払わなくなってくる。
忙しさに負ける場合もある。また、カスタマ-サプライヤの各チームが別の会社に属している時にも問題が発生する。
そんな時にはカスタマチームはサプライヤチームから離れて作業が完結する方法を選ぶこともある。
カスタマチームはサプライヤチームのモデルの変更から自身のモデルを保護する方法が必要になる。
カスタマとサプライヤの2つのコンテキストの間をつなげる変換レイヤを実装する必要性
5.7. 防腐レイヤ
レガシーアプリケーションはドメインモデリングが適切になされていないことがほとんど。
それらのモデルをドメインモデリングで作成したモデルと統合する必要がある。
外部システムとはネットワークを介して接続する方法と、データベースを使う方法とがある。
外部システムを利用するクライアントのモデルと外部のモデルとの間に防腐レイヤが存在する。
外部のモデルは内部のモデルと全く関係ないものもあるので防腐レイヤを挟んで
外部のモデルとの対話はそこに任せるようにする。防腐レイヤは内部のモデルの一部分となる。
防腐レイヤをサービスとして実装する。サービスはFacadeパターンとして実装するのが現実的
アダプタパターンにも似ている。
5.8. 別々の道
各チームのメンバはサブシステム同士の関係が最適になるように多くの時間を費やす。
チームのメンバが他のチームの要求を実装するためだけに多大な時間を費やすこともある。
独立して開発を進めて取捨選択するのも大切だが、自分が作成したモデルが他のシステムの
枠組みの中に適切にはまるようにするのも大切だ。
統合には価値よりも問題の方が大きいと結論付けられる時、別々の道を選ぶことが選択肢となる。
「別々の道」パターンが扱うのはモデルの観点からみて、microservicesで構成されたアプリケーションで、
各アプリがモデルの観点からみてほとんど、あるいは全く共通部分を持たない場合。
5.9. 公開ホストサービス
ふたつのサブシステムを統合する場合、変換レイヤを挟むのが一般的。
変換レイヤはクライアントになるサブシステムと統合したい外部システムの間のバッファとなる。
外部サブシステムをサービスプロバイダと見なしてサービスに包んでしまうと変換レイヤは必要なくなる。
連携の方式を定義する。
5.10. 蒸留
混ざり合っているものを分離すること。混合物から特定の物質の抽出をすること。副産物も手に入る。
大きなドメインを表現すると大きなモデルができあがる。
リファクタリングを繰り返しても小さくはならないので、そういう状況になった場合は「蒸留」するタイミング。
ドメインの中核を表すコアドメインを定義する。副産物がサブドメイン。
航空監視システムのコアドメインは飛行航路を算出する部分がコア。
衝突の可能性がある場合はシステムを使っている管制官が警告を受け取り、各飛行機に発する。
あるアプリケーションのコアドメインは別のアプリケーションのサブドメインになることもある。
サブドメインを実装するためのいくつかの方法
- 既成のソリューション
- 既に完成していることがメリット
- 自前の実装と比べてコアドメインと結合するのは簡単ではない
- アウトソーシング
- 他のチーム(別の会社)が設計と実装を行う。
- アウトソースしたコードの結合は大概面倒になる。
- 既存のモデル
- 自前の実装
- 最善の水準で統合できる。
- 余計な労力がかかりがち
originはこちら