目次
- はじめに:AI がコードを書く時代と、DDD 再評価の必然性
- AI 時代に起きている変化
- AI 時代の観点で再解釈する DDD
- AI × DDD の役割分担(技術者視点)
- AI × DDD の具体的な開発ワークフロー例
- まとめ:AI 時代にこそ DDD が必要な理由
1. はじめに:AI がコードを書く時代と、DDD 再評価の必然性
1-1. AI開発の現実:光と影
2023年以降、GitHub Copilot、ChatGPT、Claude、Cursorなど、開発現場に大規模言語モデル(LLM)が急速に浸透しました。これまで数時間かかっていた実装が数分で終わり、開発生産性が数倍〜数十倍になった現場も珍しくありません。
しかし、あなたもこんな経験はないでしょうか?
- AIが生成したコードを見たら、見た目は完璧だが業務ロジックが微妙に間違っている
- 修正を依頼したら、前と別の場所が壊れていた
- プロジェクトが進むにつれ、どこに何があるのか分からなくなった
- サービス間の境界が曖昧で、修正の影響範囲が読めない
これらは、AIが「文法」には強いが「意味」には弱いという本質的な特性から生まれる問題です。
1-2. AIの危険な特性:「それらしく」実装する能力
AIは与えられた要求を驚くほど「それらしく」実装してしまいます。例えば:
// ユーザーから「注文をキャンセルする機能を追加して」と言われてAIが生成したコード
class Order {
cancel() {
this.status = 'cancelled';
// 一見正しそうだが...
// - キャンセル可能な状態のチェックがない
// - 在庫の戻し処理がない
// - 決済のキャンセル処理がない
// - キャンセル理由の記録がない
}
}
このコードは「動く」し「それらしい」のですが、業務要件を満たしていません。しかも、こうしたコードが高速に量産されると、誤った仕様が急速にシステム全体に拡散します。
1-3. なぜ今、DDDなのか
この背景の中で、DDD(Domain-Driven Design:ドメイン駆動設計) が再評価されています。
DDDは2003年にEric Evansが提唱した設計手法で、複雑な業務領域(ドメイン)を扱うための実践的フレームワークです。従来は「大規模・複雑なシステム向け」と考えられていましたが、AI時代においてはその位置づけが変わりました。
AI がコードを書くなら、人間はモデル(概念)を書く必要がある。
この一文が、AI時代のエンジニアリングの本質を表しています。本記事では、この観点から「AI駆動開発 × DDD」を包括的に解説します。
2. AI 時代に起きている変化
AI導入によって開発現場に何が起きているのか、具体的に見ていきましょう。
2-1. コード生成の自動化:量産時代の到来
従来の開発
仕様書を読む(30分)
→ 設計を考える(1時間)
→ 実装する(3時間)
→ テストを書く(1時間)
→ レビュー(30分)
= 合計 6時間
AI活用の開発
AIに要求を伝える(5分)
→ コード生成(30秒)
→ レビュー・修正(30分)
→ テスト生成(5分)
= 合計 40分
この劇的なスピードアップにより、以下が可能になりました:
- ボイラープレート(お決まりコード)の即座生成:CRUD操作、API定義、データベースマイグレーションなど
- 複数パターンの試作:異なるアプローチを数分で試せる
- レガシーコードの modernize:古い技術スタックを新しいものに変換
2-2. テストやリファクタリングの自動化:品質向上の民主化
AIは既存コードの構造を解析し、以下を自動生成できます:
ユニットテストの自動生成例
// 既存の関数
function calculateDiscount(price: number, customerType: string): number {
if (customerType === 'premium') return price * 0.8;
if (customerType === 'regular') return price * 0.9;
return price;
}
// AIが生成したテスト
describe('calculateDiscount', () => {
it('should apply 20% discount for premium customers', () => {
expect(calculateDiscount(100, 'premium')).toBe(80);
});
it('should apply 10% discount for regular customers', () => {
expect(calculateDiscount(100, 'regular')).toBe(90);
});
it('should apply no discount for other customers', () => {
expect(calculateDiscount(100, 'guest')).toBe(100);
});
// AIは境界値テストも提案
it('should handle zero price', () => {
expect(calculateDiscount(0, 'premium')).toBe(0);
});
});
2-3. マイクロサービス乱立による複雑性の増加
開発速度が上がった結果、予期しない問題も生まれました。
従来(3ヶ月で2サービス)
[モノリス] → [User Service] [Order Service]
AI活用後(3ヶ月で10サービス)
[User Service] [Order Service] [Payment Service]
[Notification Service] [Analytics Service] [Inventory Service]
[Recommendation Service] [Review Service] [Shipping Service]
[Coupon Service]
サービスが急増すると:
- 境界が曖昧になる:どのサービスがどの責務を持つのか不明確
- 依存関係が複雑化:A→B→C→A のような循環依存が発生
- データの整合性が取りづらい:同じ概念が複数サービスに散在
2-4. AI は「文法」には強いが「意味」には弱い
これが最も重要な問題です。AIの特性を理解しましょう。
AIが得意なこと
- 構文エラーのない正しいコードを書く
- 一般的なパターンやベストプラクティスを適用する
- 大量のボイラープレートを瞬時に生成する
- コードスタイルを統一する
AIが苦手なこと
- 業務の意味(ドメイン)を理解する
- 業務ルールの複雑な制約を把握する
- システム全体の整合性を保つ
- 暗黙の前提条件を読み取る
具体例:在庫管理システムでの失敗
曖昧な指示
「商品の在庫を減らす機能を追加して」
AIが生成したコード(間違い例)
class Product {
reduceStock(quantity: number) {
this.stock -= quantity;
// 問題点:
// - 在庫がマイナスになる可能性
// - 引当済み在庫との区別がない
// - 在庫変動履歴が記録されない
// - 安全在庫を下回った時のアラートがない
}
}
DDDに基づく正しい設計
class Inventory {
private availableStock: number; // 利用可能在庫
private reservedStock: number; // 引当済み在庫
private safetyStock: number; // 安全在庫
reserve(quantity: number): Result<ReservationId, StockError> {
// ビジネスルールを守る
if (quantity <= 0) {
return Err(new InvalidQuantityError());
}
if (this.availableStock < quantity) {
return Err(new InsufficientStockError());
}
// 在庫を引き当て
this.availableStock -= quantity;
this.reservedStock += quantity;
// ドメインイベントを発行
const reservationId = new ReservationId();
this.addDomainEvent(new StockReserved(reservationId, quantity));
// 安全在庫チェック
if (this.availableStock < this.safetyStock) {
this.addDomainEvent(new LowStockDetected(this.availableStock));
}
return Ok(reservationId);
}
}
この例から分かるように、モデル(概念構造)が明確でないと、AIは表面的な実装しかできません。
3. AI 時代の観点で再解釈する DDD
DDDの各要素が、AI時代においてどのような新しい価値を持つのかを解説します。
3-1. ユビキタス言語は「AI と人間のインターフェース」になる
ユビキタス言語とは:開発チームとドメイン専門家(業務担当者)が共通して使う語彙・用語体系のことです。コード、会話、ドキュメント、すべてで同じ言葉を使います。
AI時代における重要性
AIに正しい語彙を与えると、生成物の精度が劇的に向上します。
悪い例:曖昧な用語を使った場合
人間「ユーザーが商品を買う機能を作って」
AI「了解しました」
→ User.buy(product) のような曖昧なメソッドを生成
→ 決済、在庫、配送の概念が混在
良い例:ユビキタス言語を使った場合
人間「Order集約に、OrderLineを追加するplaceOrderメソッドを実装して。
OrderStatusがDraftの時のみ許可。
ConfirmRuleに基づいてconfirmメソッドも実装。」
AI「了解しました」
→ DDD原則に沿った適切な実装を生成
実践例:ECサイトのユビキタス言語
| 用語 | 定義 | 悪い言い方(避ける) |
|---|---|---|
| Order | 顧客の注文を表す集約ルート | カート、購入、買い物 |
| OrderLine | 注文内の個別商品明細 | 注文アイテム、商品 |
| OrderStatus | 注文の状態(Draft/Confirmed/Shipped/Delivered/Cancelled) | ステータス、状況 |
| placeOrder | 注文を確定する操作 | 購入する、買う |
| ConfirmRule | 注文確定の業務ルール | チェック処理、バリデーション |
ユビキタス言語を使ったAIへの指示例
「Orderエンティティを作成してください。
【用語定義】
- Order: 顧客の注文を表す集約ルート
- OrderLine: 注文明細(商品・数量・単価を含む)
- OrderStatus: Draft(下書き)→ Confirmed(確定)→ Shipped(出荷済)→ Delivered(配達済)
【ビジネスルール】
- Orderは1つ以上のOrderLineを持つ
- Draftステータスの時のみ、OrderLineの追加・削除が可能
- Confirmedステータスへの遷移には、在庫確認が必要
- Shipped以降はキャンセル不可
このルールを守ったエンティティを実装してください。」
このように指示すると、AIは業務ルールを理解した実装を生成できます。
3-2. モデル中心設計は「AI が迷子にならないガイド」
モデルとは、業務の概念を整理した構造のことです。「どんなエンティティがあり、どんな関係性があり、どんな振る舞いを持つか」を定義します。
AIの補完の危険性
AIは曖昧な部分を「補完」しようとしますが、それが誤った方向に作用することがあります。
モデルがない場合
// AIに「注文処理を作って」と曖昧に依頼
class OrderProcessor {
processOrder(orderId, userId, items, paymentMethod) {
// AIは推測で実装を補完
// → 責務が不明確
// → どこまで処理するのか曖昧
// → テストしづらい
}
}
モデルがある場合
// モデル定義を提示してAIに依頼
/*
【集約】
- Order(集約ルート)
- OrderLine(エンティティ)
- OrderStatus(値オブジェクト)
【ドメインサービス】
- OrderConfirmationService: 注文確定のロジック
- 在庫チェック
- 決済確認
- 確定処理
【ユースケース(Application層)】
- PlaceOrderUseCase: 注文を作成する
*/
// AIが生成(モデルに沿った実装)
class Order {
private orderLines: OrderLine[];
private status: OrderStatus;
addOrderLine(line: OrderLine): Result<void, OrderError> {
if (!this.status.isDraft()) {
return Err(new OrderNotEditableError());
}
this.orderLines.push(line);
return Ok();
}
}
class OrderConfirmationService {
confirm(order: Order, inventory: InventoryService): Result<void, ConfirmationError> {
// 在庫チェック
for (const line of order.getOrderLines()) {
if (!inventory.hasStock(line.productId, line.quantity)) {
return Err(new InsufficientStockError(line.productId));
}
}
// 注文確定
return order.confirm();
}
}
モデルが明確だと、AIは:
- エンティティの設計意図を理解した実装ができる
- 振る舞いの境界を守ったコードを生成する
- 必要なメソッドや責務を予測可能になる
3-3. Bounded Context は「AI に渡す作業単位」になる
Bounded Context(境界づけられたコンテキスト) とは、特定のモデルが有効な範囲のことです。同じ「顧客」という言葉でも、販売コンテキストと配送コンテキストでは意味が異なります。
AI時代における重要性
大規模な要求を一度にAIに投げると、文脈が混ざり破綻しやすくなります。
悪い例:すべてを一度にAIに依頼
「ECサイトのバックエンドを全部実装して」
→ AIは混乱
→ 注文・在庫・決済・配送が全部混ざる
→ Userクラスが肥大化
→ 依存関係がスパゲッティ化
良い例:Bounded Contextごとに分割
【Order Context】
人間「Order Contextを実装してください。
- Order, OrderLine, OrderStatus
- PlaceOrder, ConfirmOrder, CancelOrder
- Inventory Contextとは、StockReservationイベントで連携」
AI → Order Contextに特化した実装を生成
【Inventory Context】
人間「Inventory Contextを実装してください。
- Inventory, StockReservation
- ReserveStock, ReleaseStock
- Order Contextからのイベントを受け取る」
AI → Inventory Contextに特化した実装を生成
【Payment Context】
人間「Payment Contextを実装してください...」
Context Map(コンテキスト間の関係図)
この構造をAIに伝えることで:
- 各コンテキスト内で一貫した用語が維持される
- コード生成の品質が高まる
- サービス境界の破綻を防げる
3-4. エンティティ/値オブジェクト/集約は「AI が理解できる設計言語」
DDDには明確な「パターン」があります。AIはパターン化された情報に極めて強いため、DDD の語彙を与えると別次元の品質になります。
主要なDDDパターン
1. エンティティ(Entity)
同一性(ID)を持つオブジェクト。ライフサイクルを通じて追跡される。
// AIへの指示
「Orderエンティティを作成してください。
- OrderIdで同一性を識別
- OrderStatusを持つ
- OrderLineのコレクションを持つ」
// AIが生成
class Order {
private readonly orderId: OrderId; // 同一性
private status: OrderStatus;
private orderLines: OrderLine[];
constructor(orderId: OrderId) {
this.orderId = orderId;
this.status = OrderStatus.Draft;
this.orderLines = [];
}
// 同一性で等価性を判定
equals(other: Order): boolean {
return this.orderId.equals(other.orderId);
}
}
2. 値オブジェクト(Value Object)
同一性を持たず、値そのもので等価性を判定する不変オブジェクト。
// AIへの指示
「Moneyという値オブジェクトを作成してください。
- amount(金額)とcurrency(通貨)を持つ
- 不変(Immutable)
- 値で等価性を判定
- add, subtractメソッドを持つ」
// AIが生成
class Money {
private readonly amount: number;
private readonly currency: string;
constructor(amount: number, currency: string) {
if (amount < 0) throw new Error('Amount cannot be negative');
this.amount = amount;
this.currency = currency;
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error('Cannot add different currencies');
}
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount &&
this.currency === other.currency;
}
}
3. 集約(Aggregate)
整合性を保つ境界。外部からは集約ルートを通じてのみアクセス可能。
// AIへの指示
「Order集約を作成してください。
- Orderが集約ルート
- OrderLineは集約内エンティティで、Orderを通じてのみアクセス
- 不変条件:注文には最低1つのOrderLineが必要
- Draftステータスの時のみOrderLineの変更が可能」
// AIが生成
class Order {
private orderId: OrderId;
private status: OrderStatus;
private orderLines: OrderLine[]; // 外部から直接アクセス不可
// 集約ルートを通じてのみ操作
addOrderLine(product: Product, quantity: number): Result<void, OrderError> {
if (!this.status.isDraft()) {
return Err(new OrderNotEditableError());
}
const line = new OrderLine(product, quantity);
this.orderLines.push(line);
return Ok();
}
confirm(): Result<void, OrderError> {
// 不変条件チェック
if (this.orderLines.length === 0) {
return Err(new EmptyOrderError());
}
this.status = OrderStatus.Confirmed;
this.addDomainEvent(new OrderConfirmed(this.orderId));
return Ok();
}
// OrderLineは外部に公開しない(カプセル化)
getOrderLines(): ReadonlyArray<OrderLine> {
return [...this.orderLines]; // コピーを返す
}
}
AIが理解できる設計言語の効果
DDDパターンを明示すると、AIは:
- 集約境界を踏まえた実装を出力できる
- 不変条件を守ったコードを生成できる
- 値オブジェクトの利点(不変性・等価性)を活用した実装を提案できる
- カプセル化を適切に行える
3-5. オニオンアーキテクチャは「AI の暴走からドメインを守る防壁」
オニオンアーキテクチャは、依存関係を内側(ドメイン)に向けることで、ビジネスロジックをインフラから守る設計です。
層の構造
AI時代における重要性
AIは便利ですが、層構造がないと以下のような問題を起こしがちです:
悪い例:層がない場合のAI生成コード
class Order {
confirm() {
// ドメイン層にインフラコードが混入!
const db = new PostgresClient();
db.query('UPDATE orders SET status = ? WHERE id = ?', ['confirmed', this.id]);
const sns = new AWS.SNS();
sns.publish({ message: 'Order confirmed' });
}
}
良い例:オニオンアーキテクチャを指示
「オニオンアーキテクチャで実装してください。
【Domain Layer】
- Order エンティティ
- confirmメソッド(純粋なビジネスロジックのみ)
【Application Layer】
- ConfirmOrderUseCase
- リポジトリとイベント発行を orchestrate
【Infrastructure Layer】
- PostgresOrderRepository
- SNSEventPublisher」
AIが生成(正しい実装)
// Domain Layer
class Order {
confirm(): Result<void, OrderError> {
// 純粋なビジネスロジック
if (this.orderLines.length === 0) {
return Err(new EmptyOrderError());
}
this.status = OrderStatus.Confirmed;
this.addDomainEvent(new OrderConfirmed(this.orderId));
return Ok();
}
}
// Application Layer
class ConfirmOrderUseCase {
constructor(
private orderRepository: IOrderRepository,
private eventPublisher: IEventPublisher
) {}
async execute(orderId: OrderId): Promise<Result<void, UseCaseError>> {
const order = await this.orderRepository.findById(orderId);
const result = order.confirm();
if (result.isErr()) {
return Err(new ConfirmationFailedError(result.error));
}
await this.orderRepository.save(order);
await this.eventPublisher.publishAll(order.getDomainEvents());
return Ok();
}
}
// Infrastructure Layer
class PostgresOrderRepository implements IOrderRepository {
async save(order: Order): Promise<void> {
// データベース操作
}
}
この構造により:
- ドメイン層が汚染されない
- コード配置の誤りを防げる
- テストが容易(ドメインロジックは純粋関数)
- 長期保守しやすい(フレームワーク変更の影響が小さい)
4. AI × DDD の役割分担(技術者視点)
AIと人間が協働する際の、明確な責任分界を理解することが成功の鍵です。
4-1. 役割分担マトリックス
| 領域 | 人間(ドメイン専門家・設計者) | AI(LLM) | 理由 |
|---|---|---|---|
| ドメイン理解 | ◎ 必須 | × 不可 | AIは業務の意味を理解できない |
| ユビキタス言語定義 | ◎ 主導 | △ 補助 | 用語の意味は人間が決める、AIは整合性チェックを支援 |
| モデル設計 | ◎ 主担当 | ○ 提案・補完 | 人間が大枠を決め、AIが詳細を提案 |
| 境界の定義 | ◎ 必須 | △ 助言 | Bounded Contextは人間が決定 |
| ビジネスルール | ◎ 定義 | × レビューのみ | AIはルールを理解できない |
| コード生成 | △ レビュー中心 | ◎ 主担当 | AIの最も得意な領域 |
| テスト生成 | ○ 戦略設計 | ◎ 自動生成 | 人間がテスト戦略を決め、AIが実装 |
| リファクタリング | ○ 判断 | ◎ 作業実行 | 人間が方針を決め、AIが作業 |
| パフォーマンス最適化 | ○ 計測・判断 | ◎ 実装 | 人間がボトルネック特定、AIが最適化 |
4-2. 具体的な協働フロー
フロー例:新機能開発
Step 1:人間が主導(ドメイン理解)
ドメイン専門家「注文のキャンセルポリシーを整理しましょう」
開発者「以下の条件で整理しますね:
- 注文確定後24時間以内:無条件キャンセル可
- 24時間〜出荷前:キャンセル料10%
- 出荷後:キャンセル不可」
→ ユビキタス言語として「CancelPolicy」と定義
Step 2:人間が主導(モデル設計)
開発者「モデルはこう設計します:
【集約】Order
- OrderStatus(Draft/Confirmed/Shipped/Delivered/Cancelled)
- confirmedAt(確定日時)
【値オブジェクト】CancelPolicy
- canCancel(order): boolean
- calculateCancellationFee(order): Money
【ドメインサービス】OrderCancellationService
- cancel(order, reason): Result<void, CancellationError>」
Step 3:AIに実装を依頼
開発者 → AI「以下のモデルで実装してください:
【CancelPolicy値オブジェクト】
- canCancel(order): boolean
- Shippedステータス以降はfalse
- Confirmedから24時間以内はtrue
- それ以外はキャンセル料が発生するがtrue
- calculateFee(order): Money
- 24時間以内:0円
- 24時間〜出荷前:注文金額の10%
TypeScriptで実装してください」
AI「了解しました」→ コード生成
Step 4:人間がレビュー
// AIが生成したコード
class CancelPolicy {
canCancel(order: Order): boolean {
if (order.status.isShipped() || order.status.isDelivered()) {
return false;
}
return true;
}
calculateFee(order: Order): Money {
const hoursSinceConfirmed = this.getHoursSince(order.confirmedAt);
if (hoursSinceConfirmed <= 24) {
return Money.zero(order.totalAmount.currency);
}
return order.totalAmount.multiply(0.1);
}
}
開発者「良いですね!ただし、Cancelledステータスの注文は
キャンセル不可にする必要があります」
AI「修正します」→ 再生成
Step 5:AIにテスト生成を依頼
開発者 → AI「このCancelPolicyのテストを生成してください。
境界値テストも含めてください」
AI → テスト生成(正常系・異常系・境界値)
4-3. AI に任せてはいけないこと
以下は人間が必ず判断・決定すべき領域です:
-
ビジネスルールの定義
- AI:「在庫が0になったら注文できなくする実装を追加しました」
- 人間:「待って。予約注文は許可する必要があるよ」
-
境界の決定
- AI:「UserServiceとOrderServiceを統合した方が効率的です」
- 人間:「いや、コンテキストが異なるから分離すべき」
-
トレードオフの判断
- AI:「パフォーマンスのためにキャッシュを追加しました」
- 人間:「データ整合性が重要なので、ここはキャッシュすべきでない」
4-4. AIに積極的に任せるべきこと
-
ボイラープレートの生成
- Repository実装
- DTOとドメインモデルの変換
- APIエンドポイント定義
-
テストコードの生成
- ユニットテスト
- 境界値テスト
- モックの作成
-
リファクタリング作業
- 命名の統一
- コード重複の除去
- フォーマット整形
-
ドキュメント生成
- API仕様書
- コードコメント
- README
5. AI × DDD の具体的な開発ワークフロー例
実際のプロジェクトでどのように進めるか、注文システムを例に詳しく見ていきましょう。
Step 1. ドメイン専門家とユビキタス言語を作る
1-1. ドメイン専門家へのヒアリング
開発者「注文業務について教えてください」
ドメイン専門家(営業担当)「
お客様が商品を選んで、カートに入れます。
その後、注文を確定すると、在庫が確保されて、決済が行われます。
決済が完了したら、倉庫に出荷指示が出ます。」
開発者「『カート』と『注文』は別物ですか?」
専門家「そうですね。カートは確定前、注文は確定後です。
カートは何度でも変更できますが、注文は確定したら
基本的に変更できません。」
開発者「なるほど。では『注文確定』という操作が重要ですね」
1-2. ユビキタス言語の定義
| 用語 | 英語 | 定義 | 関連する操作 |
|---|---|---|---|
| 注文 | Order | 顧客が商品を購入する単位 | 作成、確定、キャンセル |
| 注文明細 | OrderLine | 注文内の個別商品情報 | 追加、削除、数量変更 |
| 注文状態 | OrderStatus | 注文のライフサイクル状態 | Draft → Confirmed → Shipped |
| 注文確定 | placeOrder / confirmOrder | 注文を確定し、在庫確保・決済を実行 | - |
| 確定ルール | ConfirmRule | 注文を確定できる条件 | 在庫確認、決済手段確認 |
| キャンセルポリシー | CancelPolicy | キャンセル可否・手数料の判定ルール | - |
1-3. 言語の統一をAIに支援させる
開発者 → AI「以下のドキュメントを読んで、用語の不統一を指摘してください:
【既存仕様書】
『ユーザーが商品を購入する際、まずショッピングカートに
アイテムを追加します。その後、オーダーを確定すると...』
【ユビキタス言語定義】
- 注文(Order):顧客が商品を購入する単位
- 注文明細(OrderLine):注文内の個別商品情報
不統一な用語を指摘し、修正案を提示してください」
AI「以下の不統一を検出しました:
1. 『購入』→『注文』に統一すべき
2. 『ショッピングカート』→『注文(Draftステータス)』に統一
3. 『アイテム』→『注文明細』に統一
4. 『オーダー』→『注文』に統一
修正後:
『顧客が商品を注文する際、まず注文(Draftステータス)に
注文明細を追加します。その後、注文を確定すると...』」
Step 2. Bounded Context を整理
2-1. コンテキストの識別
業務の会話から、異なる責務領域を見つけます。
【Order Context(注文コンテキスト)】
責務:顧客の注文を管理
- Order(注文)
- OrderLine(注文明細)
- OrderStatus(注文状態)
- CancelPolicy(キャンセルポリシー)
【Inventory Context(在庫コンテキスト)】
責務:商品の在庫を管理
- Inventory(在庫)
- StockReservation(在庫引当)
- Product(商品マスタ)
【Payment Context(決済コンテキスト)】
責務:決済処理を管理
- Payment(決済)
- PaymentMethod(決済手段)
- Transaction(取引記録)
【Shipping Context(配送コンテキスト)】
責務:配送を管理
- Shipment(配送)
- DeliveryAddress(配送先)
- ShippingStatus(配送状態)
2-2. コンテキスト間の関係を定義(Context Map)
2-3. AIにContext Mapの検証を依頼
開発者 → AI「以下のContext Mapをレビューしてください:
【現在の設計】
Order Context → Inventory Context(イベント駆動)
Order Context → Payment Context(イベント駆動)
Payment Context → Inventory Context(直接呼び出し)
問題点や改善案を指摘してください」
AI「以下の問題を検出しました:
1. 循環依存のリスク
Payment → Inventory の依存は、
Order → Payment → Inventory の依存チェーンを作り、
循環依存につながる可能性があります。
2. 改善案
Payment Contextは在庫を直接操作せず、
PaymentCompletedイベントを発行し、
Inventory Contextがそれを受け取る設計を推奨します。
【推奨設計】
Order Context → Inventory Context(イベント)
Order Context → Payment Context(イベント)
Payment Context → Order Context(イベント)※新規
Order Context → Inventory Context(イベント)※在庫確保解除
Step 3. エンティティ/値オブジェクト/集約を定義
3-1. モデル設計書の作成
# Order Context モデル設計書
## 集約:Order
### 集約ルート:Order
- OrderId(値オブジェクト)
- CustomerId(値オブジェクト)
- OrderStatus(値オブジェクト)
- OrderLines(エンティティのコレクション)
- TotalAmount(値オブジェクト)
- confirmedAt(日時)
- createdAt(日時)
### 集約内エンティティ:OrderLine
- OrderLineId(値オブジェクト)
- ProductId(値オブジェクト)
- Quantity(値オブジェクト)
- UnitPrice(値オブジェクト)
- Subtotal(値オブジェクト)
## 値オブジェクト
### OrderStatus
- 列挙値:Draft, Confirmed, Shipped, Delivered, Cancelled
- 状態遷移ルール:
- Draft → Confirmed(確定操作)
- Confirmed → Shipped(出荷操作)
- Shipped → Delivered(配達完了)
- Draft/Confirmed → Cancelled(キャンセル操作)
### Money
- amount(金額)
- currency(通貨)
- 演算:add, subtract, multiply
### Quantity
- value(数量)
- 制約:1以上
## ビジネスルール(不変条件)
1. Orderは最低1つのOrderLineを持つ
2. Draftステータスの時のみOrderLineの変更が可能
3. 確定にはConfirmRuleを満たす必要がある
4. キャンセルにはCancelPolicyを満たす必要がある
3-2. AIにモデルの実装を依頼
開発者 → AI「以下のモデル設計書に基づいて、
TypeScriptでエンティティを実装してください:
[モデル設計書を貼り付け]
以下の条件で実装してください:
- 不変条件をコンストラクタとメソッドで保証
- 値オブジェクトは不変
- Resultモデルでエラーハンドリング
- ドメインイベントを発行」
AI「了解しました。実装を生成します」
AIが生成したコード例
// 値オブジェクト:OrderId
class OrderId {
private constructor(private readonly value: string) {}
static create(value: string): Result<OrderId, ValidationError> {
if (!value || value.trim().length === 0) {
return Err(new ValidationError('OrderId cannot be empty'));
}
return Ok(new OrderId(value));
}
static generate(): OrderId {
return new OrderId(uuidv4());
}
getValue(): string {
return this.value;
}
equals(other: OrderId): boolean {
return this.value === other.value;
}
}
// 値オブジェクト:OrderStatus
class OrderStatus {
private static readonly DRAFT = new OrderStatus('Draft');
private static readonly CONFIRMED = new OrderStatus('Confirmed');
private static readonly SHIPPED = new OrderStatus('Shipped');
private static readonly DELIVERED = new OrderStatus('Delivered');
private static readonly CANCELLED = new OrderStatus('Cancelled');
private constructor(private readonly value: string) {}
static draft(): OrderStatus { return OrderStatus.DRAFT; }
static confirmed(): OrderStatus { return OrderStatus.CONFIRMED; }
static shipped(): OrderStatus { return OrderStatus.SHIPPED; }
static delivered(): OrderStatus { return OrderStatus.DELIVERED; }
static cancelled(): OrderStatus { return OrderStatus.CANCELLED; }
isDraft(): boolean { return this === OrderStatus.DRAFT; }
isConfirmed(): boolean { return this === OrderStatus.CONFIRMED; }
isShipped(): boolean { return this === OrderStatus.SHIPPED; }
canTransitionTo(newStatus: OrderStatus): boolean {
// 状態遷移ルールを実装
if (this.isDraft()) {
return newStatus.isConfirmed() || newStatus.isCancelled();
}
if (this.isConfirmed()) {
return newStatus.isShipped() || newStatus.isCancelled();
}
if (this.isShipped()) {
return newStatus.isDelivered();
}
return false;
}
}
// 値オブジェクト:Quantity
class Quantity {
private constructor(private readonly value: number) {}
static create(value: number): Result<Quantity, ValidationError> {
if (value < 1) {
return Err(new ValidationError('Quantity must be at least 1'));
}
return Ok(new Quantity(value));
}
getValue(): number {
return this.value;
}
add(other: Quantity): Quantity {
return new Quantity(this.value + other.value);
}
}
// エンティティ:OrderLine
class OrderLine {
constructor(
private readonly orderLineId: OrderLineId,
private readonly productId: ProductId,
private quantity: Quantity,
private readonly unitPrice: Money
) {}
getSubtotal(): Money {
return this.unitPrice.multiply(this.quantity.getValue());
}
changeQuantity(newQuantity: Quantity): void {
this.quantity = newQuantity;
}
equals(other: OrderLine): boolean {
return this.orderLineId.equals(other.orderLineId);
}
}
// 集約ルート:Order
class Order extends AggregateRoot<OrderId> {
private status: OrderStatus;
private orderLines: OrderLine[];
private confirmedAt?: Date;
private constructor(
orderId: OrderId,
private readonly customerId: CustomerId,
private readonly createdAt: Date
) {
super(orderId);
this.status = OrderStatus.draft();
this.orderLines = [];
}
static create(customerId: CustomerId): Order {
const order = new Order(
OrderId.generate(),
customerId,
new Date()
);
order.addDomainEvent(new OrderCreated(order.getId(), customerId));
return order;
}
// ビジネスメソッド:注文明細の追加
addOrderLine(
product: ProductId,
quantity: Quantity,
unitPrice: Money
): Result<void, OrderError> {
// 不変条件チェック:Draftステータスのみ変更可能
if (!this.status.isDraft()) {
return Err(new OrderNotEditableError('Order is not in Draft status'));
}
const line = new OrderLine(
OrderLineId.generate(),
product,
quantity,
unitPrice
);
this.orderLines.push(line);
this.addDomainEvent(new OrderLineAdded(this.getId(), line));
return Ok();
}
// ビジネスメソッド:注文確定
confirm(): Result<void, OrderError> {
// 不変条件チェック:最低1つのOrderLineが必要
if (this.orderLines.length === 0) {
return Err(new EmptyOrderError('Order must have at least one line'));
}
// 状態遷移チェック
const newStatus = OrderStatus.confirmed();
if (!this.status.canTransitionTo(newStatus)) {
return Err(new InvalidStatusTransitionError(
`Cannot transition from ${this.status} to ${newStatus}`
));
}
this.status = newStatus;
this.confirmedAt = new Date();
// ドメインイベント発行
this.addDomainEvent(new OrderConfirmed(
this.getId(),
this.getTotalAmount(),
this.orderLines.map(line => ({
productId: line.getProductId(),
quantity: line.getQuantity()
}))
));
return Ok();
}
// ビジネスメソッド:キャンセル
cancel(policy: CancelPolicy, reason: string): Result<void, OrderError> {
// ポリシーチェック
if (!policy.canCancel(this)) {
return Err(new CancellationNotAllowedError('Order cannot be cancelled'));
}
const fee = policy.calculateFee(this);
const newStatus = OrderStatus.cancelled();
if (!this.status.canTransitionTo(newStatus)) {
return Err(new InvalidStatusTransitionError());
}
this.status = newStatus;
this.addDomainEvent(new OrderCancelled(this.getId(), reason, fee));
return Ok();
}
// 計算ロジック
getTotalAmount(): Money {
return this.orderLines.reduce(
(total, line) => total.add(line.getSubtotal()),
Money.zero('JPY')
);
}
// ゲッター(カプセル化)
getStatus(): OrderStatus {
return this.status;
}
getOrderLines(): ReadonlyArray<OrderLine> {
return [...this.orderLines];
}
getConfirmedAt(): Date | undefined {
return this.confirmedAt;
}
}
Step 4. AI にコード生成を依頼
すでにStep 3で実装されているので、次は周辺コードを依頼します。
4-1. Repository Interfaceの生成
開発者 → AI「Orderエンティティ用のRepositoryインターフェースを作成してください。
以下のメソッドを含めてください:
- findById
- save
- delete
- findByCustomerId
DDDのリポジトリパターンに従ってください」
AI「了解しました」
// AIが生成
interface IOrderRepository {
findById(orderId: OrderId): Promise<Result<Order, RepositoryError>>;
save(order: Order): Promise<Result<void, RepositoryError>>;
delete(orderId: OrderId): Promise<Result<void, RepositoryError>>;
findByCustomerId(customerId: CustomerId): Promise<Result<Order[], RepositoryError>>;
nextId(): OrderId;
}
4-2. Application Layerの生成
開発者 → AI「以下のユースケースを実装してください:
【PlaceOrderUseCase(注文確定ユースケース)】
入力:
- orderId: OrderId
処理:
1. Orderを取得
2. Order.confirm()を呼び出し
3. ドメインイベントをイベントバスに発行
4. Orderを保存
出力:Result<void, UseCaseError>
オニオンアーキテクチャに従い、Application Layerとして実装してください」
AI「了解しました」
// AIが生成
class PlaceOrderUseCase {
constructor(
private readonly orderRepository: IOrderRepository,
private readonly eventBus: IEventBus
) {}
async execute(request: PlaceOrderRequest): Promise<Result<void, UseCaseError>> {
// 1. Orderを取得
const orderResult = await this.orderRepository.findById(request.orderId);
if (orderResult.isErr()) {
return Err(new OrderNotFoundError(request.orderId));
}
const order = orderResult.value;
// 2. Order.confirm()を呼び出し
const confirmResult = order.confirm();
if (confirmResult.isErr()) {
return Err(new ConfirmationFailedError(confirmResult.error));
}
// 3. Orderを保存
const saveResult = await this.orderRepository.save(order);
if (saveResult.isErr()) {
return Err(new SaveFailedError(saveResult.error));
}
// 4. ドメインイベントを発行
const events = order.getDomainEvents();
for (const event of events) {
await this.eventBus.publish(event);
}
order.clearDomainEvents();
return Ok();
}
}
interface PlaceOrderRequest {
orderId: OrderId;
}
Step 5. AI の出力を人間がレビューし、修正
開発者「良い実装ですね。ただし、トランザクション管理が抜けています。
イベント発行前にデータベース保存が失敗した場合、
ロールバックが必要です。
また、confirm()が失敗した場合のログ出力も追加してください」
AI「修正します」
// AIが修正した実装
class PlaceOrderUseCase {
constructor(
private readonly orderRepository: IOrderRepository,
private readonly eventBus: IEventBus,
private readonly unitOfWork: IUnitOfWork,
private readonly logger: ILogger
) {}
async execute(request: PlaceOrderRequest): Promise<Result<void, UseCaseError>> {
// トランザクション開始
await this.unitOfWork.begin();
try {
// 1. Orderを取得
const orderResult = await this.orderRepository.findById(request.orderId);
if (orderResult.isErr()) {
await this.unitOfWork.rollback();
return Err(new OrderNotFoundError(request.orderId));
}
const order = orderResult.value;
// 2. Order.confirm()を呼び出し
const confirmResult = order.confirm();
if (confirmResult.isErr()) {
await this.unitOfWork.rollback();
this.logger.warn(`Order confirmation failed: ${confirmResult.error}`);
return Err(new ConfirmationFailedError(confirmResult.error));
}
// 3. Orderを保存
const saveResult = await this.orderRepository.save(order);
if (saveResult.isErr()) {
await this.unitOfWork.rollback();
this.logger.error(`Order save failed: ${saveResult.error}`);
return Err(new SaveFailedError(saveResult.error));
}
// 4. コミット
await this.unitOfWork.commit();
// 5. ドメインイベントを発行(コミット後)
const events = order.getDomainEvents();
for (const event of events) {
await this.eventBus.publish(event);
}
order.clearDomainEvents();
this.logger.info(`Order confirmed successfully: ${order.getId()}`);
return Ok();
} catch (error) {
await this.unitOfWork.rollback();
this.logger.error(`Unexpected error in PlaceOrderUseCase: ${error}`);
return Err(new UnexpectedError(error));
}
}
}
Step 6. 仕様・モデルを更新しながらサイクルを回す
継続的な改善サイクル
【フィードバックループ】
Week 1:
- 初期モデル作成
- AI実装
- レビュー → 課題発見「キャンセルルールが不足」
Week 2:
- CancelPolicy追加
- AI実装
- レビュー → 課題発見「返金処理が抜けている」
Week 3:
- RefundPolicy追加
- Paymentコンテキストとの連携設計
- AI実装
- レビュー → 改善点「イベント駆動にすべき」
Week 4:
- イベント駆動アーキテクチャに変更
- AI実装
- レビュー → 安定
このサイクルを回すことで、AIとDDDによる継続的進化開発(Evolutionary Model) が成立します。
6. まとめ:AI 時代にこそ DDD が必要な理由
6-1. AIは「速さ」をもたらしたが、「複雑さ」も増幅した
AI導入により、以下が実現しました:
- コード生成速度が10倍〜100倍
- プロトタイピングが数時間で完了
- リファクタリングが自動化
しかし同時に、こんな問題も:
- 曖昧な仕様が高速で拡散
- 文脈を理解しないコードが量産
- モデルがないシステムが急速に破綻
6-2. 「速く作る」から「正しく作る」へのシフト
従来の開発では「いかに速く実装するか」が課題でしたが、AI時代は違います。
AI以前の課題
実装速度が遅い
→ 人月の神話
→ 開発者を増やしても遅い
AI時代の課題
実装は速いが、方向性を誤ると破綻する
→ 「何を作るか」の定義が重要
→ モデル(概念)の明確化が鍵
6-3. AI は DDD を不要にするのではなく、必須にする
重要な結論:
AIが強くなるほど、モデル(ドメイン)の重要性が高まる
なぜなら:
- AIは意味を理解できない → 人間がモデルで意味を定義する必要がある
- AIは補完しすぎる → 境界を明確にしないと、誤った方向に暴走する
- AIは高速に生成する → 誤りも高速に拡散するため、最初の設計が重要
6-4. AI時代のエンジニアの役割
従来のエンジニア像:
要件定義 → 設計 → 実装 → テスト
すべてを1人でこなす「実装者」
AI時代のエンジニア像:
ドメイン理解 → モデル設計 → AIへの指示 → レビュー
「アーキテクト/モデラー/AIマネージャー」
求められるスキルの変化:
| 従来 | AI時代 |
|---|---|
| コーディング速度 | モデリング能力 |
| フレームワーク知識 | ドメイン理解力 |
| デバッグ技術 | 抽象化思考 |
| 実装パターン | 設計パターン |
| 単独作業 | AIとの協働 |
6-5. DDDはAI時代の「共通言語」
DDDが提供するもの:
- ユビキタス言語 → AIとの対話インターフェース
- モデル中心設計 → AIの補完を正しい方向に導く
- Bounded Context → AIに渡す作業単位の明確化
- 戦術パターン → AIが理解できる設計言語
- オニオンアーキテクチャ → AIの暴走からドメインを守る防壁
6-6. 実践への第一歩
今日から始められること:
ステップ1:小さく始める
現在のプロジェクトで、1つの機能に対して:
1. ユビキタス言語を3つ定義してみる
2. エンティティと値オブジェクトを区別する
3. 不変条件を明示する
ステップ2:AIと協働する
AIに依頼する際:
1. 「Orderエンティティを作って」ではなく
2. 「Orderエンティティを作成。OrderIdで識別、
Draftステータスのみ編集可能、最低1つのOrderLineが必要」
と具体的に指示する
ステップ3:継続的に改善
1週間ごとに振り返り:
- モデルの曖昧さはどこにあったか?
- AIが誤解した部分はどこか?
- ユビキタス言語は機能したか?
6-7. DDD vs SDD:どちらを選ぶべきか?
近年、SDD(Specification-Driven Development:仕様駆動開発) という考え方も注目されています。DDDとSDDはどう違い、どう使い分けるべきでしょうか?
SDDとは何か
SDD(Specification-Driven Development) は、仕様(Specification)を中心に開発を進める手法です。形式的な仕様記述から実装やテストを自動生成することを目指します。
主な特徴:
- 形式的仕様記述:数学的・論理的に厳密な仕様
- 仕様からコード生成:仕様から実装を自動生成
- 契約による設計(Design by Contract):事前条件・事後条件・不変条件を明示
- 検証可能性:仕様の正しさを形式的に検証可能
DDDとSDDの比較表
| 観点 | DDD(ドメイン駆動設計) | SDD(仕様駆動開発) |
|---|---|---|
| 中心概念 | ドメインモデル(業務の概念) | 形式的仕様(動作の定義) |
| 記述方法 | ユビキタス言語、クラス図、コード | 形式仕様言語(TLA+, Alloy等) |
| 主な目的 | 業務理解の共有、複雑さの管理 | 正確性の保証、自動検証 |
| コミュニケーション | 自然言語ベース、非技術者も参加 | 数学的、技術者中心 |
| 適用領域 | ビジネスロジック、複雑な業務 | 安全性重視、アルゴリズム |
| 学習曲線 | 中程度(概念の理解が必要) | 高い(形式手法の知識が必要) |
| AI時代の強み | AIへの指示が自然、反復的改善 | 厳密な検証、正確性保証 |
具体例で理解する:注文キャンセル機能
同じ「注文キャンセル機能」を実装する場合、DDDとSDDではアプローチが大きく異なります。
DDDアプローチ:業務の概念を自然に表現
DDDでは、ドメイン専門家と会話するようにコードを書きます。ビジネスルールをそのままコードに落とし込む感覚です。
// ユビキタス言語を使った自然な記述
class CancelPolicy {
/**
* 注文がキャンセル可能かを判定する
*
* ビジネスルール:
* - 出荷済み・配達済みの注文はキャンセル不可
* - 既にキャンセルされた注文はキャンセル不可
*/
canCancel(order: Order): boolean {
// ビジネスルールを自然言語に近い形で記述
if (order.isShipped() || order.isDelivered()) {
return false;
}
if (order.isCancelled()) {
return false;
}
return true;
}
/**
* キャンセル料を計算する
*
* ビジネスルール:
* - 確定後24時間以内:無料
* - 24時間〜出荷前:10%
*/
calculateFee(order: Order): Money {
const hoursSinceConfirmed = this.getHoursSince(order.confirmedAt);
if (hoursSinceConfirmed <= 24) {
return Money.zero(order.totalAmount.currency);
}
return order.totalAmount.multiply(0.1);
}
}
DDDの特徴:
- ✅ 読みやすい:非技術者(営業、企画)も理解できる
- ✅ メンテナンスしやすい:ビジネスルールが変わった時、どこを変えればいいか一目瞭然
- ✅ AIに伝えやすい:「出荷済みの注文はキャンセル不可」と自然言語で指示できる
- ❌ 網羅性の保証が難しい:すべてのパターンをカバーしているか不明
- ❌ 並行処理の正確性:複数のリクエストが同時に来た場合の動作が不明確
SDDアプローチ:数学的に厳密な仕様
SDDでは、すべての状態と遷移を数学的に定義します。形式的な検証ツール(TLA+、Alloy等)で正確性を証明できます。
// TLA+風の形式仕様(擬似コード)
SPECIFICATION OrderCancellation
CONSTANTS
OrderStatus = {Draft, Confirmed, Shipped, Delivered, Cancelled}
VARIABLES
order_status, // 注文の現在の状態
confirmed_at, // 確定日時(NULL or 自然数)
current_time // 現在時刻
// 型不変条件:すべての状態で満たされるべき条件
TypeInvariant ==
/\ order_status ∈ OrderStatus
/\ confirmed_at ∈ Nat ∪ {NULL}
/\ current_time ∈ Nat
// キャンセル可能条件の述語
CanCancelPredicate ==
/\ order_status ∉ {Shipped, Delivered, Cancelled}
// キャンセル料の計算(すべてのケースを網羅)
CancellationFee ==
CASE order_status = Draft → 0
[] order_status = Confirmed ∧ (current_time - confirmed_at) ≤ 24 → 0
[] order_status = Confirmed ∧ (current_time - confirmed_at) > 24 → total_amount * 0.1
[] OTHER → UNDEFINED
// 安全性特性:キャンセル後は再度キャンセルできない
SafetyProperty ==
□(order_status = Cancelled ⇒ ¬CanCancelPredicate')
// 活性特性:キャンセルリクエストは最終的に処理される
LivenessProperty ==
□◇(cancellation_requested ⇒ ◇order_status = Cancelled ∨ ◇cancellation_rejected)
SDDの特徴:
- ✅ 正確性を証明できる:モデル検査ツールですべての状態を検証
- ✅ バグを事前に発見:実装前にデッドロックや不整合を検出
- ✅ 網羅性が保証される:考慮漏れがない
- ❌ 学習コストが高い:形式手法の知識が必要
- ❌ 非技術者には理解困難:営業や企画担当者が読めない
- ❌ AIへの指示が複雑:形式仕様をAIに伝えるのは困難
アプローチの違いを表で比較
| 視点 | DDDアプローチ | SDDアプローチ |
|---|---|---|
| 記述スタイル | 自然言語に近い | 数学的記法 |
| 検証方法 | テスト・レビュー | 形式検証(モデル検査) |
| エラー検出 | 実行時・テスト時 | 設計時(実装前) |
| 変更容易性 | 高い(ビジネスルールの変更に強い) | 低い(仕様変更には再検証が必要) |
| AIとの相性 | 非常に良い(自然言語で指示) | 限定的(形式仕様の理解が必要) |
| チーム協働 | 全員参加可能 | 技術者中心 |
| 適用コスト | 中程度 | 高い |
| 保証レベル | テストカバレッジ依存 | 数学的証明 |
どちらを選ぶべきか?
DDDを選ぶべき例:ECサイトの注文キャンセル
要件:
- ビジネスルールが複雑だが、明確に定義できる
- 営業やカスタマーサポートと協働して仕様を決める
- 要件が頻繁に変わる(キャンセル料の条件など)
- AIで高速に実装・修正したい
→ DDDのアプローチが適している
→ 自然言語ベースで、チーム全体で理解・変更できる
SDDを選ぶべき例:銀行の決済システム
要件:
- 二重決済の絶対的な防止が必要
- すべての状態遷移で整合性を保証
- 規制当局への証明が必要
- 一度決まった仕様はほぼ変わらない
→ SDDのアプローチが適している
→ 形式検証で安全性を数学的に証明
AI時代における使い分け
DDDを選ぶべき場合
-
複雑なビジネスロジックを扱う
- ECサイト、会計システム、在庫管理など
- ドメイン専門家との協働が必要
- 要件が頻繁に変わる
-
チームでの開発
- 非技術者(営業、企画、ドメイン専門家)との協働
- ユビキタス言語で共通理解を作る
- AIに自然言語で指示したい
-
AI駆動開発との相性
- AIが理解しやすい自然な記述
- 反復的な改善がしやすい
- プロンプトで詳細に説明できる
例:AIへの指示
「Order集約にキャンセル機能を追加してください。
CancelPolicyに基づいて、出荷前のみキャンセル可能。
24時間以内は無料、それ以降は10%の手数料。」
→ AIは自然に理解して実装できる
SDDを選ぶべき場合
-
安全性・正確性が最重要
- 金融システム(決済処理、取引アルゴリズム)
- 医療機器制御
- 航空宇宙システム
- 暗号化アルゴリズム
-
複雑なアルゴリズムや状態遷移
- 分散システムの合意アルゴリズム(Raft, Paxos)
- 並行処理の正確性保証
- デッドロック検出
-
形式的検証が必要
- 数学的証明が求められる
- 規制対応(航空、医療、金融)
- セキュリティクリティカル
例:分散合意アルゴリズム
形式仕様で状態遷移を定義
→ モデル検査ツールで検証
→ すべての状態で安全性を保証
→ 実装に変換
ハイブリッドアプローチ:DDDとSDDの併用
実際のプロジェクトでは、両方を使い分けることが効果的です。
実践例:注文システムでのハイブリッド
DDD部分(ビジネスロジック)
// ドメインモデル:自然な記述
class Order {
confirm(): Result<void, OrderError> {
if (this.orderLines.length === 0) {
return Err(new EmptyOrderError());
}
// 決済処理は形式仕様で検証されたアルゴリズムを使用
const paymentResult = this.paymentProcessor.process(this);
if (paymentResult.isOk()) {
this.status = OrderStatus.Confirmed;
return Ok();
}
return Err(new PaymentFailedError(paymentResult.error));
}
}
SDD部分(決済アルゴリズム)
// 形式仕様で定義された決済処理
// - 二重決済の防止を形式検証
// - デッドロックフリーを証明
// - すべての状態遷移を検証済み
class PaymentProcessor {
// 形式仕様から生成されたコード
// すべてのエッジケースが検証済み
process(order: Order): Result<Transaction, PaymentError> {
// 形式仕様に基づいた実装
// TLA+で検証済みのロジック
}
}
AI時代における選択基準
| シナリオ | 推奨アプローチ | 理由 |
|---|---|---|
| スタートアップのMVP開発 | DDD | 速い反復、要件変更に強い、AIと相性良い |
| 大規模ECサイトの注文管理 | DDD | 複雑なビジネスルール、ドメイン専門家との協働 |
| 決済システムのコア機能 | SDD | 正確性・安全性が最重要 |
| 在庫管理の基本機能 | DDD | ビジネスロジック中心 |
| 在庫引当の競合制御 | SDD | 並行制御の正確性保証 |
| AIエージェントとの協働開発 | DDD | 自然言語ベース、反復改善しやすい |
| 分散トランザクション | SDD | 形式検証が必要 |
| ユーザー向け機能 | DDD | 要件が変わりやすい |
| インフラ・アルゴリズム | SDD | 正確性が重要 |
まとめ:DDDとSDDの使い分け
DDD(ドメイン駆動設計)が向いているケース
- ビジネスロジックが複雑
- ドメイン専門家との協働が必要
- 要件が頻繁に変わる
- AI駆動開発を活用したい
- チーム全体で理解を共有したい
SDD(仕様駆動開発)が向いているケース
- 安全性・正確性が最重要
- 複雑なアルゴリズムや状態遷移
- 形式的検証が必要
- 規制対応が求められる
- 数学的証明が必要
AI時代の実践的アプローチ
1. システム全体のアーキテクチャ → DDD
2. ビジネスロジック → DDD
3. クリティカルなアルゴリズム → SDD
4. AIとの協働部分 → DDD
5. 形式検証が必要な部分 → SDD
多くのビジネスアプリケーションでは、DDDをベースにしつつ、クリティカルな部分でSDDを併用するのが現実的です。特にAI時代においては、DDDの自然言語ベースのアプローチが、AIとの協働において大きなアドバンテージとなります。
参考リンク
関連記事
- DDDの戦術パターン詳解
- イベント駆動アーキテクチャとDDD
- AIペアプログラミング実践ガイド