[読書]ドメイン駆動設計入門
Chapter1 ドメイン駆動設計とは
- ドメインは領域の意味を持った言葉。ソフトウェアにおけるドメインは「プログラムを適用する対象となる領域」を指す。
- ドメインに含まれるものが何かが重要。
- モデル:現実の事象あるいは概念を抽象化した概念。
- モデリング:抽象化する作業のこと。結果としてモデルが得られる。
- ドメイン駆動設計では、モデルのことおドメインモデルと呼ぶ。
- ドメインオブジェクト:ドメインモデルをソフトウェアで動作するモジュールとして表現したもの。
Chapter2 値オブジェクト
- 値オブジェクト:システム固有の値を表現するために定義されたオブジェクト。
- 値の3つの性質
- 不変である
- 交換が可能である
- 透過性がによって比較される
- 不変である
- 値オブジェクトの値は変更されない。値を変更するためのメソッドは実装されるべきではない。
- 交換が可能である
- 値は不変であるが値を変更することは必要。
- 一見矛盾している用に見えるが、ここでいう値の変更というのは「変数に代入すること」を指す。
- つまり、値オブジェクトの変更は値と同じように代入操作によって交換することをで表現される。
- e.g.) 値オブジェクトを new して代入する。元の値を変更するというよりは、新しいオブジェクトを生成し入れ替えるイメージ。
- つまり、値オブジェクトの変更は値と同じように代入操作によって交換することをで表現される。
- 一見矛盾している用に見えるが、ここでいう値の変更というのは「変数に代入すること」を指す。
- 値は不変であるが値を変更することは必要。
var hoge = new Hoge('A');
hoge.change('B'); // NG:不変であることに違反している。
hoge = new Hoge('B'); // OK:値の交換(代入)で変更している。
- 等価性によって比較される
- 値オブジェクトはシステム固有の値。あくまでも値。
- 値オブジェクトを比較する際は、値オブジェクトの属性を比較するための Equals メソッドを実装し利用する。
- 値オブジェクトにすべきかどうかの判断基準
- そこにルールが存在しているか
- それ単体で取り扱いたいか
- 値オブジェクトで重要なことは独自の振る舞いを定義できること
- 値オブジェクトを採用するモチベーション
- 表現力を増す
- ただのプリミティブな String とかではなく、XXName とかにすることで、この値オブジェクトの構成がわかりやすくなる。
- 不正な値を存在させない
- バリデーションを行うことができる。
- 誤った代入を防ぐ
- プリミティブだと実装ミスで定義しても気づきにくく、コードを見るだけで判別し辛いが、値オブジェクトだと気付きやすくなる。
- 自己文書化を正当化できる。
- ロジックの散財を防ぐ
- 値オブジェクトを扱うふるまいを値オブジェクト内で定義することで、ロジックの散財を防げる。
- 表現力を増す
Chapter2 値オブジェクト 感想
今まではなぜ値の変更はダメだが、new したオブジェクトを返すのは良いのか。という概念が理解できていなかったが、この章を読むことで理解できたように思う。
コードの自己文書化というのは、とても開発者フレンドリーな考え方だと思ったので、ドメインオブジェクトの実装を意識して取り組みたい。
Chapter3 エンティティ
- 値オブジェクトもエンティティもどちらもドメインオブジェクト
- 違いは同一性によって識別されるか否か。
- エンティティは属性ではなく同一性によって識別されるオブジェクト
- エンティティの3つの性質
- 可変である
- エンティティはふるまいを通じて属性を変更できる。
- ただし可能な限り不変にしておくことが望ましい。
- エンティティはふるまいを通じて属性を変更できる。
- 同じ属性であっても区別される
- 値オブジェクトは等価性によって比較されるため、属性がそれぞれ同じであるオブジェクトはまったく同じものとして扱われる。
- そのため例として、「人間」は値オブジェクトにあてはめることができない。
- エンティティ同士を区別するためには、識別子(Identity)が利用される。e.g.) XXId
- 値オブジェクトは等価性によって比較されるため、属性がそれぞれ同じであるオブジェクトはまったく同じものとして扱われる。
- 同一性により区別される
- 属性に変更があったとしても、変更前、変更後と同一のオブジェクトとみなす。
- e.g.) ユーザネームを変更したとしても、変更前、変更後と同一のユーザである。
- エンティティの比較処理では、同一性を表す識別子(id)だけが比較対象となる。
- cf.) 値オブジェクトの比較処理は全ての属性が対象となる。
- 属性に変更があったとしても、変更前、変更後と同一のオブジェクトとみなす。
- 可変である
- 値オブジェクトとエンティティの判断基準
- ライフサイクルが存在し、連続性が存在するかどうか。
- そういったものはエンティティで実装することが望まれる。
- e.g.) ユーザー
- 作成 -> 変更 -> 削除
- e.g.) ユーザー
- そういったものはエンティティで実装することが望まれる。
- ライフサイクルが存在し、連続性が存在するかどうか。
- ドメインオブジェクトを定義するメリット
- コードのドキュメント性が高まる
- ドメインにおける変更をコードに伝えやすくする
Chapter3 エンティティ 感想
値オブジェクトとエンティティとの違いがわかった。
エンティティと聞くと、DB を操作する際に扱う ORM とやり取りするためのクラスという印象があったが、DDD の観点では全く別ものであるということがわかった。
今後この手の話をする際は、混合しないように注意していきたい。
FYI) by chatgpt
DDD(ドメイン駆動設計)の世界でのエンティティ:
DDDでは、「エンティティ」はドメインモデル内の重要な概念の一つです。これは、変更可能な状態を持ち、同一性に基づいて識別されるものです。つまり、同じ属性を持つ別のエンティティがあっても、それらは異なるエンティティと見なされます。エンティティは通常、ビジネスルールにおいて重要な対象や概念を表現するために使用されます。データベースの世界でのエンティティ:
データベースの文脈では、「エンティティ」は通常、データベース内のテーブルに対応します。テーブル内の各レコードはエンティティのインスタンスであり、各列はそのエンティティの属性を表します。この場合のエンティティは、データベース内での永続化や検索を容易にするために使用されます。
Chapter4 ドメインサービス
- ソフトウェア開発の文脈で語られるサービス: クライアントのために何かを行うオブジェクト
- ドメイン駆動設計で登場するサービス
- ドメインサービス
- アプリケーションサービス
- ドメインサービス:値オブジェクトやエンティティに記述すると不自然になるふるまいを解決するためのオブジェクト
- 不自然な振る舞い e.g.) ユーザーのドメインオブジェクトに重複確認のふるまいを実装する。
- 誰が誰とが分かりづらく違和感のある実装になる。
- 不自然な振る舞い e.g.) ユーザーのドメインオブジェクトに重複確認のふるまいを実装する。
- ドメインサービスは濫用してはいけない
- 乱用するとドメインオブジェクトがただのデータだけを持つ無口なオブジェクトになる。
- こういったオブジェクトの状態をドメインモデル貧血症という。
- 本来ドメインオブジェクト内で閉じていたロジックが点在してしまう。
- 乱用するとドメインオブジェクトがただのデータだけを持つ無口なオブジェクトになる。
- ドメインのサービスの命名規則
- ドメインの概念 e.g.) User
- ドメインの概念 + Service e.g.) UserService ★筆者のおすすめ
- ドメインの概念 + DomainService e.g.) UserDomainService
- 共通認識が取れていればどれでも問題ないが、Service とつけたほうがサービスということがわかりやすい。
- だいたい Domain パッケージの階層内にドメインサービスを作ることとなるので、Service だけとすることが多い模様
Chapter4 ドメインサービス 感想
自分の中でドメインサービスは悪だという言葉が独り歩きしており、どうして悪なのかがピンと来ていなかったが、腹落ちできた。
また思考停止で悪というわけでもなく、使用したほうが可読性をあげるケースもありそうなので、必要に応じて採用していきたい。
Chapter5 リポジトリ
- リポジトリはデータを永続化し再構築するといった処理を抽象的に扱うためのオブジェクト
- リポジトリには具体的でややこしいデータ永続化の処理を扱い、アプリケーションの処理の趣旨を際立たせる効果がある
- リポジトリは現在のインスタンスの状態を永続化し、また再構築するオブジェクト
- リポジトリの責務はあくまでオブジェクトの永続化。
- そのため、インスタンスの保存(Save)や復元(Find)メソッドは存在しうるものであるが、重複チェック(Exists)は責務としてふさわしくない。(リポジトリの実装次第で動作が変わってしまう可能性があるため)
- その場合はドメインサービスが主体となって重複確認処理の責務をもたせる。
- そのため、インスタンスの保存(Save)や復元(Find)メソッドは存在しうるものであるが、重複チェック(Exists)は責務としてふさわしくない。(リポジトリの実装次第で動作が変わってしまう可能性があるため)
- リポジトリのテストをするときは、メモリをデータストアとして、インメモリで動作すると捗る。e.g.) 連想配列
- 特定の技術基盤に流用するためにドメインオブジェクトにゲッターやセッターを定義しない。
- 永続化のふるまいは永続化を行うオブジェクトを引数にとる。つまり対象の識別子と更新項目を引き渡して更新させるようなメソッドは用意しない。
- これをしてしまうと、リポジトリに多くの更新処理を定義させる結果となってしまうため。
- e.g.)
- OK: Update(User)
- NG: UpdateName(UserId, UserName), UpdateEmail(UserId, Email), UpdateAddress(UserId, Address)
- e.g.)
- これをしてしまうと、リポジトリに多くの更新処理を定義させる結果となってしまうため。
Chapter5 リポジトリ 感想
リポジトリに関しては自分が元々思っていた考えとほぼ同じ内容だったので、おさらいのような形となった。
テストにインメモリを使うことを推奨するというのは、私にとって新しい考え方だった。
データストアのコンテナを立ててまで UT, IT をする必要がないテストに関しては、こういった手法を使うのも手だと思ったので、頭の片隅に入れておく。
Chapter6 アプリケーションサービス
- アプリケーションサービスはユースケースを表現するオブジェクト
- アプリケーションサービスのでドメインオブジェクトを公開するメリット・デメリット
- メリット
- アプリケーションサービスの実装コードが比較的シンプルになる。
- デメリット
- ドメインオブジェクトを返すことで、意図せぬメソッド呼び出しを可能にしてしまう。
- ドメインオブジェクトのふるまいを呼び出す役目はアプリケーションサービス。
- その枠組を超えて、ドメインオブジェクトのふるまいを呼び出してしまうとアプリケーションとして提供されるべきだったコードが各所に散りばめられてしまう。
- ドメインオブジェクトに対する多くの依存が発生することは問題。
- メリット
- 対策方法
- アクセス修飾子(public, protect, private)によるメソッド呼び出しの制限
- パッケージ構成によっては意味をなさない
- ドメインオブジェクトのメソッド呼び出しに対して制限を加えるという紳士協定
- 強制力が小さく、期待できない
- ドメインオブジェクトを直接公開しない(推奨)
- クライアントには DTO にデータを移し替えて返す。
- DTO に対するデータの移し替え処理はアプリケーションサービスで実装する。
- 修正箇所をまとめるために DTO のコンストラクタでインスタンスを引数として受け取る
- フィールドが増えても修正箇所が DTO 内に閉じる
- クライアントには DTO にデータを移し替えて返す。
- アクセス修飾子(public, protect, private)によるメソッド呼び出しの制限
- ドメインオブジェクトを非公開にすることでコード量は増えるので、プロジェクト特性を考慮して採用する
- コマンドオブジェクトを用いることで、アプリケーションサービスのメソッドのシグネチャが変更されることを避けることができる。
- e.g.)
- Update(UserId, UserName)
- -> Update(UserCommand) コマンドオブジェクト UserCommand に UserId, UserName を閉じることで、扱うフィールドが増えても Update()メソッドを修正する必要がなくなる。
- e.g.)
- コマンドオブジェクトは処理のファサードといえる。
(FYI)
- コマンドオブジェクト:ソフトウェアデザインの一部として使われるデザインパターンの一つで、特定の操作やリクエストをオブジェクトとしてカプセル化する方法。
- ファサード:複雑なシステムやサブシステムの一連のインターフェースに対して、簡素な統一されたインターフェースを提供する。
- エラーを使うか例外を扱うか
- エラー
- メリット
- 結果オブジェクトを返すため表現に柔軟性がある。
- デメリット
- 開発者に対して強制力を持たないため、ハンドリングはクライアントの任意となり、見過ごしてしまう可能性がある。
- メリット
- 例外
- メリット
- 強制力を持つ。例外なのでハンドリングをしなければ、プラグラムが終了する。
- デメリット
- 戻り値を返却しないため、エラーの表現ができない。
- エラーを返す場合に比べて、パフォーマンスが少し落ちる
- メリット
- エラー
- アプリケーションサービスはドメインオブジェクトのタスク調整に徹するべき。
- 凝集度:モジュールの責任範囲がどれだけ集中しているかを測る尺度。
- LCOM:凝集度を測る計算式
- 端的に説明すると全てのインスタンス変数は全てのメソッドで使われるべきという指標。
- LCOM:凝集度を測る計算式
- 凝集度を高めるにはクラスを分割する。
- クラスを細かく分割することで、まとまりの表現ができなくなる。
- その時はパッケージ(名前空間)で表現する。
- より柔軟性を担保するには、アプリケーションサービスにインタフェースを用いると良い。
- 実装クラスをモック化することで、並行した開発やモックを用いたテストが行いやすい。
- サービスとはクライアントのために何かを行うモノ。
- サービスは自身のふるまいを変化させる目的で状態を保持しない。e.g.) isSend などの boolean をアプリケーションサービスに持つ
- 状態がもたらす複雑さは開発者を混乱させる。
- アプリケーションを実装する時に気をつけることは、ドメインのルールが記述されないようにすること。
Chapter6 アプリケーションサービス 感想
ドメイン駆動開発を行っていると、たくさんのオブジェクトが出てくるが、その理由がアプリケーションサービスの説明で少し分かった気がする。
それぞれの層が持つ責務をしっかり理解して開発・設計を行う必要があると強く感じた。
Chapter7 依存関係のコントロール
- 依存はあるオブジェクトからオブジェクトを参照するだけで発生する。
- それはインタフェースも同様
- アプリケーションサービスは必然的に依存関係を持つこととなる。その時、抽象型に依存させると依存をコントロールさせることができる。
- 依存関係逆転の原則
- 上位レベルのモジュールは下位レベルのモジュールに依存してはならない、どちらのモジュールも抽象に依存すべきである。
- 抽象は、実装の詳細に依存してはならない。実装の詳細が抽象に依存すべきである。
- 抽象に依存せよ
- プログラムにはレベルと呼ばれる概念がある。レベルは入出力からの距離を示す。
- 抽象型を導入することで、Service も Repository も抽象型である IRepository に依存することになる。
- これにより上位レベルのモジュールが下位レベルのモジュールに依存しなくなり、抽象に依存すべきを果たすことができる。
- e.g.)
- x: Service -> Repository
- o: Service -> IRepository <- Repository
- e.g.)
- これにより上位レベルのモジュールが下位レベルのモジュールに依存しなくなり、抽象に依存すべきを果たすことができる。
- 主導権を抽象に
- 抽象が詳細に依存すると、低レベルの方針変更が、高レベルに波及する。これはおかしい。
- 主体となるべきは高レベルのモジュール。
- 依存関係をコントールするパターン。
- Service Locator パターン:ServiceLocator と呼ばれるオブジェクトに依存解決先となるオブジェクトを事前に登録し、必要となる各所で ServiceLocator を経由してインスタンスを取得するパターン
- ただしアンチパターンであるとも言われている。
- 依存関係が外部から見えづらくなる
- テストの維持が難しくなる
- 変更が発生した場合、テストを実行するまで発覚しづらい。テストを維持するにはある程度の強制力が必要。
- IoC Container パターン
- 依存性の注入(Dependency Injection)をサポートするための仕組み。Spring フレームワークでも使われている。
- コンストラクタインジェクションを用いると、依存が増えた時にコンストラクタの引数を変更する必要が出てくる。
- 適切な変更がないとコンパイルエラーが発生する。-> 変更に強制力をもたせることができる。
- コンストラクタインジェクションを用いると、依存が増えた時にコンストラクタの引数を変更する必要が出てくる。
- 設定によって具象クラスを制御することができる。
- e.g.)
- @Qualifier を用いて、適切な具象クラスを制御できる。
- @Profile を用いて、production, dev, test などの環境毎の具象クラスを制御できる。
- e.g.)
Chapter7 依存関係のコントロール 感想
個人的にここまでで一番ためになった章。今までなかなか理解できていなかった依存関係逆転の原則について実例を用いた説明のおかげで、腹落ちさせることができた。
この章を理解するかしないかでアプリケーションの開発に大きく影響を及ぼすので、しっかり頭に留めておきたい。
Chapter8 ソフトウェアシステムを組み立てる
- UI がGUI であっても CUI であっても、ドメイン駆動設計の恩恵を受けることができる。
- シングルトンと static は異なるもの。
- シングルトン:インスタンスを一つに限定しながら、通常のオブジェクトと同様に取り扱える。
- static:クラスのメンバやメソッドに適用される修飾子。クラス全体に共通するメンバやメソッドを表現できる。
- コントローラ:コントローラの責務は入力の変換。フロントからのデータをビジネスロジックが必要とする入力データへ変換する作業に集中する。
- コントローラのコードはほとんど同じようなシンプルなコードとなることが多い。
- コントローラがそれ以上のことをこなしている場合、ドメインの重要な知識やロジックがコントローラに漏れ出していることを疑う。
- コントローラが受け取るオブジェクトはアプリケーションサービスが受け取るオブジェクトとほとんど同じ構造になることがある。それらは用途が異なるものなので、原則として使い回さない方が良い。
Chapter8 ソフトウェアシステムを組み立てる 感想
目新しいことは特になかったが、
コントローラの説明をゲームに例えているは、個人的にわかりやすく秀逸だと思った。
Chapter9 ファクトリ
- ファクトリ:生成を責務とするオブジェクト
- ファクトリが活躍する例
- 生成時の採番処理
- コンストラクタで DB の採番テーブルにアクセスする処理がある場合、それをファクトリに移譲する。
- 生成時の採番処理
- ドメインオブジェクトを見てもファクトリに気づけない。
- そのため、パッケージのグループでファクトリに気づけるようにする。
- SnsDomain.Models.Users.User
- SnsDomain.Models.Users.IUserFactory
- そのため、パッケージのグループでファクトリに気づけるようにする。
- クラスそれ自体がファクトリになる以外に、メソッドがファクトリとして機能することもある。オブジェクトの内部データを利用してインスタンスを生成する必要があるときに利用される。
- 生成が複雑なインスタンスを構築する処理をまとめるためにファクトリを利用するのはよい習慣。
- コンストラクタはなるべく単純にしておきたいため。
- 目安としては、コンストラクタ内で他のオブジェクトを生成するかどうか。
- 生成処理が複雑でないのであれば、素直にコンストラクタで呼ぶ方が好ましい。
- ファクトリはドメインを由来とするオブジェクトではないが、ドメインを表現するために必要なオブジェクト。ドメインの設計を完成させるためには、ドメインモデルを表現する以外の要素が存在することを認識しておくことが大切。
Chapter9 ファクトリ 感想
ファクトリというと何か小難しいイメージがして食わず嫌いをしていたが、本性で有用性や使い方について知ることができた。
意識せずに使っていたクラスやメソッドがファクトリだったんだなと改めて思ったので、今後はより自信を持って設計・開発ができそう。
Chapter10 データの整合性を保つ
- 整合性とは、「矛盾がなく一貫性のあること・ズレがないこと」をいう
- 整合性を保つために実施する2つの防衛
- ユニークキー制約による防衛
- ユニークキー制約によって重複登録などを防ぐことができる
- しかし、ドメインのルールを守るためにユニークキー制約だけに頼ることは得策ではない。
- コード上にドメインのルールが表現されず、開発者からどういうルールが存在するのかが分からないため。
- ユニークキー制約はセーフティネットとして活用されるべき。ソフトウェアの安全性を高めるためにドメインルールをビジネスロジックで表現し、ユニークキー制約もかけるといった併用すると良い。
- トランザクションによる防衛
-
トランザクションによって、関連する一連の動作の整合性を保つことができる。
-
トランザクションを扱うには、データベースコネクションを使い回す必要がある。
- ただ、データベースコネクションをサービスに依存させるのはよろしくない。
- 特定の技術基盤に依存することになり、上位レベルのモジュールが下位レベルのモジュール依存することになるため。
- ただ、整合性という考え方ドメインルールで表したい概念であるため、明示的に表現したい。
- 特定の技術基盤に依存することになり、上位レベルのモジュールが下位レベルのモジュール依存することになるため。
- ただ、データベースコネクションをサービスに依存させるのはよろしくない。
-
整合性を保ちがながら特定の技術基盤に依存せず明示的に表現させるパターン
- トランザクションスコープを利用したパターン
- C# の機能。
- トランザクションを行う範囲を定義している。トランザクションスコープ自体でトランザクションが開始されるわけではなく、このスコープ内で DB コネクションを開こうとする際に自動でトランザクションが開始される。
- AOP を利用したパターン
- Java で使われる機能
- @Transaction をメソッドに付与する。そのメソッドの終了時にコミットが実施される。
- そのメソッドでトランザクションが開始されることが分かりやすい。
- ユニットオブワークを利用したパターン
- オブジェクトの変更を記録するオブジェクト(ユニットオブワーク)を利用する
- データベースへの変更を管理し、一括してコミットまたはロールバックを行う。
- トランザクションスコープを利用したパターン
-
トランザクションは一貫性を保証するためにデータをロックする
- ロックの範囲は可能な限り小さくすべき。
- 1つの指針としては、一度のトランザクションで保存するオブジェクトを1つに限定し、そのオブジェクトをなるべく小さくすること。
-
- ユニークキー制約による防衛
Chapter10 データの整合性を保つ 感想
整合性はドメインルールなので、明示的に表現したいというのは、確かになーと感じた。
DDD の思想に則ってコードのドキュメント性を高め、開発者フレンドリーなコードを心がけたい。
Chapter11 アプリケーションを1から組み立てる
- ドメインの重要なルールはアプリケーションサービスに記述しない。
- 複数箇所に重複する可能性があり、修正が困難となるため。
Chapter11 アプリケーションを1から組み立てる 感想
今までの章で習った内容のおさらいという章。特筆することは特になし。
Chapter12 ドメインのルールを守る「集約」
- 集約は不変条件を維持する単位として切り出され、オブジェクトの操作に秩序をもたらす。
- 集約には境界とルートが存在する。
- 集約の境界:集約に何が含まれるのかの定義
- 集約ルート(AR:Agregate Root):集約に含まれる特定のオブジェクト
- 外部からの集約に対する操作は集約ルートを経由して行う。
- 境界内に存在するオブジェクトを外部にさらけ出さないことで、集約内の不変条件を維持できる。
- 外部からの集約に対する操作は集約ルートを経由して行う。
- 集約を表す図はあくまでも集約の境界と、そこに含まれるモデルが主題であり、コードに対する正確性を問うものではない。
- デメテルの法則:オブジェクトの操作に関する基本的な原則
- メソッドを呼び出すオブジェクトは4つに限定される
- オブジェクト自身
- 引数として渡されたオブジェクト
- インスタンス定数
- 直接インスタンス化したオブジェクト
- フィールドがゲッターを通じて公開されていると、本来オブジェクトに記述されるべきルールが漏れ出す可能性がある。
- メソッドを呼び出すオブジェクトは4つに限定される
デメテルの法則について
cf.)https://tech-blog.rakus.co.jp/entry/20200701/programming
- オブジェクトの内部データはむやみに公開するべきではないが、完全に非公開にするとリポジトリがインスタンスを永続化する時に困る。
- そのためのアプローチ
- ルールによる防衛
- 「リポジトリ以外で getter/setter を使わない」という紳士協定。
- 強制力が低いため、用意に破られてしまう。
- 「リポジトリ以外で getter/setter を使わない」という紳士協定。
- 通知オブジェクトを使う
- データモデルを生成するためのオブジェクト
- 内部データを非公開にできるが、その分コードの記述量が増える。
- データモデルを生成するためのオブジェクト
- ルールによる防衛
- 集約をどう区切るか
- メジャーなのは「変更の単位」
- ある集約Aから別の集約Bへ変更を加えようとすると、影響がリポジトリに現れる。
- A の更新処理のロジックに、Bの更新処理がを同時に書く必要が出てくる。
- A のリポジトリに追加されたBのロジックは、Bのリポジトリにも登場し、コードの重複が発生する。
- 集約に対する変更はあくまでその集約自身に実施させ、永続化の依頼も集約ごとに行われる必要がある。
- そのためリポジトリは変更の単位である集約ごとに用意する。
- そもそも別集約のインスタンスをもたないという選択もある
- そうすれば、メソッドの呼び出しようがないため。
- その代わりに識別子を持たせることで、そのインスタンスを保持しているように見せかける。
- インスタンスを持たないため、メモリも節約される。
- 識別子を持たせる場合は、ゲッターを持たせてもよい。
- 理想でいうとゲッターは削除すべき。
- 識別子はエンティティを表現するためのシステマチックな属性のため、ビジネスルールが記述されることは少ない。
- そのため、公開することでデメリットよりもメリットのほうが大きいケースがある。
- 集約の起きさはなるべく小さく保つべき。
- 巨大な集約が出来上がったら、一度境界を見つめ直す。
- 複数の集約を同一トランザクションで操作することも可能な限り避ける。
- 避けられない場合は、結果整合性というアプローチを取ることもできる。
- 言葉との齟齬をなるべく消す。
- e.g.) 30 人で満席という条件を記述する時に、
>=29
ではなく>=30
とできるようにメソッド追加を工夫し誤解を招かないようにする。
- e.g.) 30 人で満席という条件を記述する時に、
Chapter12 ドメインのルールを守る「集約」 感想
ドメイン設計をするにあたって重要な章だった。
集約についての説明や、基準、対応アプローチが書かれていて勉強になった。
この集約が正しくできるかが、DDD の鍵になると思うので、この章は繰り返し読み直したいと思う。
Chapter13 複雑な条件を表現する「仕様」
- 仕様はオブジェクトの評価を行うオブジェクト
- 評価自体をオブジェクトに切り出すことでオブジェクト本来の見通しが良くなる。
- 複雑な評価手順はアプリケーションサービスに書かれがち。
- しかし、オブジェクトの評価はドメインの重要なルールなので、サービスには書くのは問題。
- サービスにドメインルールに基づくロジックを記述することは避ける。
- エンティティや値オブジェクトがドメインモデルの表現に専念するために、リポジトリを操作することは可能な限り避けたい。
- エンティティや値オブジェクトにリポジトリを操作させないために取られる手段として、仕様オブジェクトがある。
- e.g.) XxxSpecification
- 仕様はれっきとしたドメインオブジェクトである。
- 仕様オブジェクトの内部で、リポジトリを使用したくないという考え方もある。
- その場合は、ファーストクラスコレクションを用いる。
- ファーストクラスコレクション:List といった汎用的な集合オブジェクトを利用するのではなく、特化した集合オブジェクトを用意するパターン
- ファーストクラスコレクションを利用する場合、アプリケーションサービスでファーストクラスコレクションへのデータ詰め替え処理が必要となる。
- 仕様は単独で取り扱う以外に、リポジトリに渡して仕様に合致するオブジェクトを検索する活用もある。
- そうすることで、重要なルールがリポジトリの実装クラスに漏れ出すことを防げる。
- リポジトリは強力なパターンゆえ、ドメインの重要なルールをインフラストラクチャの領域に染み出すことを助長してしまう。
- ドメインの重要な知識はできる限りドメインオブジェクトとして表現すべき。
- リポジトリに仕様を引き渡して、メソッドを呼び出せることにより、対象となるオブジェクトを抽出する方法もある。
- その場合は、仕様のインタフェースと実装クラスを用意し、リポジトリに実装クラスを渡してあげる。
- そうすることで、仕様ごとにリポジトリに追加定義をする必要がなくなる。
- その場合は、仕様のインタフェースと実装クラスを用意し、リポジトリに実装クラスを渡してあげる。
- 仕様をリポジトリのフィルターとして扱うときは、パフォーマンスのことを常に考慮しておく必要がある。
- 特殊な条件下の検索をしたいときは、仕様やリポジトリを使わないといったことも視野に入れる。
- アプリケーションの役目はあくまで、ユーザーの問題を解決すること。ユーザーフレンドリーであるべき。
- ドメインの防衛を理由に不便なアプリケーションにするのは正しいとは言えない。
- アプリケーションの役目はあくまで、ユーザーの問題を解決すること。ユーザーフレンドリーであるべき。
- 読み取り(クエリ)で要求されるデータは複雑になりがちだが、動作自体は単純でドメインのロジックと呼べるものはほとんどない。
- クエリにおいてはある程度の緩和をする事がある。
- 書き込み(コマンド)はドメインとしての制約が多く存在する。
- コマンドにおいてはドメインを隔離するためにドメインオブジェクトやそれに関わるものを積極的に利用する。
- こういった考えを CQS, CQRS という。
- コマンドとクエリに大別し、プレゼンテーションが要求するパフォーマンスを維持しながら、システムを統制することに寄与する考え方。
Chapter13 複雑な条件を表現する「仕様」 感想
この章は初めての考え方であった。今まで担当したアプリケーションでは仕様オブジェクトという考え方を取り入れたものはほとんどなかったと思うので、取り入れる余地は多そう。
今後リファクタリングを検討し、よりよい構成にしてみたいと思った。
Chapter14 アーキテクチャ
- ドメイン駆動設計にとってアーキテクチャは主役ではない
- アンチパターン:利口なUI
- 画面にビジネスロジックを持たせる。
- 同じロジックを持つ画面が増えた時に、コードが点在してしまう。
- ドメイン駆動設計がアーキテクチャに求めること
- アーキテクチャによって何がどこに記述されるべきかといった疑問に対する回答を明確にし、ロジックが無秩序に点在することを防ぐ。
- 画面にビジネスロジックを持たせる。
レイヤードアーキテクチャ
- いくつかの層が積み重なる形で表現されるアーキテクチャ
- プレゼンテーション層(ユーザーインタフェース層)
- ユーザーインタフェースとアプリケーションを結びつける。主な責務は表示と解釈。
- アプリケーション層
- ドメイン層の住人を取りまとめる層。アプリケーションサービスが挙げられる。
- ドメインオブジェクトの直接のクライアントとなり、ユースケースを実現するための進行役になる。
- ビジネスの重要なルールやふるまいを記述してはいけない。
- ドメイン層
- 一番重要な層。ソフトウェアを適用しようとしている領域で問題解決に必要な知識を表現する。
- ドメインモデルに表現するコードは全てこの層にある。
- ドメインオブジェクトをサポートする役割のあるファクトリやリポジトリのインターフェースもこの層に含まれる。
- インフラストラクチャ層
- 他の層を支える技術的基盤へのアクセスを提供する層
- プレゼンテーション層(ユーザーインタフェース層)
- 上位のレイヤーは自身より下位のレイヤーに依存することが許される。
ヘキサゴナルアーキテクチャ
- アプリケーションとそれ以外のインターフェースや保存媒体は付け外しできるようにするというコンセプト。
- ゲーム機が良い例え。
- e.g.) 付け外し可能なアダプタ
- コントローラ
- モニター
- 記憶装置
- e.g.) 付け外し可能なアダプタ
- ヘキサゴナルアーキテクチャはアダプタがポートの形状と合えば動作することに見立てて、ポートアンドアダプタと呼ばれることもある。
- e.g.)
- プライマリアダプタ
- アプリケーションサービスのメソッドを呼び出すコントローラ
- プライマリポート
- アプリケーションサービスのメソッド
- セカンダリポート
- リポジトリのインタフェース
- セカンダリアダプタ
- リポジトリの実装
- プライマリアダプタ
- e.g.)
- レイヤードアーキテクチャとの違い
- インタフェースを利用した依存関係の整理に言及している点。
クリーンアーキテクチャ
- ビジネスルールをカプセル化したモジュールを中心に捉えるというコンセプト。
- 登場する Entities はドメイン駆動設計のエンティティを示さない。ドメインオブジェクトに近い概念。
- ヘキサゴナルアーキテクチャとの違いは、その実装の仕方が詳しく言及されているか否か。
Chapter14 アーキテクチャ 感想
アーキテクチャはドメイン駆動設計に集中するための手段であることがわかった。本当に大切なことはソフトウェアで解決したいことを実現すること。手段が目的にならないように心がけたい。
Chapter15 ドメイン駆動設計のとびらを開こう
- 軽量DDD:ドメイン駆動設計に登場するパターンだけを取り入れる手法。
- 軽量DDDはあくまで実装のパターンを指す。真にドメイン駆動設計を行うにはドメインに向き合うことが必要不可欠
- ドメインエキスパートとモデリングをする
- ドメインエキスパート:ドメインの実践者
- ドメインよりも技術的なアプローチに向き合うのは誤り。
- ドメインエキスパートと協力して有益なドメインをモデリングする必要がある。
- ドメインとコードはモデルを通じてつなげることができる。互いに行き来することでより良いドメインモデルを得られる。
- ユビキタス言語:プロジェクトにおける共通言語
- ドメインエキスパートも開発者も理解できるユビキタス言語を使うことを定義することで、開発者はドメインに対する理解を深め、ドメインエキスパートは開発者が欲する知識がどういったものかを養っていけ
- ユビキタス言語を利用することで、設計とコードを結びつけることが容易になる。
- コードがドメインモデルをそのまま表現できていれば、ドメインの変化はそのままコードへ適用することができるため
- 境界づけられたコンテキスト:ドメインの国境のようなもの。ドメインにも境が存在し、その境を越えるとユビキタス言語は変わることがある。
- それは必ずしも定義が揺れていることを指すわけではない。
- こうしたときは無理に同じオブジェクトでドメインを表現するのではなく、同じ名前の別のオブジェクトを定義して良い。
- コンテキストの境界はパッケージを分割することで表現できる。
- コンテキストマップ:コンテキスト同士の関係を定義し、ドメイン全体を俯瞰できるようなもの。
- これによって同名のオブジェクトがどのコンテキストに属するのかを判断できる。
- 別のコンテキストのドメインを扱う時に、注意していても変更箇所が干渉してしまうことがある。
- 意図しない変更を防ぐためにしっかりとテストを準備しておく。
- ドメイン駆動設計はボトムアップのアプローチである。
Chapter15 ドメイン駆動設計のとびらを開こう 感想
開発をしているとどうしてもシステムのあり方やコードの綺麗さに目が行きがちだが、いちばん大切なことは「ソフトウェアによって解決したいものは何か」、つまりドメインを明確にすることであると強く感じた。
この気持ちはシステムに携わる以上常に持ち続けたいと思う。