ひとことで言うと#
ビジネスの専門家とエンジニアが共通の言葉(ユビキタス言語)を使い、ビジネスドメインの知識をそのままコードに落とし込む設計手法。技術ではなくビジネスがソフトウェア設計の中心。
押さえておきたい用語#
- ユビキタス言語(Ubiquitous Language)
- ビジネス部門とエンジニアが共通して使う用語体系のこと。コード内のクラス名・メソッド名にもこの言語を反映し、「翻訳コスト」をゼロにすることを目指す。
- 境界づけられたコンテキスト(Bounded Context)
- 同じ用語でも文脈によって意味が異なることを認め、モデルが有効な範囲を明確に区切った領域のこと。マイクロサービスの境界設計に直結する概念。
- 集約(Aggregate)
- データの整合性を保つ単位としてエンティティをグループ化したもの。集約ルートを通じてのみ内部のデータを変更でき、トランザクション境界となる。
- 値オブジェクト(Value Object)
- IDではなく値そのもので比較されるオブジェクトのこと。金額、住所、メールアドレスなど。不変(イミュータブル)であることが特徴。
- イベントストーミング(Event Storming)
- ドメインイベント(起きた出来事)を付箋で洗い出し、ビジネスプロセスを可視化するワークショップ手法のこと。DDDの戦略的設計の入口として使われる。
DDDの全体像#
こんな悩みに効く#
- ビジネス部門の言葉とコードの変数名が全然違い、翻訳コストが大きい
- ビジネスロジックが複雑で、どこに何が書いてあるかわからない
- マイクロサービスに分割したいが、どこで切ればいいかわからない
基本の使い方#
ビジネス部門とエンジニアが同じ用語を使う。
- ドメインエキスパート(業務の専門家)と対話し、重要な概念を洗い出す
- 用語集を作り、コード内のクラス名・メソッド名に反映する
- 「ユーザー」ではなく「顧客」「配送先」「注文者」のようにドメインの文脈で命名する
ポイント: コードがドメインエキスパートにも読めるレベルを目指す。
エンティティ、値オブジェクト、集約のパターンでモデルを構築する。
- エンティティ: 一意のIDで識別されるオブジェクト(例:注文、顧客)
- 値オブジェクト: 値そのもので比較されるオブジェクト(例:金額、住所)
- 集約: 整合性を保つ単位でエンティティをグループ化する
- ドメインサービス: 特定のエンティティに属さないビジネスロジック
ポイント: 集約のルートを通じてのみデータを変更する。これが整合性の要。
同じ用語でも文脈によって意味が異なることを認め、コンテキストを分ける。
- 「顧客」は販売部門では「購入者」、サポート部門では「問い合わせ者」
- 各コンテキストは独自のモデルを持つ
- コンテキスト間の関係を「コンテキストマップ」で可視化する
ポイント: これがマイクロサービスの境界設計に直結する。
大局的な設計と詳細な設計の両方を進める。
- 戦略的設計: コンテキストの分割、チーム間の連携パターン
- 戦術的設計: エンティティ、リポジトリ、ファクトリーの実装
- イベントストーミングでドメインイベントを洗い出す
ポイント: 戦略的設計(どう分けるか)の方が戦術的設計(どう作るか)より重要。
具体例#
ユビキタス言語の確立: ビジネス側と対話し、「契約」「保険料」「給付金」「免責期間」「保険事故」などの用語を定義。コード内でもContract、Premium、Benefitのように命名。
ドメインモデル: Contract(エンティティ、集約ルート)がPremium(値オブジェクト)を持つ。Contract.calculatePremium()でビジネスルールを表現。保険料の計算ロジックがDBアクセスやUI表示と混在しない。
境界づけられたコンテキスト:
- 契約コンテキスト: 契約の締結・変更・解約を管理
- 請求コンテキスト: 保険事故の申請と給付金の算出
- 顧客コンテキスト: 顧客情報の管理
結果: 「契約」は契約コンテキストでは詳細な保険条件を持つが、請求コンテキストでは契約番号と基本条件のみ参照。同じ「契約」でもコンテキストごとにモデルが異なることで、各チームが独立して開発可能になった。
状況: 5年間運用したECモノリス(コード30万行)。チーム15人で開発しているが、変更の衝突が頻発し、デプロイ頻度が月1回に低下。
イベントストーミング実施(2日間): ビジネス側8名+エンジニア10名で、ドメインイベントを付箋200枚以上で洗い出し。
発見したコンテキスト:
- 注文(OrderPlaced, OrderShipped)
- 在庫(InventoryReserved, StockReplenished)
- 決済(PaymentReceived, RefundIssued)
- 顧客(CustomerRegistered, AddressUpdated)
結果: 4つの境界づけられたコンテキスト→4つのマイクロサービスに分割。各チーム3〜4名で独立開発が可能になり、デプロイ頻度が月1回→週2〜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件。税率計算の精度バグも構造的にゼロに。
やりがちな失敗パターン#
- 戦術的パターンだけ真似する — エンティティや値オブジェクトのクラスだけ作って満足するが、ユビキタス言語もコンテキスト分割もしない。DDDの本質は戦略的設計にある
- ドメインエキスパートと対話しない — エンジニアだけで「ドメインモデルっぽいもの」を作ってしまう。ビジネスの専門家との継続的な対話が必須
- すべてのプロジェクトにDDDを適用する — CRUDが中心のシンプルなアプリにDDDはオーバーキル。複雑なビジネスロジックがある領域にだけ適用する
- 集約を大きくしすぎる — 1つの集約に多くのエンティティを詰め込み、パフォーマンスやロックの問題を引き起こす。集約はできるだけ小さく設計する
まとめ#
DDDは 「ビジネスの複雑さにソフトウェアで立ち向かう」 ための設計手法。ユビキタス言語と境界づけられたコンテキストが最も重要な概念。学習コストは高いが、複雑なビジネスドメインを扱うシステムでは最も効果を発揮する。まずは自分のプロジェクトで 「ビジネスの言葉でコードを書く」 ことから始めよう。