Laravel での DDD アーキテクチャ — 4 層構成と依存性逆転
ここからは第2部として、第1部で学んだDDDの概念をLaravelでどのように実装するかを解説します。
本章からLaravel固有の機能(Controller、Eloquent、サービスプロバイダなど)が登場します。これらの基礎はChapter 2「Laravel API開発の基礎」で解説していますので、必要に応じて参照してください。
アーキテクチャの比較
従来のLaravel構造
ビジネスロジックが Controller と Model に散在します。
DDD + クリーンアーキテクチャ
クリーンアーキテクチャは、Robert C. Martin(通称 "Uncle Bob")が提唱した設計思想で、ドメインロジックを中心に据え、フレームワークやデータベースといった外部技術から独立させることを目指します。
Robert C. Martinの著書「Clean Architecture」で提唱されたアーキテクチャパターンです。本質的な考え方は、ビジネスロジック(ドメイン層)を中心に据え、技術的な詳細(フレームワーク、DB、UI)を外側に配置することです。これにより、ビジネスルールは技術の変更に影響されず、長期的に保守しやすいシステムを構築できます。
本書が採用する 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への移行など、永続化方法の変更がドメインに影響しない |
なぜこの分離が重要なのか
-
変更の影響範囲を限定
- UI変更がビジネスロジックに影響しない
- データベース変更がビジネスロジックに影響しない
- フレームワーク変更がビジネスロジックに影響しない
-
テスト容易性の向上
- ドメイン層は外部依存なしでテスト可能
- モックを使った高速なユニットテスト
- データベース不要なビジネスロジックのテスト
-
理解しやすいコード
- 各層の責務が明確で、どこに何を書くべきかが分かる
- ビジネスロジックがドメイン層に集約される
- 新しいメンバーでも迷わず開発できる
依存関係の方向
依存性逆転の原則(DIP: Dependency Inversion Principle)
重要: インフラ層はドメイン層に依存します(依存性逆転の原則)。ドメイン層がインフラ層に依存することはありません。
SOLID原則の「D」に該当する、オブジェクト指向設計の重要な原則です。
原則の内容:
- 上位レベルのモジュール(ドメイン層)は、下位レベルのモジュール(インフラ層)に依存してはならない。両方とも抽象(インターフェース)に依存すべき
- 抽象は詳細に依存してはならない。詳細(実装)が抽象に依存すべき
平たく言うと: 「実装ではなく、インターフェース(契約)に依存しましょう」ということです。
なぜインフラ層がドメイン層に依存するのか?
通常のレイヤー構造では「上から下へ」依存します(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
{
// ビジネスロジック
}
この設計の利点
-
ドメイン層が技術的詳細から独立
// ドメイン層のテスト: DBなしで高速にテスト可能$order = Order::create($orderId, $address);$order->addItem(...);// ビジネスロジックのみをテスト -
実装の切り替えが容易
// Eloquentから別のORMへ変更しても、ドメイン層は無変更final class DoctrineOrderRepository implements OrderRepositoryInterface{// Doctrine ORMを使った実装} -
依存関係の流れ 従来の設計(悪い例)— ビジネスロジックが 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
各層の知識と責務
| 層 | 知っていること | 知らないこと |
|---|---|---|
| Controller | HTTPリクエスト/レスポンス形式 | ビジネスルール、テーブル構造 |
| UseCase | ドメインオブジェクトの使い方 | HTTPリクエスト形式、テーブル構造 |
| Domain Entity | ビジネスルール | 永続化方法、フレームワーク |
| Repository | テーブル構造、Eloquent | HTTPリクエスト形式 |
Eloquent Modelとドメインエンティティの違い
| 特性 | Domain Entity | Eloquent Model |
|---|---|---|
| 責務 | ビジネスルールの表現 | データベースとのマッピング |
| ビジネスルール | 持つ | 持たない |
| DB操作 | 持たない | 持つ(save, findなど) |
| テスト容易性 | 高い(DBなしでテスト可能) | 低い(DBが必要) |
なぜ分離するのか
- 関心の分離: ビジネスロジックと永続化ロジックを分離
- テスト容易性: ドメインエンティティはDBなしでテスト可能
- 柔軟性: 永続化方法を変更してもドメインに影響しない
- ビジネスルールの明確化: ドメインエンティティにビジネスルールが集約される
参考資料
クリーンアーキテクチャとDDDについてさらに学びたい方は、以下のリソースを参照してください。
クリーンアーキテクチャ
- Robert C. Martin「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(書籍)
- クリーンアーキテクチャの原典。アーキテクチャ設計の原則を包括的に学べます
- The Clean Architecture(記事)
- Uncle Bobによるクリーンアーキテクチャの解説記事
SOLID原則
- 依存性逆転の原則(DIP) は、SOLID原則の「D」に該当します
- クリーンアーキテクチャを理解する上で、SOLID原則の理解は必須です
次のチャプターでは、アプリケーション層(ユースケース層)の設計について詳しく見ていきます。