Domain Driven Design(ドメイン駆動設計) Quickly 日本語版
- DDD Quickly JPを読んだので、各章ごとの簡単なまとめを記述する。
- https://www.infoq.com/jp/minibooks/domain-driven-design-quickly/
イントロダクション
- ソフトウエアの設計は芸術であり、科学や数学の様に決まった回答など存在しない。
- なので、私たちはソフトウエア の作成過程全体に適用できるような、原則や技法を発見することがある。
- そのソフトウエアの設計にはいくつかの方法があり、この本の目的は、この20年間に現れて徐々に発展してきたある設計方法であるドメイン駆動設計という。
ドメイン駆動設計とは何か
- 初めに理解すべきことは、ソフトウエアは特定のドメインから生まれ、そしてそのドメインと深く関わっていること。
- ソフトウエアを導入する真の目的は、そのドメインの業務をより効率的にするため。
- なので、私たちもドメインから始める必要があり、ドメインの抽象を作る必要がある。
- その「抽象」とはドメインのモデルのこと。
- 大事なこととして、ドメインモデルはドメインの専門家の頭の中にある単なる知識ではなく、ドメインモデルはそういった知識を厳密に取捨選択しながら作成する抽象のこと。
- ドメインモデルを考える上で、もっとも大切なのは「モデルを他人に伝えなければならない」ということ。
ユビキタス言語
- ドメインモデルを作成するときは、このようなコミュニケーションのやり方違いを克服するため、共通言語の拠り所としてドメインモデルを使う。
- どんな形態のコミュニケーションであれ、常にこのドメインモデルで表現すべきであり、このような性質からこの言語は 「ユビキタス言語」と呼ばれる。
- ユビキタス言語を使うことで設計のすべての部分が結びつき、設計チームが効 率よく作業するための前提が生まれる。
モデル駆動設計
- そして次は、モデルをコードとして実装していく作業について。
- 推奨できる設計手法のひとつに分析モデルと呼ばれるものがある。この手法はコード設計の手法とは別のもので、コード設計者とは別の人が使うのが一般的であり、ソフトウエアとして実装することを考慮していない。
- このようなモデルを使うのはドメインを理解するためです。このモデルを使えば、ある程度ドメインの知識が確立でき、よく分析された正確なモデルが出来上がるが、混乱の原因になると思われるからだ。そして、このモデルが設計を担当する開発者へ渡された時にこのモデルは設計の原則を念頭において作られていないため、元のモデルとコードは無関係になることが度々。
- つまり、より良い方法はドメインモデリングと設計を密接にすることである。
レイヤアーキテクチャ
- アプリケーションを作成するとき、アプリケーションの大部分はドメインと直接関係しないが、そのような部分はインフラを構成し、ソフトウエア自体を支える。
- かといって、ドメインと関係するコードが他のレイヤに混ざると、機能が密着しすぎている実装になり、モデル駆動で開発したオブジェクトが役に立たない。
- したがって、複雑なプログラムをレイヤに分割する必要がある。各レイヤの凝集度が強く、下位のレイヤだけ依存するレイヤを設計するべき。
- 以下が4つのレイヤに分けた場合の例である。
ユーザーインターフェース:
ユーザに対して情報を表示し、ユーザの命令を解釈する。
アプリケーションレイヤ:
アプリケーションの活動を調整する薄いレイヤ。ビジネスロジックを含まない。また、ビジネスオブジェクトの状態を保持しな
い。しかし、アプリケーションの処理の進み具合を保持することがある。
ドメインレイヤ
ドメインについての情報を含むレイヤ。業務ソフトウエアの心臓部。ビジネスオブジェクトの状態を保持する。ビジネスオブジェ
クトの永続化処理をインフラストラクチャ層に委託する。まれにビジネスオブジェクトの状態もインフラストラクチャ層に委託す
ることがある。
インフラストラクチャレイヤ
他のすべてのレイヤを補助するライブラリとして働く。レイヤ間の情報のやり取りを制御し、ビジネスオブジェクトの永続化を実
装する。また、ユーザインターフェイス等を補助するライブラリを含む。
エンティティ
- 一意性を持つものとして分類できるオブジェクトが存在する。ソフトウエアの様々な状態の中で、常に同一であり続けるオブジェクトのことだ。
- このようなオブジェクトをエンティティと呼ぶ。
- エンティティはドメインモデルにおいて重要なオブジェクトであり、モデリングの始めから考慮するべきもの。オブジェクトをエンティティにするべきかそうでないのかを決めるのも重要。
バリューオブジェクト
- しかし、オブジェクトの中には一意性を保証する必要がないものもある。
- このようなオブジェクトをバリューオブジェクトと呼ぶ。
- バリューオブジェクトを作成するにあたって、ルールが存在する。①バリューオブジェクトは共有可能ならば、変更不可能にすること。②バリューオブジェクトは小さくてシンプルなオブジェクトにすること。③他のオブジェクトがバリューオブジェクトを必要とするときは、単純に値だけを渡してやるか、バリューオブジェクトそのものをコピーして渡してやること。一意性を保持する必要がないので、好きなだけコピーを作成できる。そして好きなときに破棄できる。
サービス
- ドメインにはどのオブジェクトにも属さないような振る舞い、つまり動詞がいくつかある。
- これらの振る舞いはドメインにとって重要だが、特定のオブジェクトにこの振る舞いを加えれば、そのオブジェクトは台無しになってしまう可能性がある。本来はどのオブジェクトにも属さない振る舞いを加えることになるからだ。
- しかし、振る舞いは必ず何らかのオブジェクトに属している。
- 例えば、ある銀行口座から他の口座へと送金する場合、送金機能は送金元の口座と送金先の口座のどちらにあるべきだろうか。どちらにあっても間違っているように思える。ドメインにこのような振る舞いが見つかった場合、最もよい方法はこの振る舞いをサービスとして定義することだ。
- サービスは内部に状態を保持せず、単純に機能だけを提供する。
モジュール
- モジュールはほとんどのプロジェクトで広く使われる。巨大なモデルの中からモジュールを見つけて、それらのモジュールの関係を見つければ、そのモデルを簡単に把握できます。
- モジュールを使うもうひとつの理由は、コードの品質に関係がある。ソフトウエアのコードは高い凝集度と低い結合度を持つべきだ、という考えは広く受け入れられている。凝集度はクラスやメソッドに対して使われていた考えだが、モジュールにも適用できる。
- モジュールの各部分が同じデータを操作する場合は通信的凝集になります。同じデータを扱う部分はグループ化したほうがいいように思える。それらは強く関係しているからだ。
- 他方、モジュール全体にまたがってひとつのうまく定義されたタスクを実行する場合は機能的凝集になり、これは最良の凝集だと考えられています。
- また、モジュールは正確に定義されたインターフェイスを持たなければならない。他のオブジェクトからアクセスできるのはそのインターフェイスだけにするべき。モジュール内の3つのオブジェクトを呼び出すよりも、ひとつのインターフェイスへアクセスするほうが優れた方法です。モジュール間の結合度が弱まるからだ。
アグリゲート
- アグリゲートとは、日本語で集約を意味する。アグリゲートはモデルの無駄な干渉や使いにくいシステムを改善してくれる概念のことだ。
- プロジェクトを開発するにあたって、ドメインモデルが複数個存在し、かつ依存し合う場合が存在する。
- 例えば、銀行のシステムにおいて、一人の顧客が複数個の口座を所持している場合などだ。ユーザー(User)と口座(Account)というドメインモデルが必要になる。
- この場合、UserがAccountを配列で所持していると仮定し、Userが所持しているすべてのAccountの残高を計算したいとする。これはドメインの知識として必要な関数となる。
- この処理を行うにあたり、ドメイン知識によるが、もしUser内のドメイン関数でなく、Accountのドメイン関数として必要になってくると、処理がものすごく複雑になる。なぜなら、Account自体は単体のモデルであり、それをUserが配列で複数個所持しているからだ。
- そこで登場するのが、ルートオブジェクトである。ルートオブジェクトとは、言葉の通り根幹となるモデルである。
- この例の場合、Accountを複数個所持可能なAccountsという新たなモデルを作成し、Accountsの中ですべての残高を計算するドメイン関数を定義してあげると良い。
ファクトリー
- ルートオブジェクトを通して、モデルに本当に必要な知識のみを伝えてあげることは可能である考え方がアグリゲートであるが、そのドメインモデルを作成する処理をドメインモデル内に記述するのは不適切である。なぜなら、ドメインの知識でないから。
- そこで、ファクトリーパターンが登場する。
- ファクトリーとはデザインパターンの一種であり、生成に関するパターンである。
- ファクトリメソッドは他のオブジェクトを作成するのに必要な知識を保持し隠蔽するメソッド。
- しかし、オブジェクトを取得するにあたって、毎回生成を行っていると不都合が生じるので注意。
リポジトリ
- そこで登場するのがリポジトリパターンである。
- リポジトリとは「貯蔵庫」という意味であり、オブジェクトの参照を取得するのに必要なロジックをすべてカプセル化するためのパターンのことだ。
- 注意点として、リポジトリがファクトリのように見えるかもしれないが、それは誤っている。
- 確かにリポジトリもファクトリと同様にオブジェクトを作成する。
- しかし、ゼロからオブジェクトを作成するのではなく、既存のオブジェクトを再作成する。ファクトリは新しいオブジェクトのみを生成し、リポジトリはそうでない。
- 新しいオブジェクトをリポジトリに追加する場合は、まずファクトリを使ってオブジェクトを作成し、それからそのオブジェクトをリポジトリに渡して保存する。