メインコンテンツまでスキップ

なぜ DDD か — Fat Controller / Fat Model の限界

DDDの歴史的背景

ドメイン駆動設計(DDD)は、2003年にEric Evans氏が著書「Domain-Driven Design: Tackling Complexity in the Heart of Software」で提唱した設計手法です。この本は、複雑なビジネスロジックを持つソフトウェアの設計において、ドメインモデルを中心に据える重要性を説いています。

注記

DDDは20年以上の歴史を持つ設計思想ですが、その本質的な価値は今なお色褪せることなく、現代のソフトウェア開発においても広く採用されています。

従来のLaravelの課題

Laravelの標準的なMVCアーキテクチャは、シンプルなCRUD操作には適しています。しかし、複雑なビジネスロジックが増加すると、以下の問題が発生しがちです。

具体例:Fat Controllerの問題

// 問題のあるController例
class OrderController extends Controller
{
public function store(Request $request)
{
// バリデーション
$validated = $request->validate([...]);

// ビジネスロジックがControllerに散らばる
$order = new Order($validated);

if ($order->total > 10000) {
$order->discount = $order->total * 0.1; // 割引計算
}

$order->save();

// 在庫チェック
foreach ($order->items as $item) {
$inventory = Inventory::find($item->product_id);
if ($inventory->stock < $item->quantity) {
throw new Exception('在庫不足');
}
$inventory->stock -= $item->quantity;
$inventory->save();
}

// メール送信
Mail::to($order->user)->send(new OrderConfirmed($order));

return response()->json($order);
}
}

このコードには以下の問題があります。

問題説明
責任の集中Controller が「バリデーション」「ビジネスロジック」「永続化」「通知」すべてを担当
テスト困難DB接続やメール送信なしにはテストできない
再利用不可同じロジックが他の場所で必要になったらコピペになる
変更困難割引ルール変更時、Controllerを修正する必要がある

Fat Modelの問題

「ControllerをスリムにしてModelに処理を移そう」というアプローチも一般的ですが、これも新たな問題を生みます。

// Fat Modelの例
class Order extends Model
{
public function calculateDiscount()
{
if ($this->total > 10000) {
return $this->total * 0.1;
}
return 0;
}

public function checkInventory()
{
foreach ($this->items as $item) {
$inventory = Inventory::find($item->product_id);
if ($inventory->stock < $item->quantity) {
throw new Exception('在庫不足');
}
}
}

public function sendConfirmationEmail()
{
Mail::to($this->user)->send(new OrderConfirmed($this));
}

// その他、注文に関連するメソッドが増え続ける...
}

このFat Modelアプローチにも以下の問題があります。

問題説明
単一責任原則の違反Modelが「ドメインロジック」「永続化」「外部サービス連携」を担当
Eloquent依存ビジネスロジックがEloquentに強く依存し、テストや再利用が困難
肥大化1つのModelに数千行のコードが集まり、保守性が低下

ビジネスロジックが散在することの具体的なデメリット

ビジネスロジックがController、Model、Service、Helperなどに散在すると、以下の深刻な問題が発生します。

1. ビジネスルールの把握が困難

「注文の割引ルールはどこに書いてあるのか?」を調べるために、複数のファイルを横断的に読む必要があります。

2. 修正漏れのリスク

ビジネスルールが変更された際、修正箇所を見落とすリスクが高まります。

「10,000円以上で10%割引」→「15,000円以上で15%割引」に変更

修正箇所:
✓ OrderController.php (修正済み)
✓ CartService.php (修正済み)
✗ CheckoutHelper.php (修正漏れ!)
✓ ReportGenerator.php (修正済み)

# 1箇所の修正漏れが本番環境でのバグに

3. テストの網羅性確保が困難

同じビジネスルールが複数箇所に存在すると、すべての実装に対してテストを書く必要があります。

4. コードの重複

同じビジネスロジックが複数箇所にコピペされ、保守コストが増大します。

DDDを採用するメリット

DDDを採用すると、以下のメリットが得られます。

1. ビジネスルールの明確化

コードを読むだけで、ビジネスの仕組みが理解できるようになります。

// DDDのアプローチ:ビジネスルールがドメインオブジェクト(※)に集約
// ※ドメインオブジェクトについては次章で詳しく説明します
class Order
{
public function confirm(): void
{
if (!$this->status->canBeConfirmed()) {
throw new DomainException('この注文は確定できません');
}

if (empty($this->orderLines)) {
throw new DomainException('注文明細が空です');
}

$this->status = OrderStatus::CONFIRMED;
}
}

2. 変更への対応力向上

ビジネスルール変更の影響範囲を限定でき、安全な変更が可能になります。

3. テスタビリティの向上

ビジネスロジックが独立し、データベースなしでもドメインロジックのテストが可能になります。

// DBなしでテスト可能
public function test_確定済み注文はキャンセルできる(): void
{
$order = Order::create($orderId, $shippingAddress);
$order->addItem($lineId, $productId, 2, new Money(1000, 'JPY'));
$order->confirm();

$order->cancel();

$this->assertTrue($order->status()->isCancelled());
}

4. チーム間コミュニケーションの改善

開発者と業務担当者が同じ概念と言葉で議論できるようになります。

業務担当者: 「注文を確定するときに、明細が空だとエラーにしたい」

開発者: 「Order.confirm() メソッドに、明細が空かどうかのチェックを追加します」

# 同じ言葉(ユビキタス言語)で会話できる
# ※ユビキタス言語については次章で詳しく説明します

DDDの本質

DDDは「技術のための技術」ではありません。ビジネス価値を最大化するための設計思想です。

次のチャプターでは、DDDの基本概念を詳しく見ていきます。

参考資料

DDDについてさらに学びたい方は、以下の資料をご参照ください。

書籍

  • Eric Evans「Domain-Driven Design: Tackling Complexity in the Heart of Software」(2003) - DDD の原典
  • 成瀬允宣「ドメイン駆動設計入門」- 日本語で学べる実践的なDDD入門書

オンラインリソース