ドメイン駆動設計(DDD)

英語名 Domain-Driven Design
読み方 ドメイン ドリブン デザイン
難易度
所要時間 習得に数ヶ月、継続的に改善
提唱者 エリック・エヴァンス
目次

ひとことで言うと
#

ビジネスの専門家とエンジニアが共通の言葉(ユビキタス言語)を使い、ビジネスドメインの知識をそのままコードに落とし込む設計手法。技術ではなくビジネスがソフトウェア設計の中心。

押さえておきたい用語
#

押さえておきたい用語
ユビキタス言語(Ubiquitous Language)
ビジネス部門とエンジニアが共通して使う用語体系のこと。コード内のクラス名・メソッド名にもこの言語を反映し、「翻訳コスト」をゼロにすることを目指す。
境界づけられたコンテキスト(Bounded Context)
同じ用語でも文脈によって意味が異なることを認め、モデルが有効な範囲を明確に区切った領域のこと。マイクロサービスの境界設計に直結する概念。
集約(Aggregate)
データの整合性を保つ単位としてエンティティをグループ化したもの。集約ルートを通じてのみ内部のデータを変更でき、トランザクション境界となる。
値オブジェクト(Value Object)
IDではなく値そのもので比較されるオブジェクトのこと。金額、住所、メールアドレスなど。不変(イミュータブル)であることが特徴。
イベントストーミング(Event Storming)
ドメインイベント(起きた出来事)を付箋で洗い出し、ビジネスプロセスを可視化するワークショップ手法のこと。DDDの戦略的設計の入口として使われる。

DDDの全体像
#

DDD:戦略的設計と戦術的設計の二層構造
戦略的設計(どう分けるか)← より重要ユビキタス言語ビジネスとエンジニアの共通用語コードの命名にも反映する境界づけられたコンテキストモデルが有効な範囲を区切る→ マイクロサービスの境界に直結コンテキストマップ: コンテキスト間の関係を可視化イベントストーミングでドメインイベントを洗い出す戦術的設計(どう作るか)エンティティ: IDで識別値オブジェクト: 値で比較集約: 整合性の単位リポジトリ: 永続化の抽象化ドメインサービス: 横断ロジック集約ルート経由でのみデータを変更DDDの本質技術ではなくビジネスが中心ドメインエキスパートとの継続的な対話が不可欠コードがドメインエキスパートにも読めるレベルを目指す
DDD導入の進め方
1
ユビキタス言語
ビジネスとエンジニアで共通用語を確立
2
ドメインモデル
エンティティ・値オブジェクト・集約を設計
3
コンテキスト分割
境界づけられたコンテキストを定義
戦略×戦術統合
イベントストーミングで全体を俯瞰

こんな悩みに効く
#

  • ビジネス部門の言葉とコードの変数名が全然違い、翻訳コストが大きい
  • ビジネスロジックが複雑で、どこに何が書いてあるかわからない
  • マイクロサービスに分割したいが、どこで切ればいいかわからない

基本の使い方
#

ステップ1: ユビキタス言語を確立する

ビジネス部門とエンジニアが同じ用語を使う

  • ドメインエキスパート(業務の専門家)と対話し、重要な概念を洗い出す
  • 用語集を作り、コード内のクラス名・メソッド名に反映する
  • 「ユーザー」ではなく「顧客」「配送先」「注文者」のようにドメインの文脈で命名する

ポイント: コードがドメインエキスパートにも読めるレベルを目指す。

ステップ2: ドメインモデルを設計する

エンティティ、値オブジェクト、集約のパターンでモデルを構築する

  • エンティティ: 一意のIDで識別されるオブジェクト(例:注文、顧客)
  • 値オブジェクト: 値そのもので比較されるオブジェクト(例:金額、住所)
  • 集約: 整合性を保つ単位でエンティティをグループ化する
  • ドメインサービス: 特定のエンティティに属さないビジネスロジック

ポイント: 集約のルートを通じてのみデータを変更する。これが整合性の要。

ステップ3: 境界づけられたコンテキストを定義する

同じ用語でも文脈によって意味が異なることを認め、コンテキストを分ける

  • 「顧客」は販売部門では「購入者」、サポート部門では「問い合わせ者」
  • 各コンテキストは独自のモデルを持つ
  • コンテキスト間の関係を「コンテキストマップ」で可視化する

ポイント: これがマイクロサービスの境界設計に直結する。

ステップ4: 戦略的設計と戦術的設計を組み合わせる

大局的な設計と詳細な設計の両方を進める

  • 戦略的設計: コンテキストの分割、チーム間の連携パターン
  • 戦術的設計: エンティティ、リポジトリ、ファクトリーの実装
  • イベントストーミングでドメインイベントを洗い出す

ポイント: 戦略的設計(どう分けるか)の方が戦術的設計(どう作るか)より重要。

具体例
#

例1:保険システムでユビキタス言語とコンテキスト分割を適用する

ユビキタス言語の確立: ビジネス側と対話し、「契約」「保険料」「給付金」「免責期間」「保険事故」などの用語を定義。コード内でもContractPremiumBenefitのように命名。

ドメインモデル: Contract(エンティティ、集約ルート)がPremium(値オブジェクト)を持つ。Contract.calculatePremium()でビジネスルールを表現。保険料の計算ロジックがDBアクセスやUI表示と混在しない。

境界づけられたコンテキスト:

  • 契約コンテキスト: 契約の締結・変更・解約を管理
  • 請求コンテキスト: 保険事故の申請と給付金の算出
  • 顧客コンテキスト: 顧客情報の管理

結果: 「契約」は契約コンテキストでは詳細な保険条件を持つが、請求コンテキストでは契約番号と基本条件のみ参照。同じ「契約」でもコンテキストごとにモデルが異なることで、各チームが独立して開発可能になった。

例2:ECモノリスをイベントストーミングでマイクロサービスに分割する

状況: 5年間運用したECモノリス(コード30万行)。チーム15人で開発しているが、変更の衝突が頻発し、デプロイ頻度が月1回に低下。

イベントストーミング実施(2日間): ビジネス側8名+エンジニア10名で、ドメインイベントを付箋200枚以上で洗い出し。

発見したコンテキスト:

  • 注文(OrderPlaced, OrderShipped)
  • 在庫(InventoryReserved, StockReplenished)
  • 決済(PaymentReceived, RefundIssued)
  • 顧客(CustomerRegistered, AddressUpdated)

結果: 4つの境界づけられたコンテキスト→4つのマイクロサービスに分割。各チーム3〜4名で独立開発が可能になり、デプロイ頻度が月1回→週2〜3回に改善。

例3:値オブジェクトの導入でバグを構造的に排除する

Before: 金額をすべてint型で扱い、「円」と「ドル」の混同バグが年5件発生。calculateTotal(int price, int quantity, int taxRate) のような関数で引数の順序を間違えるバグも頻発。

値オブジェクト導入:

  • Moneyクラス: 金額と通貨を持つ。Money(1500, Currency.JPY)
  • TaxRateクラス: 税率を表現。TaxRate.of(10)で10%
  • Quantityクラス: 数量を表現。負の数を受け付けない

After: calculateTotal(Money price, Quantity quantity, TaxRate taxRate)引数の取り違えがコンパイル時に検出される。通貨混同バグは年5件→0件。税率計算の精度バグも構造的にゼロに。

やりがちな失敗パターン
#

  1. 戦術的パターンだけ真似する — エンティティや値オブジェクトのクラスだけ作って満足するが、ユビキタス言語もコンテキスト分割もしない。DDDの本質は戦略的設計にある
  2. ドメインエキスパートと対話しない — エンジニアだけで「ドメインモデルっぽいもの」を作ってしまう。ビジネスの専門家との継続的な対話が必須
  3. すべてのプロジェクトにDDDを適用する — CRUDが中心のシンプルなアプリにDDDはオーバーキル。複雑なビジネスロジックがある領域にだけ適用する
  4. 集約を大きくしすぎる — 1つの集約に多くのエンティティを詰め込み、パフォーマンスやロックの問題を引き起こす。集約はできるだけ小さく設計する

まとめ
#

DDDは 「ビジネスの複雑さにソフトウェアで立ち向かう」 ための設計手法。ユビキタス言語と境界づけられたコンテキストが最も重要な概念。学習コストは高いが、複雑なビジネスドメインを扱うシステムでは最も効果を発揮する。まずは自分のプロジェクトで 「ビジネスの言葉でコードを書く」 ことから始めよう。