LESSON 25分

ストーリー

高橋アーキテクト
ヘキサゴナルアーキテクチャを学んだことで、PortとAdapterの概念は身についたね
高橋アーキテクト
次はRobert C. Martin — 通称Uncle Bobが提唱したクリーンアーキテクチャだ。ヘキサゴナルの考え方を発展させ、より明確な4層構造と”依存性ルール”を定義したものだ
あなた
ヘキサゴナルとは何が違うんですか?
高橋アーキテクト
考え方の根本は同じだ。ただ、クリーンアーキテクチャはUse Caseを独立した層として明確に位置づけ、各層の責任をより厳密に定義している

クリーンアーキテクチャの4層

┌───────────────────────────────────────────┐
│  Layer 4: Frameworks & Drivers            │
│  Express, Prisma, SendGrid, AWS SDK       │
│  ┌─────────────────────────────────────┐  │
│  │  Layer 3: Interface Adapters        │  │
│  │  Controller, Presenter, Gateway     │  │
│  │  ┌───────────────────────────────┐  │  │
│  │  │  Layer 2: Application         │  │  │
│  │  │  Business Rules (Use Cases)   │  │  │
│  │  │  ┌─────────────────────────┐  │  │  │
│  │  │  │  Layer 1: Enterprise    │  │  │  │
│  │  │  │  Business Rules         │  │  │  │
│  │  │  │  (Entities)             │  │  │  │
│  │  │  └─────────────────────────┘  │  │  │
│  │  └───────────────────────────────┘  │  │
│  └─────────────────────────────────────┘  │
└───────────────────────────────────────────┘

依存性ルール: 矢印は常に内側に向かう
Layer 4 → Layer 3 → Layer 2 → Layer 1

各層の役割

Layer 1: Enterprise Business Rules(エンティティ層)

企業全体のビジネスルールを表現します。最も安定した層です。

// entities/Order.ts
class Order {
  private _status: OrderStatus;

  constructor(
    readonly id: OrderId,
    readonly customerId: string,
    private _items: OrderItem[],
    status: OrderStatus
  ) {
    this._status = status;
  }

  get totalAmount(): Money {
    return this._items.reduce(
      (sum, item) => sum.add(item.subtotal),
      Money.zero('JPY')
    );
  }

  canBeCancelled(): boolean {
    return this._status === OrderStatus.PENDING
        || this._status === OrderStatus.CONFIRMED;
  }

  cancel(): void {
    if (!this.canBeCancelled()) {
      throw new DomainError('この注文はキャンセルできません');
    }
    this._status = OrderStatus.CANCELLED;
  }
}

Layer 2: Application Business Rules(ユースケース層)

アプリケーション固有のビジネスルール(ユースケース)を実装します。

// use-cases/CancelOrderUseCase.ts
class CancelOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private paymentGateway: PaymentGateway,
    private notifier: NotificationSender
  ) {}

  async execute(input: CancelOrderInput): Promise<CancelOrderOutput> {
    const order = await this.orderRepo.findById(
      OrderId.fromString(input.orderId)
    );
    if (!order) throw new NotFoundError('注文が見つかりません');

    order.cancel(); // Layer 1のビジネスルールを呼び出す

    await this.orderRepo.save(order);
    await this.paymentGateway.refund(input.orderId, order.totalAmount);
    await this.notifier.sendOrderConfirmation(order);

    return { orderId: order.id.value, status: 'CANCELLED' };
  }
}

Layer 3: Interface Adapters(インターフェースアダプター層)

データの変換を行います。外部のフォーマットと内部のフォーマットを橋渡しします。

// adapters/controllers/OrderController.ts
class OrderController {
  constructor(private cancelOrder: CancelOrderUseCase) {}

  async cancel(req: Request, res: Response): Promise<void> {
    // HTTPリクエスト → Use Case入力 への変換
    const input: CancelOrderInput = {
      orderId: req.params.id,
      reason: req.body.reason,
    };

    const output = await this.cancelOrder.execute(input);

    // Use Case出力 → HTTPレスポンス への変換
    res.json({
      orderId: output.orderId,
      status: output.status,
      message: 'Order cancelled successfully',
    });
  }
}

// adapters/gateways/PrismaOrderRepository.ts
class PrismaOrderRepository implements OrderRepository {
  async findById(id: OrderId): Promise<Order | null> {
    const row = await prisma.order.findUnique({ where: { id: id.value } });
    // DBレコード → ドメインエンティティ への変換
    return row ? this.toDomain(row) : null;
  }
}

Layer 4: Frameworks & Drivers(フレームワーク・ドライバー層)

最も外側の層。フレームワーク、ライブラリ、DBドライバーなどの具体的な技術です。

// frameworks/express/app.ts
import express from 'express';

const app = express();
app.use(express.json());

// ルーティング設定(フレームワーク固有のコード)
app.delete('/api/orders/:id', (req, res) =>
  orderController.cancel(req, res)
);

// frameworks/prisma/schema.prisma
// model Order {
//   id        String @id
//   status    String
//   ...
// }

依存性ルール(The Dependency Rule)

内側の層は外側の層について何も知らない。

Layer知っていること知らないこと
Layer 1 (Entity)ビジネスルールのみUse Case、Controller、DB
Layer 2 (Use Case)Entity、Port IFController、Prisma、Express
Layer 3 (Adapter)Use Case、Entityフレームワーク固有の詳細
Layer 4 (FW)すべて-
// Layer 2のUse Caseが知っているのは:
// - Layer 1のEntity(Order, Money, OrderId)
// - Layer 2で定義されたPort(OrderRepository interface)
// 知らないのは:
// - Prisma(Layer 4)
// - Express(Layer 4)
// - Controller(Layer 3)

ヘキサゴナルとの対応

ヘキサゴナルクリーン役割
DomainEntity (Layer 1)ビジネスルール
Port (in)Use Case Input (Layer 2)入力インターフェース
Port (out)Use Case Output IF (Layer 2)出力インターフェース
Adapter (in)Controller (Layer 3)入力変換
Adapter (out)Gateway (Layer 3)出力変換
-Framework (Layer 4)技術詳細

まとめ

ポイント内容
4層構造Entity → Use Case → Adapter → Framework
依存性ルール内側は外側を知らない
Layer 1企業全体のビジネスルール(最も安定)
Layer 2アプリケーション固有のユースケース
Layer 3データ変換(内部↔外部)
Layer 4フレームワーク・ドライバー(最も不安定)

チェックリスト

  • 4層それぞれの役割を説明できる
  • 依存性ルールを理解した
  • ヘキサゴナルとの対応関係を把握した
  • 各層に配置すべきコードを判断できる

次のステップへ

次は「Use Caseの設計」を学びます。クリーンアーキテクチャの核となるUse Case層を、どのように設計するかを詳しく見ていきましょう。


推定読了時間: 25分