複雑さに立ち向かう!DDD(ドメイン駆動設計)入門
1 はじめに
一言でいうと
「業務(ドメイン)の知識をコードに直接反映させ、複雑なビジネスロジックを整理・解決するための設計手法」 です。
なぜ今、重要なのか
現代のソフトウェア開発、特にエンタープライズ領域やマイクロサービスアーキテクチャにおいては、システムの技術的な難易度よりも「ビジネスルールの複雑さ」がボトルネックになることが多くなりました。
従来のデータ中心(データベース設計ありき)のアプローチでは、ビジネス要件が変わるたびにコードの至る所を修正する「スパゲッティコード」になりがちです。DDDは、「ビジネスの言葉」と「プログラムの言葉」を統一することで、仕様変更に強く、開発者とビジネス専門家が協力しやすいシステム構築を可能にします。
2 ビギナー向け・ドキュメント
概要:翻訳機をなくそう
プログラマと、業務の専門家(ドメインエキスパート)が話すとき、こんなことはありませんか?
- 専門家: 「顧客が退会したら、30日後にポイントを失効させて…」
- プログラマ: 「えーと、つまり
Userテーブルのstatusフラグを 0 にして、Pointテーブルのdeleted_atに日付を入れるSQLを書くってことですね?」
この「翻訳」こそがバグや認識祖語の温床です。DDDでは、コードの中でも「退会する」「失効させる」という言葉(メソッド)をそのまま使います。 「業務の言葉(ユビキタス言語)」でコードを書く、これがDDDの第一歩です。
公式情報・推奨リソース
DDDには絶対的な「公式マニュアル」はありませんが、原典とされる書籍やコミュニティが情報源となります。
- Domain Language (Eric Evans氏のサイト): https://www.domainlanguage.com/
- Microsoft .NET Architecture Guides (DDDの章): https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/
- 書籍: 『エリック・エヴァンスのドメイン駆動設計』(通称:青本)、『実践ドメイン駆動設計』(通称:赤本)
導入:ドメインモデルのイメージ
「貧血症なドメインモデル(アンチパターン)」と「リッチなドメインモデル(DDD)」の違いを見てみましょう。
× 貧血症なモデル(データとロジックが分離している) データを入れるだけの箱(Entity)と、それを操作するServiceが別々になっています。
// データ構造のみ
class User {
public email: string;
}
// ロジックが外部にある
class UserService {
changeEmail(user: User, newEmail: string) {
if (!newEmail.includes('@')) {
throw new Error("無効なメールです");
}
user.email = newEmail;
}
}
○ リッチなドメインモデル(DDD的アプローチ) データとロジック(ルール)が一体化しています。不正な状態になることを自身で防ぎます。
class User {
private _email: string;
constructor(email: string) {
this.setEmail(email);
}
// 「メールアドレスを変更する」という業務の意図をメソッドにする
public changeEmail(newEmail: string): void {
this.setEmail(newEmail);
}
private setEmail(email: string): void {
if (!email.includes('@')) {
throw new Error("無効なメールアドレスです");
}
this._email = email;
}
}
// 使う側はロジックを知らなくても安全に操作できる
// user.changeEmail('new@example.com');
3 会話集
エンジニアの現場や学習の場でよくあるQ&Aです。
Q1. DDDって結局、何から始めればいいの?
Aさん: 「用語集を作ることから? それともディレクトリ構成を変える?」 Bさん: 「まずは『ユビキタス言語』の定義からですね。エンジニアと企画担当者が同じ言葉を使って会話できているか? コード上の変数名と、会議で使う言葉が一致しているか? そこを合わせるのがスタートラインです。」
Q2. どんなプロジェクトでもDDDをやるべき?
Aさん: 「簡単なCRUDアプリを作るんですが、RepositoryとかEntityとか全部用意する必要がありますか?」 Bさん: 「いいえ、それはオーバーエンジニアリングになりかねません。DDDは『複雑なビジネスルール』がある場合に真価を発揮します。単なる掲示板やマスタ管理画面なら、フレームワーク標準のMVCだけで十分ですよ。」
Q3. 「ドメインサービス」と「アプリケーションサービス」の違いは?
Aさん: 「どっちもServiceクラスですよね? 何が違うんですか?」 Bさん: 「ドメインサービスは『ドメインモデル単体では表現しきれない業務ルール(例:重複チェック)』を扱います。一方、アプリケーションサービスは『ユースケースの進行役(例:DBから取得して、メソッドを呼んで、保存する)』です。アプリケーションサービスには業務ロジックを書かないのが鉄則です。」
4 より深く理解する為に
DDDは大きく「戦略的設計」と「戦術的設計」の2つに分類されます。
戦略的設計 (Strategic Design)
システム全体をどう分割し、チームをどう編成するかという「マクロな視点」です。
- 境界づけられたコンテキスト (Bounded Context):
- 同じ「商品」という言葉でも、「配送システム」では重量やサイズが重要で、「販売システム」では価格や説明文が重要です。これらを無理に一つのクラスにまとめず、コンテキスト(文脈)ごとにモデルを分けるという考え方です。
- これにより、マイクロサービス化の分割単位としても機能します。
戦術的設計 (Tactical Design)
コードレベルでの実装パターンです。
- エンティティ (Entity): IDによって同一性が識別されるオブジェクト(例:ユーザー、注文)。属性が変わってもIDが同じなら同じ物とみなします。
- 値オブジェクト (Value Object): 値そのものが意味を持つオブジェクト(例:住所、金額)。中身が変われば別の物とみなします(イミュータブルにするのが基本)。
- 集約 (Aggregate): 整合性を保つべきオブジェクトのまとまり。データの変更は必ず「集約ルート(Aggregate Root)」を通して行います。
- リポジトリ (Repository): データの永続化(保存・取得)を抽象化したインターフェース。DBの実装詳細をドメイン層から隠蔽します。
メリットとデメリット
- メリット: 業務ロジックが散らばらず、仕様変更時の影響範囲が特定しやすい。テストが書きやすい。
- デメリット: 初期学習コストが高い。クラス数が増える。単純なアプリには不向き。
5 関連ワード
この記事を理解する上で重要なキーワードです。
- ユビキタス言語 (Ubiquitous Language)
- 開発者とビジネス専門家の間で共通して使用される言語(語彙)。コード、ドキュメント、会話のすべてで統一して使います。
- イベントストーミング (Event Storming)
- 付箋を使って時系列に「ドメインイベント(業務上で起きた事実)」を並べ、ドメインの全体像やコンテキストの境界を探るワークショップ手法。
- オニオンアーキテクチャ / クリーンアーキテクチャ
- DDDと相性の良いアーキテクチャパターン。ドメイン層をシステムの中心(依存の最下層)に置き、UIやDBなどの詳細を外側に配置します。
- CQS / CQRS
- コマンド(更新)とクエリ(参照)を分離する設計。DDDでは参照系で複雑なドメインモデルを経由させると遅くなるため、CQRSを用いて参照系を最適化することがよくあります。
- 不変条件 (Invariant)
- ドメインオブジェクトが常に満たしていなければならない整合性のルール(例:注文明細は必ず1つ以上存在する)。
6 要点チェック
最後に、DDDの重要なポイントを振り返ります。
- 言葉を統一せよ: 開発者とビジネス側が同じ「ユビキタス言語」を使うことが全ての基本。
- ドメイン中心: データベースの都合ではなく、ビジネスのルール(ドメイン)を中心に設計する。
- コンテキストを分ける: 一つの巨大なモデルを作ろうとせず、文脈ごとに境界(Bounded Context)を引いてモデルを分離する。
- ロジックを凝集: データの入れ物とロジックを分離せず、オブジェクト自身に振る舞いを持たせる。