LESSON 25分

ストーリー

高橋アーキテクトが、あなたのチームのプロジェクト構造を見せてくれた。

高橋アーキテクト
このプロジェクトは、典型的なレイヤードアーキテクチャだね。Controller → Service → Repository。多くのチュートリアルで紹介される構造だ
あなた
はい、フレームワークのガイド通りに作りました
高橋アーキテクト
ここまでは良い。でも、この構造を続けていくと、やがて壁にぶつかる。その壁を理解することが、次のアーキテクチャを学ぶ出発点になる

レイヤードアーキテクチャとは

最も広く使われているアーキテクチャパターンです。アプリケーションを水平な層に分割します。

┌─────────────────────────────────┐
│    Presentation Layer           │  ← Controller, View
│    (プレゼンテーション層)          │
├─────────────────────────────────┤
│    Business Logic Layer         │  ← Service
│    (ビジネスロジック層)            │
├─────────────────────────────────┤
│    Data Access Layer            │  ← Repository
│    (データアクセス層)              │
├─────────────────────────────────┤
│    Database                     │  ← DB, 外部API
│    (インフラストラクチャ)           │
└─────────────────────────────────┘
// 典型的なレイヤードアーキテクチャの例
// Controller層
class OrderController {
  constructor(private orderService: OrderService) {}

  async createOrder(req: Request, res: Response) {
    const order = await this.orderService.create(req.body);
    res.json(order);
  }
}

// Service層
class OrderService {
  constructor(private orderRepository: OrderRepository) {}

  async create(data: CreateOrderDto) {
    // ビジネスロジック
    const order = new Order(data);
    return this.orderRepository.save(order);
  }
}

// Repository層
class OrderRepository {
  async save(order: Order): Promise<Order> {
    return prisma.order.create({ data: order });
  }
}

レイヤードアーキテクチャの利点

利点説明
理解しやすい層が明確で、新しいメンバーが把握しやすい
責任が分離される各層の役割が決まっている
フレームワークとの親和性Express, NestJS, Spring Bootなどが自然に対応
学習コストが低いチュートリアルや参考実装が豊富

レイヤードアーキテクチャの限界

限界1: 依存性の方向が下向きに固定される

Controller → Service → Repository → Database

                              ビジネスロジックが
                              DBに依存してしまう
// Service層がRepository(=インフラ)の具体実装に依存
class OrderService {
  constructor(private orderRepository: OrderRepository) {}

  async calculateTotal(orderId: string): Promise<number> {
    // OrderRepositoryがPrismaを使うことを前提としている
    const order = await this.orderRepository.findById(orderId);
    // ビジネスロジックがインフラの都合に引きずられる
    return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }
}

ビジネスロジックが最も重要なのに、それがデータベースの都合に依存してしまいます。

限界2: 層を貫通するショートカットが生まれる

Controller ──→ Service ──→ Repository
    │                         ↑
    └─────────────────────────┘
    「Serviceを通すまでもないから直接...」
// やがて生まれるショートカット
class OrderController {
  constructor(
    private orderService: OrderService,
    private orderRepository: OrderRepository // 直接Repositoryを使い始める
  ) {}

  async getOrder(req: Request, res: Response) {
    // 「単純な取得だからServiceを経由しなくていいよね」
    const order = await this.orderRepository.findById(req.params.id);
    res.json(order);
  }
}

限界3: テストの困難さ

// Repository層のテストにはDBが必要
class OrderRepository {
  async findById(id: string): Promise<Order> {
    return prisma.order.findUnique({ where: { id } }); // Prisma = DB必須
  }
}

// Service層のテストにもDBが間接的に必要になる
// モックを書こうにも、Repositoryの具体的なクラスに依存している

限界4: ドメインモデルの貧血化

// ビジネスロジックがServiceに集中し、Entityはただのデータ入れ物に
class Order {
  id: string;
  items: OrderItem[];
  status: string;
  totalAmount: number;
  // メソッドがない = 貧血ドメインモデル(Anemic Domain Model)
}

class OrderService {
  // 本来Orderが持つべきロジックがServiceにある
  async cancel(orderId: string) {
    const order = await this.repo.findById(orderId);
    if (order.status !== 'pending') throw new Error('Cannot cancel');
    order.status = 'cancelled';
    await this.repo.save(order);
  }
}

限界のまとめ

限界影響
下向き依存の固定ビジネスロジックがインフラに依存する
ショートカット層の境界が崩壊し、スパゲッティ化する
テスト困難外部依存なしにテストできない
貧血モデルドメインロジックがServiceに分散する

「では、どうすればいいのか?」

高橋アーキテクトは問いかけた。

「ビジネスロジックが最も重要なのに、それが最も不安定なインフラ層に依存している。この依存の方向を逆転させたらどうなる? それが次に学ぶ”関心の分離と依存性の方向”のテーマだ」


まとめ

ポイント内容
レイヤードアーキテクチャ理解しやすく、広く使われている
限界依存が下向き固定、ショートカット発生、テスト困難、貧血モデル
根本原因ビジネスロジックがインフラに依存してしまう
解決の方向依存の方向を逆転させる

チェックリスト

  • レイヤードアーキテクチャの構造を説明できる
  • 4つの限界をそれぞれ理解した
  • 貧血ドメインモデルの問題点を把握した
  • 依存の方向が問題の根本原因であることを理解した

次のステップへ

次は「関心の分離と依存性の方向」を学びます。依存性逆転の原則をアーキテクチャレベルで適用する方法を理解しましょう。


推定読了時間: 25分