ドメイン駆動設計 モデリング/実装ガイドを読んで
参考リンク
第1章 DDD概要
DDDとは
モデリングによってソフトウェアの価値を高めることを目指した開発手法
モデル(ドメインモデル)とは
ある特定の問題領域における物事の特定の側面を抽象化したもの
※問題解決に必要ない側面はモデル化しても意味がないということ
良いモデル
問題解決ができるモデル
- 問題を正しく理解するためにドメインエキスパートとの会話を大事に
- 運用過程で得られたドメイン知識をモデルに還元するサイクルを早く回す。改善されたモデルをソフトウェアの実装に反映するサイクルを早く回す。
DDDに取り組む姿勢
- 課題ドリブン
- 決められたルールに従うのではなく、自分達が解決したい課題を前提に目的を言語化して納得感を持って、みんなで最適解を導いていくようにしよう
- 小さく初めて、早期失敗、改善サイクルをたくさん回していこう
- いきなり完璧なモデルは作れないことを前提に
第2章 DDD概要 モデリングから実装まで
ドメインモデル貧血症
ドメイン知識(制約/ルール etc..)を持たないドメインモデルの状態
(モデリングで浮き彫りになったドメイン知識をドメインモデルへ寄せて実装をしていくことでなくしていきましょう=>高凝集)
ドメイン層の設計基本方針
- ドメインモデルの知識を対応するドメインクラスに表現する
- 常に正しい状態のインスタンスしか生成させない(常にデータの整合性を担保できる)
- 完全コンストラクタ
- ファクトリパターン
- 外部ミューテーションの禁止(正しいミューテーションのみを外部公開)
第3章 DDD固有のモデリング手法
集約
集約とは
強い整合性を持ったオブジェクトのまとまり
- オブジェクトとオブジェクトが整合性の担保のために強く結びついている
- トランザクションが同一であるべき
集約境界
- 大きすぎる集約→大きすぎるトランザクション
- 集約同士の整合性→実装の複雑度が上がる
コストを理解して、保ちたい整合性の強さとのトレードオフで都度判断
境界づけられたコンテキスト
境界づけられたコンテキストとは
あるモデルが適用される範囲を明示的に定義するための境界
(コンテキスト=文脈 ※文脈が異なると同じ言葉でも意味合いが異なる)
第4章 設計の基本原則
モジュール単位で高凝集・低結合
※モジュール 機能をまとめた単位(ex. クラス,レイヤー,アプリケーション, etc... 様々に粒度がある )
- 理解しやすくなる
- 拡張しやすくなる(バグを埋め込みにくくなる)
- 再利用性しやすくなる
- テストしやすくなる
凝集度
あるモジュールの責務=>データ、振る舞いの関連性の強さ 高いほどOK
※高いほど、ある変更の影響範囲を一つのモジュール内で閉じ込められる
結合度
あるモジュールが別のモジュールと依存し合っている程度 低いほどOK
※低いほどモジュールの変更が別のモジュールに影響しづらい
第5章 アーキテクチャー
- レイヤードアーキテクチャー
- オニオンアーキテクチャー
- ヘキサゴナルアーキテクチャー(ポート&アダプター)
- クリーンアーキテクチャー
第6章 ドメイン層の実装
ドメイン層を隔離する目的
価値あるシステムを作るために、柔軟にビジネス要件の変更への対応を行えるように、それ以外とは隔離する。そうすることで妥協的な設計の積み重ねを避けていける
DDD戦術
- エンティティ
識別子によって同一判定されるモデル(※属性は概念的には可変) - 値オブジェクト
属性値によって同一判定されるモデル(※属性は不変) - ドメインサービス
複数のドメインに対する操作等、エンティティや値オブジェクトでの表現に無理があるもの - リポジトリ
集約単位で永続化層や外界へのアクセス手段を提供 - ファクトリー
複雑なドメイン生成のための責務を追う。
ドメイン構築のためのドメイン知識を持つためドメイン層にいるべき - ドメインイベント
エンティ同士の関連
- 集約内はインスタンス参照
- 集約外はID参照
蛇足
- Repositoryがなぜドメイン層なのか?
- 集約単位が他の何かに依存して変化してしまわないように
- Entity自体がRepositoryを持って永続化をコールしちゃダメなのか?
- ドメイン操作に加えて永続化という多重責務状態になるため非推奨
第7章 ユースケース(アプリケーション)層の実装
ユースケース層の責務
ドメイン層/アプリケーション層の公開メソッドを組み立てユースケースを実現する(何をするかには言及するが具体的にどうやるか?は可能な限り触れないのが望ましい)
第8章 CQRS
CORS
Repositoryパターンの限界
参照形処理において複数集約を扱う必要性が生じる
- 複数集約へのアクセスがそれぞれ必要になり実装複雑度が上がる(条件構築等)
- 集約単位での情報取得でパフォーマンスが悪い
CORSパターン
更新形と参照形とでモデルを分離する(※軽量CORS 実際のCORSはスキーマさえも切り離すケースがある)
ユースケース単位で専用のDTO
とQueryService
を用意する
デメリット
- データ参照箇所が更新形と参照形で分かれている可能性を常に考慮し続けなければならない
イベントソーシング
ステートソーシングとの対比
状態を保存するのではなく、起きたイベントを保存してイベントを全てリプレイすることで最新の状態を導けるような形式
第9章 プレゼンテーション層の実装
プレゼンテーション層の責務
- クライアントからのリクエスト定義
- クライアントへのレスポンス定義
- WEBアプリケーションフレームワーク関連の処理
- アプリケーション層の適切なユースケースへの処理の移譲
- エラーハンドリング
第10章 アーキテクチャー全般・ライブラリなど
例外処理
- バリデーション
ドメイン層では原則実装=>最も重要な制約(整合性あるデータ状態をアプリケーション処理を通して担保するため)
その上で、他の層でバリデーションロジックの重複を許すかどうかはトレードオフ- フロント=>リアルタイムバリデーションによるUX向上
- プレゼンテーション層=>APIとしてのI/F定義
パッケージ構成例
root
|--presentation
|--domainA(agregateA)
|--domainB(agregateB)
|--application
|--domain
|--infrastructure
Webフレームワークへの依存
なるべくプレゼンテーション層に寄せる、その上でアプリケーション層、ドメイン層としてDI周りは許容範囲
O/Rマッパーについて
O/Rマッパーのためのモデルをそのままドメインモデルとして扱うのは危険
- 公開setterを必要とするため、クラスとしての安定性が損なわれる
- DB構造にドメインモデルが依存する形になってしまう
RepositoryでO/Rモデル→ドメインモデルと詰め替えをすると良い