ストーリー
高橋アーキテクトが、あなたのチームのプロジェクト構造を見せてくれた。
レイヤードアーキテクチャとは
最も広く使われているアーキテクチャパターンです。アプリケーションを水平な層に分割します。
┌─────────────────────────────────┐
│ 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分