読書記録 - ドメイン駆動設計入門
サンプルコード
Chapter2 値オブジェクト
システムで必要な値が必ずしもプリミティブとは限らない、必要とされる処理に沿った値が必要
例: fullNameオブジェクト (firstNameとLastNameを引数にもつ)
2-2 値の性質
不変である
交換可能である
等価性によて比較される
値オブジェクトの属性を取り出して比較するのではなく、値オブジェクト同士を比較
属性が追加されても修正不要
2-3 値オブジェクトにする基準
2-4 ふるまいを持った値オブジェクト
例: 量と通貨単位を属性にもつ値オブジェクト
2-5 値オブジェクトを採用するモチベーション
表現力を増す
例: a20421-100-1 といった製品番号 プロダクトコード、枝番、ロットと属性を分けることで構成がわかりやすくなる
不正な値を存在させない
字数制限などを設けて不正な値を弾くことができる
誤った代入を防ぐ
例: ユーザIDとユーザ名にそれぞれ値オブジェクトを適用
ロジックの散在を防ぐ
例: ユーザ名の作成処理と更新処理
ユーザ名を値オブジェクト化することで、ユーザ名の検証(字数制限など)が1箇所にまとまる
Chapter3 エンティティ
3-1 エンティティとは
例: システムのユーザ
ユーザ情報が変更されても、ユーザ自体は変更されない
属性ではなく同一性で識別される
3-2 エンティティの性質について
可変である
同じ属性であっても区別される
同姓同名でも、必ずしも同一人物ではない
エンティティを区別するための識別子を利用(ユーザであればユーザID)
同一性を持つ
属性が変わっても同一と識別したい
同一性の判断のため識別子を利用
3-3 エンティティの判断基準としてのライフサイクルと連続性
ライフサイクルが存在し、そこに連続性が存在するかどうか
可変なオブジェクトは取り扱いに慎重さが求められ厄介、可能な限り不変なオブジェクトにした方がいい
3-4 値オブジェクトとエンティティどちらにもなりうるモデル
3-5 ドメインオブジェクトを定義するメリット
コードのドキュメント性が高まる
字数制限などのルールがコードに反映されている
ドメインにおける変更をコードに伝えやすくする
Chapter4 ドメインサービス
4-2 ドメインサービスとは
値オブジェクトやエンティティに記述すると不自然なふるまいを記述
例: ユーザ名の重複チェック
重複の有無をUserクラス自身に問い合わせることになり不自然
自身の振る舞いを変更するようなインスタンス特有の状態を持たないオブジェクト
4-3 ドメインサービスの濫用が行き着く先
ドメインサービスへの記述は「不自然なふるまい」に限定する
実はなんでもドメインサービスへ記述できる
ドメインオブジェクトの役割が見えづらくなる
可能な限りドメインサービスは避けた方が良い
4-4 エンティティや値オブジェクトと共にユースケースを組み立てる
4-5 物流システムに見るドメインサービスの例
物流拠点...出庫と入庫の振る舞いがある
輸送処理が物流拠点の中で行われるとぎこちなさを感じる
→輸送ドメインサービスを定義
Chapter5 リポジトリ
データの永続化(保存)、再構築(復元)処理を抽象的に扱うためのオブジェクト
データストアの処理(DB接続)を他の処理から独立させる
テスト用リポジトリを作れば、テストのためにDBやテーブルを用意する手間が省ける
5-8 リポジトリに定義されるふるまい
永続化
オブジェクトの破棄に関する操作も含まれる
オブジェクトの作成・更新処理は定義しない
再構築
最も利用されるのは識別子によって検索されるメソッド
Chapter6 アプリケーションサービス
ユースケースを実現するオブジェクト
例: ユーザの登録・更新・退会処理
6-2 ユースケースを組み立てる
DTO(Data Transfer Object)
ドメインオブジェクトを非公開としたとき、クライアントでデータ転送用オブジェクトを利用
コマンドオブジェクト
例: 引数にデータを渡すか渡さないかで挙動を制御したい時
→ユーザ情報が追加されるたびにアプリケーションサービスのメソッドのシグネチャが変更されることになる
→コマンドオブジェクトを作り間接的にアプリケーションサービスの処理を制御
6-3 ドメインのルールの流出
例: ユーザ作成処理とユーザ更新処理の両方に重複チェック
→ドメインのルールはドメインオブジェクトに記述
6-4 凝集度
モジュールの責任範囲がどれだけ集中しているか図る尺度
Chapter7 柔軟性をもたらす依存関係のコントロール
7-3 依存関係逆転の原則(Dependency Inversion Principle)
上位レベルのモジュールは下位レベルのモジュールに依存してはならない、どちらのモジュールも抽象に依存するべきである
抽象は、実装の詳細に依存してはならない。実装の詳細が抽象に依存すべきである
7-4 依存関係をコントロールする
Service Locatorパターン
ServiceLocatorと呼ばれるオブジェクトに依存解決先となるオブジェクトを事前に登録しておき、インスタンスが必要となる箇所でServiceLocatorを経由してインスタンス取得
インスタンス化を行うコードが点在しなくなる
ServiceLocatorに登録されるインスタンスの設定をプロダクション用とテスト用など用途に応じて一括管理すると便利
一方でアンチパターンとも言われる
依存関係が外部から見えづらくなる
テストの維持が難しくなる
IoC Containerパターン
Chapter8 ソフトウェアシステムを組み立てる
8-2 コマンドラインインタフェースに組み込んでみよう
8-3 MVCフレームワークに組み込んでみよう
Startupクラスで依存関係を設定(リポジトリなどの依存解決の設定を行う)
開発用と本番用切り替えの設定を構成ファイルに記述
コントローラを実装
フロントからのデータをビジネスロジックが必要とする入力データに変換する作業に集中
8-4 ユニットテストを書こう
テスト用のリポジトリを用意
ユニットテストがロジックのあるべき姿を示す手がかりとなる
Chapter9 複雑な生成処理を行う「ファクトリ」
複雑なオブジェクトの生成をオブジェクトとして定義
9-2 採番処理をファクトリに実装した例の確認
9-3 ファクトリとして機能するメソッド
メソッドがファクトリとして機能することもある
9-4 複雑な生成処理をカプセル化しよう
単純に生成方法が複雑なインスタンスを構築する処理をまとめるため、ファクトリを利用するのもよい習慣
「コンストラクタ内で他のオブジェクトを生成するかどうか」はファクトリを作る際のよい指標となる
Chapter10 データの整合性を保つ
10-2 致命的な不具合を確認する
例: ユーザ登録時の重複チェック
タイミングによっては重複チェックがOKとなり複数ユーザが登録されてしまう
10-3 ユニークキー制約による防御
データベースの特定からむが唯一無二であることを保証
ユニークキー制約をそのまま機能として使うのではなく、あくまでセーフティーネットとして活用するべき
10-4 トランザクションによる防御
データの整合性を保つために利用される一般的な手段
処理内容の反映はコミット処理時に行われる
トランザクションスコープを利用したパターン (C#の TransactionScope)
AOP(Aspect Oriented Programming) を利用したパターン
C#の場合は @Transactional アノテーション
ユニットオブワークを利用したパターン
あるオブジェクトの変更を記録するオブジェクト
トランザクションのロックは可能な限り小さくするべき
一つの指針としては、一度のトランザクションで保存するオブジェクトを1つに限定
Chapter11 アプリケーションを1から組み立てる
11-1 アプリケーションを組み立てるフロー
要求に従い必要な機能を考える
機能を成り立たせるために必要なユースケースを洗い出し
ドメインオブジェクトを準備
ドメインオブジェクトを用いてユースケースを実現するアプリケーションを実装
11-3 サークルの知識やルールをオブジェクトとして準備する
値オブジェクト CircleId、CircleName を定義
エンティティ Circle を定義
サークルの永続化に必要となるリポジトリICircleRepositoryを定義
サークルの生成に必要なファクトリ ICircleFactory
ユーザ名重複確認用 CircleService
11-4 ユースケースを組み立てる
サークル作成のコマンドオブジェクト CircleCreateCommand、サークル参加用コマンドオブジェクト CircleJoinService
CircleApplicationService
Chapter12 ドメインのルールを守る「集約」
12-1 集約とは
code:C#
var userName = new UserName("NewName");
user.Name = userName; // NG
user.changeName(userName); // OK
デメテルの法則
外部から内部のオブジェクトに対して直接操作するのではなく、それを保持するオブジェクトに依頼
→直観的に不変条件を維持できる
12-2 集約をどう区切るか
もっとメジャーなのは「変更の単位」
サークルの変更はサークルの集約内部で納め、ユーザの変更はユーザの集約内部で納めるべき
12-3 集約の大きさと操作の単位
集約はなるべく小さく保つべき
12-4 言葉との齟齬を消す
code: C#
public bool isFull()
{
return members.Count >= 29;
}
「サークルに所属するユーザの最大数は30名まで」というルールに対し、コードには29が出てくる
コードには可能な限り言葉との齟齬をなくすべき
code: C#
public bool isFull()
{
return CountMembers() >= 30;
}
public int CountMembers()
{
return members.Count + 1;
}
Chapter13 複雑な処理を表現する「仕様」
仕様...あるオブジェクトがある評価基準に達しているかを判定するオブジェクト
13-1仕様とは
例: サークルの人数上限メソッド isFull
(要件が複雑になると)リポジトリ操作が必要
エンティティや値オブジェクトがリポジトリ操作するのは避けるべき
→仕様として切り出す
13-2 仕様とリポジトリを組み合わせる
リポジトリに仕様を引き渡し、仕様に合致するオブジェクトを検索
例: お勧めサークル検索機能
検索条件がインフラストラクチャのリポジトリのクラスに依存するのは推奨されない
→検索条件は仕様オブジェクトで定義
仕様をインタフェースにする方法もある
仕様をリポジトリのフィルタとして扱うときは、パフォーマンスの考慮が必要
複雑な読み取り動作でパフォーマンスの懸念がある場合、局所的にドメインオブジェクトから離れることもある
SQLクエリ直接実行、ORMマッパーなど
CQS (Comand-query separation)やCQRS(Command Query Responsibility Segregation)という考え方がある
Chapter14 アーキテクチャ
14-1 アーキテクチャの役目
14-1-1 アンチパターン: 利口なUI
ドメインオブジェクトに記載されるべき重要なルールや振る舞いがUIに記述された状態
何がどこに記述されるべきかを明確にし、ロジックの無秩序な点在を防ぐ
14-2 アーキテクチャの解説
レイヤードアーキテクチャ
4つの層から成り立つ
プレゼンテーション層(ユーザーインタフェース層)
アプリケーション層
ドメイン層
インフラストラクチャ層
原則は依存の方向が上から下
ヘキサゴナルアーキテクチャ
ゲーム機が良い例え
クリーンアーキテクチャ
4つの同心円によって説明されるアーキテクチャ
ユーザーインタフェースやデータストアなどの詳細を端に追いやり、依存の方向を内側に向ける
→詳細が抽象に依存する依存関係逆転の原則を達成
Chapter15 ドメイン駆動設計のとびらを開こう
15-3 ユビキタス言語
認識の齟齬や翻訳にコストをかけないために、プロジェクトにおける共通言語が必要
15-4 境界付けられたコンテキスト
変化に対する摩擦を防ぐため、モデルに対する捉え方が異なる箇所でシステムを分割
分割した領域ごとに言語の統一を目指す
15-5 コンテキストマップ
コンテキスト同士の関係を定義し、ドメイン全体を俯瞰できるようにコンテキストマップを作る必要がある
15-6 ボトムアップドメイン駆動設計