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

Laravel での DDD アーキテクチャ — 4 層構成と依存性逆転

ここからは第2部として、第1部で学んだDDDの概念をLaravelでどのように実装するかを解説します。

Laravelの基礎を復習したい方へ

本章からLaravel固有の機能(Controller、Eloquent、サービスプロバイダなど)が登場します。これらの基礎はChapter 2「Laravel API開発の基礎」で解説していますので、必要に応じて参照してください。

アーキテクチャの比較

従来のLaravel構造

ビジネスロジックが Controller と Model に散在します。

DDD + クリーンアーキテクチャ

クリーンアーキテクチャは、Robert C. Martin(通称 "Uncle Bob")が提唱した設計思想で、ドメインロジックを中心に据え、フレームワークやデータベースといった外部技術から独立させることを目指します。

クリーンアーキテクチャとは?

Robert C. Martinの著書「Clean Architecture」で提唱されたアーキテクチャパターンです。本質的な考え方は、ビジネスロジック(ドメイン層)を中心に据え、技術的な詳細(フレームワーク、DB、UI)を外側に配置することです。これにより、ビジネスルールは技術の変更に影響されず、長期的に保守しやすいシステムを構築できます。

参考: The Clean Architecture

用語について(Clean Architecture と Onion Architecture)

本書が採用する 4 層構成(ドメインを中心に据える形)は、厳密には Jeffrey Palermo の Onion Architecture に近いものです。Robert C. Martin の Clean Architecture は本来、同心円の 4 リング(Entities / Use Cases / Interface Adapters / Frameworks & Drivers)で表現されます。両者は「依存を内側(ドメイン)に向ける」という核心を共有しています。実務では区別せず「クリーンアーキテクチャ」と総称されることが多いため、本書もその慣習に従います。

各層の責務

各層は明確に役割が分かれており、この分離によって変更の影響範囲を局所化し、テストしやすいコードを実現します。

責務含まれるものなぜ重要か
プレゼンテーション層HTTPリクエスト/レスポンスの処理Controller, Request, Resource入力形式の変更に強い。REST APIからGraphQLへの移行など、インターフェースの変更がドメインに影響しない
アプリケーション層ユースケースの調整UseCase, Command業務フローの明確化。ビジネスプロセス全体を俯瞰でき、複数のドメインオブジェクトの調整を担当
ドメイン層ビジネスルールの表現Entity, Value Object, Repository Interfaceビジネスロジックの保護。技術的な詳細から完全に独立し、純粋なビジネスルールのみを持つ
インフラ層技術的な実装Repository Implementation, Eloquent Model技術選択の柔軟性。EloquentからDoctrine ORMへの移行など、永続化方法の変更がドメインに影響しない

なぜこの分離が重要なのか

  1. 変更の影響範囲を限定

    • UI変更がビジネスロジックに影響しない
    • データベース変更がビジネスロジックに影響しない
    • フレームワーク変更がビジネスロジックに影響しない
  2. テスト容易性の向上

    • ドメイン層は外部依存なしでテスト可能
    • モックを使った高速なユニットテスト
    • データベース不要なビジネスロジックのテスト
  3. 理解しやすいコード

    • 各層の責務が明確で、どこに何を書くべきかが分かる
    • ビジネスロジックがドメイン層に集約される
    • 新しいメンバーでも迷わず開発できる

依存関係の方向

依存性逆転の原則(DIP: Dependency Inversion Principle)

重要: インフラ層はドメイン層に依存します(依存性逆転の原則)。ドメイン層がインフラ層に依存することはありません。

依存性逆転の原則(DIP)とは?

SOLID原則の「D」に該当する、オブジェクト指向設計の重要な原則です。

原則の内容:

  1. 上位レベルのモジュール(ドメイン層)は、下位レベルのモジュール(インフラ層)に依存してはならない。両方とも抽象(インターフェース)に依存すべき
  2. 抽象は詳細に依存してはならない。詳細(実装)が抽象に依存すべき

平たく言うと: 「実装ではなく、インターフェース(契約)に依存しましょう」ということです。

なぜインフラ層がドメイン層に依存するのか?

通常のレイヤー構造では「上から下へ」依存します(Controller → Service → Repository実装)。しかしクリーンアーキテクチャでは、ドメイン層を中心に据えるため、インフラ層の依存を「逆転」させます。

具体例で理解する

// ドメイン層: インターフェースを定義(抽象)
namespace App\Domain\Order;

interface OrderRepositoryInterface
{
public function save(Order $order): void;
public function findById(OrderId $id): ?Order;
}
// インフラ層: インターフェースを実装(詳細)
namespace App\Infrastructure\Repository;

use App\Domain\Order\OrderRepositoryInterface; // ← ドメイン層に依存

final class EloquentOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
// Eloquentを使った実装
}
}
// ドメイン層: エンティティ
namespace App\Domain\Order;

// ドメイン層は「インターフェース」しか知らない
// Eloquentの存在を知らない(依存していない)
final class Order
{
// ビジネスロジック
}

この設計の利点

  1. ドメイン層が技術的詳細から独立

    // ドメイン層のテスト: DBなしで高速にテスト可能
    $order = Order::create($orderId, $address);
    $order->addItem(...);
    // ビジネスロジックのみをテスト
  2. 実装の切り替えが容易

    // Eloquentから別のORMへ変更しても、ドメイン層は無変更
    final class DoctrineOrderRepository implements OrderRepositoryInterface
    {
    // Doctrine ORMを使った実装
    }
  3. 依存関係の流れ 従来の設計(悪い例)— ビジネスロジックが Eloquent に強く依存します。

DIP 適用後(良い例)— ドメインがインフラから完全に独立します。

ディレクトリ構成

app/
├── Domain/ # ドメイン層
│ ├── Order/
│ │ ├── Order.php # 集約ルート(エンティティ)
│ │ ├── OrderId.php # 識別子(値オブジェクト)
│ │ ├── OrderLine.php # エンティティ
│ │ ├── OrderLineId.php # 識別子(値オブジェクト)
│ │ ├── OrderStatus.php # 列挙型
│ │ ├── ProductId.php # 識別子(値オブジェクト)
│ │ ├── ShippingAddress.php # 値オブジェクト
│ │ └── OrderRepositoryInterface.php # リポジトリインターフェース
│ │
│ └── Shared/
│ └── Money.php # 共有の値オブジェクト

├── Application/ # アプリケーション層
│ └── UseCase/
│ └── Order/
│ ├── CreateOrderUseCase.php
│ ├── CreateOrderCommand.php
│ └── ConfirmOrderUseCase.php

├── Infrastructure/ # インフラ層
│ ├── Eloquent/
│ │ ├── OrderModel.php # Eloquent Model
│ │ └── OrderLineModel.php # Eloquent Model
│ ├── Repository/
│ │ └── EloquentOrderRepository.php
│ └── Provider/
│ └── RepositoryServiceProvider.php

└── Http/ # プレゼンテーション層
├── Controllers/
│ └── OrderController.php
└── Requests/
└── CreateOrderRequest.php

各層の知識と責務

知っていること知らないこと
ControllerHTTPリクエスト/レスポンス形式ビジネスルール、テーブル構造
UseCaseドメインオブジェクトの使い方HTTPリクエスト形式、テーブル構造
Domain Entityビジネスルール永続化方法、フレームワーク
Repositoryテーブル構造、EloquentHTTPリクエスト形式

Eloquent Modelとドメインエンティティの違い

特性Domain EntityEloquent Model
責務ビジネスルールの表現データベースとのマッピング
ビジネスルール持つ持たない
DB操作持たない持つ(save, findなど)
テスト容易性高い(DBなしでテスト可能)低い(DBが必要)

なぜ分離するのか

  1. 関心の分離: ビジネスロジックと永続化ロジックを分離
  2. テスト容易性: ドメインエンティティはDBなしでテスト可能
  3. 柔軟性: 永続化方法を変更してもドメインに影響しない
  4. ビジネスルールの明確化: ドメインエンティティにビジネスルールが集約される

参考資料

クリーンアーキテクチャとDDDについてさらに学びたい方は、以下のリソースを参照してください。

クリーンアーキテクチャ

  • Robert C. Martin「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(書籍)
    • クリーンアーキテクチャの原典。アーキテクチャ設計の原則を包括的に学べます
  • The Clean Architecture(記事)
    • Uncle Bobによるクリーンアーキテクチャの解説記事

SOLID原則

  • 依存性逆転の原則(DIP) は、SOLID原則の「D」に該当します
  • クリーンアーキテクチャを理解する上で、SOLID原則の理解は必須です

次のチャプターでは、アプリケーション層(ユースケース層)の設計について詳しく見ていきます。