エリック・エヴァンスのドメイン駆動設計
モデル
モデルとは現実の何らかの側面や興味の対象となる概念を表している。
またモデルは簡素化したものである。
なので問題を解決する上での関連する側面を抽象化して、それ以外の詳細を無視する、現実に対する一つの解釈。
Custmer modelが存在したときに、現実ではクレームを言ったりするし、寄付をしたりすることもあるかもしれないが、Customerとしての要素のうちで、あるシステムにおいて必要な部分だけ切り取ることで、モデルとしての抽象化を成立させる。
ドメインモデルとは特定の図ではなく、図が伝えようとしている考え方である。
そのため、伝えるということにおいて図を必須として必要とするものではない。
ある目的に従って現実の概要を表現しているものであり、写実的なものではない。
ソフトウェアの核心はドメインに関係した問題をユーザーのために解決する能力である。
それ以外の特徴はこの目的を達成するための副次的なものに過ぎない。
ウォーターフォール手法とドメイン駆動手法(XP)の違いは、ソフトウェア開発者のフィードバックがあるかどうかである。ビジネスサイドはビジネスサイドのみで話した上でソフトウェア開発者に依頼を投げる。
これにより、ソフトウェア開発者のドメインについての学習の機会を損ねるため、新たな依頼などが積み上がってきたときに、既存のドメインとのよい組み合わせなどが発見できず、コードが散らばってしまう。
メンバー全員でドメインを噛み砕くことで、機械的に機能を作り出すのではなく開発者が原理を習得しようとし、ドメインエキスパートはより本質的なところを抽出しようとする
ポリシー化する
ある一部のドメインルールをアプリケーションメソッドに格納するのではなく、特別なメソッド、ポリシーメソッドを使用してそれを注入することにより、要件がわかっていない技術者に対しての設計図的な役割を示すようにする。
ユビキタス言語
ドメインエキスパートと技術者における言語に違いが生じて、翻訳コストが高くなるケースが存在する。
また一人の技術者としても話しことばと聞きことばが異なるケースは多様に存在する。
これらのケースを回避するために、予め専門用語を定義しておく。
これをユビキタス言語と呼ぶ。
図について
多くの場合モデリングとはUML図を指す事が多いが、UML図を書くのは労力を使いすぎる。
図の本質的な役割は、直感的に見ることでわかりやすい方法でコミュニケーションをとるという伝達手段に過ぎない。
そのため図は考え方の骨格を示している。
またその図が表す設計の詳細はコードによって落とし込まれているべきである。
モデルは図ではないということを理解するのは重要。
XPにおいてはプログラムに設計を語らせることを是としていて、設計書の作成をよしとしない。
しかしDDDにおいてはドキュメントは重要だと考える。
なぜならコードは詳細すぎて、全体像を追えないことがいくつかあるからである。
ドメインモデルとコードモデルは常に概ね一致するべきである。
結局設計と実装がかけはなれていたのでは意味がない。
そのために強固なユビキタス言語を使用して、ドメインと実装のどちらにも適用できるような、単一なモデルを作成すること。
モデリングをして、コードを書かないような仕事をしては行けない
モデリングの際のフィードバックを受けられなくなるから。
またコーディングをする人は全員、ドメインエキスパートととの話し合いに参加しなければならない
開発者がモデルの概念をおろそかにしていると、だんだん実装がモデリングと離れていき、最終的にモデルを用いないようなコーディングになってしまう場合が存在する。
モデル駆動設計においてモデルは疎結合にされるべきである。
そのため関連の矢印を双方向ではなく、単方向にすることで関連をへらす事ができる。
エンティティ
同一性を持つドメインオブジェクト
そのデータを一意に特定できるような性質を持つオブジェクト
例えば銀行の口座番号や、人でいうと名前/顔/生年月日などの組み合わせがIDとなる。
モデル図はエンティティがどのような関連を持つか、ということが重要である。
値オブジェクト
エンティティが同一性を担保できるオブジェクトに対して、値オブジェクトは同一性を担保できないオブジェクト。
銀行口座の預金、人でいう性別/年齢など。
module
疎結合/高凝集のコードの塊
moduleの名前にはユビキタス言語の一部を使用する
集約(Aggregate)
モデルはエンティティと値オブジェクトを含んだ、集合体となることがおおい。
この集合体の集約をどうするかが大事である。
集約したモデルは外部からアクセスできる位置を一つに絞る。
そうすることで内部の値が知らないうちに変更されているということを防ぐ事ができる。
集約をすることで、トランザクションの単位を統一し、アクセスすることで競合をなくす
ファクトリ
集約されたモデル群はどこかで作成されなければならない。しかし複雑な作成メソッドをドメインモデル自身に加えるのはオブジェクトの責務と外れてしまう。
しかし、サービスはアプリケーション層などにドメインの作成メソッドを組み込むと内部の実装が流出してしまうということにもなる。
したがってモデル作成ロジックはドメイン層のモデルの集合とは別の部分に、作成する必要がある。
これをファクトリと呼ぶ
銀行口座開設のように、ファクトリ自体がドメインとなるケースもあるが、ならないケースがほとんどである。
つまりこれは非機能要件となる。
ファクトリは通常、生成したいモデルと密接に関係しているモデルの中で実装する。
そうすることで、隠蔽力を上げる事ができる。
ファクトリはアトミックでないといけない
つまりモデル生成のメソッドは一つの箇所に集約してないと行けない
ファクトリは引数との結合関係にあるため、引数は慎重な検討が必要となる。
複雑すぎる引数設計は密結合の原因となる。
エンティティファクトリと値オブジェクトファクトリと最大の違いは、エンティティは後から値を追加できるという点である。
値オブジェクトは不変であるので、ファクトリで生成した後は変更不可のままでよい。
しかしエンティティファクトリは有効な集約に必要な値のみ必要なのであり、他のデータは随時setしておけばよい
ファクトリは再構成のための機能を保つ必要もある。
再構成のためのファクトリはIDを更新させてはいけない。
ファクトリは再構成のための機能を保つ必要もある。
再構成のためのファクトリはIDを更新させてはいけない。
repository
既に作成済みのオブジェクトにアクセスしなければいけない場合がある。
クエリはグローバルである。クエリを使うとどこからでも保存したデータにアクセスすることができるが、それはドメイン駆動設計のカプセル化から離れてしまっている。
また、集約オブジェクトが持つ値オブジェクトにアクセスするときには、必ず集約ルートをたどって値オブジェクトにアクセスしなければならない。
ただその関連付けが複雑すぎると、毎回たくさんのメソッドを通らないとデータの取り出しが実現できな
い。
リポジトリパターンとは主眼をドメインモデルに戻す役割をもつアーキテクチャである。
システムの根幹はデータアクセスにある。
しかし我々が扱わないといけないものはデータアクセスではなく、ドメインモデルである。
そのためデータアクセスはカプセル化して単純化する。
リポジトリの必要要素
型の抽象化
DBは多態性が存在しないので、抽象化することはコードの理解に貢献する
クライアントから切り離された点を活かすこと
クライアントはリポジトリにアクセスするだけでよい。そのためデータアクセスはRDBでなくてもよい。つまりインメモリデータベースに保存するようにするということもできる
トランザクションはクライアントに委ねる
リポジトリはトランザクション制御を持たない
ファクトリはオブジェクトのライフサイクルの初期、リポジトリは中期を表す
つまり最初の作成はファクトリ、データの格納/取り出しはリポジトリが管理する
しかし、リポジトリがなんらかのオブジェクトではないデータをオブジェクト化しなければならないケースが発生する(ファイルなど)
そのようなときはファクトリを使ってオブジェクトを再編成する。
ドメインモデルは改良の継続の末に本当にわかりやすいものになる。
その領域までいくのに開発者は負担を強いられないといけない
ドメインモデルには、仕様モデルと呼ばれるモデルに対する仕様を記載したモデルを作成することもある。
これはサービスと使用感が似ている
メソッド名、クラス名は重要である。
膨大なソフトウェアの機能から自分が修正したい部分を反映させる際に、他のコードをみる必要がある。
このとき、その膨大なソフトウェアを詳細にみる必要があるコードは開発者にとって優しくない。
よいソフトウェアの設計とは 名前を見ただけで、そのコードが何をしているのか判断できる である。
つまり、開発者は全てのコードを詳細に見なくても、修正コードを描くことができる。
この意味でメソッド名、クラス名は最重要である。
またテストコードをわかりやすく書くのも重要である。
テストで何をやっているかわかることでコードの処理の全容を簡単に把握する事ができる。
これこそが、カプセル化/高凝集による最大のメリットである。
副作用とは
ある関数を呼び出したときに、その関数が他の関数を呼び出すといった流れがいくつもネストされ、自分が把握していない範囲の変更もその関数を呼び出すことにより及ぼしてしまうこと。
このような関数は適切に抽象化されていないインターフェースといえる。
安全に中身を詳細に見ることなく関数を使用できるように適切な抽象化が必須となる。
この件におけるベストプラクティスは、その関数を呼び出した際の出力が何回やっても同じ値になることが保証されているかどうかというところである。
ある一定の引数に対して、返り値が違うとその関数は副作用を持っていると言える。
とは言っても状態を変更する操作もある。
これを本書では** コマンド **とよび、ステータスに変更のみの必要最低限の操作のみに留めるべきである。
値オブジェクトは不変であることが保証されている。
これを利用することで開発者はその関数が不変であることを保証するきっかけにすることができる。
そのため、値オブジェクトが使える部分があれば積極的に使用していくべきである。
オブジェクト同士の依存関係を極限まで減らすことは良いことであるが限界もある。
其の場合、閉じた操作を使用する。
これは引数と戻り値が等しい場合、複雑な処理を関数に押し込めるといったものである。
抽象的なインターフェースが明白であることがカプセル化の中で重要であるので、複雑性を関数の内部に押し込めることで、明白なインターフェースを作成する事ができる。