ストーリー
関心の分離(Separation of Concerns)
関心の分離とは、システムの各部分が1つの責任だけを持つように分割することです。SOLID原則の「単一責任の原則」をシステム全体に拡張した考え方です。
3つの関心領域
┌──────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ドメイン │ │アプリケー│ │インフラ │ │
│ │ │ │ション │ │ストラクチャ│ │
│ │ ビジネス │ │ │ │ │ │
│ │ ルール │ │ ユースケース│ │ DB, API │ │
│ │ 最も安定 │ │ 仲介役 │ │ 最も不安定 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└──────────────────────────────────────────────┘
| 関心領域 | 役割 | 変更頻度 |
|---|---|---|
| ドメイン | ビジネスルールとエンティティ | 低い(ビジネスが変わらない限り不変) |
| アプリケーション | ユースケースの実行フロー | 中程度(機能追加時に変更) |
| インフラストラクチャ | DB、API、UI、フレームワーク | 高い(技術の入れ替え、DB変更など) |
// ドメイン: 注文のビジネスルール(最も安定)
class Order {
cancel(): void {
if (this.status !== OrderStatus.PENDING) {
throw new Error('確定済みの注文はキャンセルできません');
}
this.status = OrderStatus.CANCELLED;
}
}
// アプリケーション: ユースケースの流れ(中程度の安定性)
class CancelOrderUseCase {
async execute(orderId: string): Promise<void> {
const order = await this.orderRepo.findById(orderId);
order.cancel();
await this.orderRepo.save(order);
await this.notifier.notify(order.userId, 'キャンセル完了');
}
}
// インフラ: 具体的な技術実装(最も不安定)
class PostgresOrderRepository {
async findById(id: string): Promise<Order> {
const row = await prisma.order.findUnique({ where: { id } });
return Order.fromPersistence(row);
}
}
依存性の方向
安定依存の原則
安定しているものに依存し、不安定なものには依存しない。
レイヤードアーキテクチャの依存方向(問題あり):
ドメイン → インフラ
(安定) → (不安定) ← ビジネスロジックがDBに依存
理想的な依存方向:
インフラ → ドメイン
(不安定) → (安定) ← DBがビジネスロジックに合わせる
依存性逆転の原則(DIP)をアーキテクチャに適用
Month1で学んだDIPを、クラスレベルではなくアーキテクチャレベルで適用します。
// Step 1: ドメイン層にインターフェース(Port)を定義
// domain/ports/out/OrderRepository.ts
interface OrderRepository {
findById(id: string): Promise<Order>;
save(order: Order): Promise<void>;
}
// Step 2: ドメイン層はインターフェースにのみ依存
// application/CancelOrderUseCase.ts
class CancelOrderUseCase {
constructor(private orderRepo: OrderRepository) {} // インターフェースに依存
async execute(orderId: string): Promise<void> {
const order = await this.orderRepo.findById(orderId);
order.cancel();
await this.orderRepo.save(order);
}
}
// Step 3: インフラ層がインターフェースを実装
// adapters/out/PostgresOrderRepository.ts
class PostgresOrderRepository implements OrderRepository {
async findById(id: string): Promise<Order> {
// Prismaを使った具体的な実装
const row = await prisma.order.findUnique({ where: { id } });
return Order.fromPersistence(row);
}
async save(order: Order): Promise<void> {
await prisma.order.update({
where: { id: order.id },
data: order.toPersistence(),
});
}
}
依存の方向が変わった
Before(レイヤード):
CancelOrderUseCase → PostgresOrderRepository → Prisma → DB
「ビジネスロジックがDBに依存」
After(依存性逆転):
CancelOrderUseCase → OrderRepository(interface)
↑
PostgresOrderRepository ────┘(インフラがインターフェースに依存)
「DBがビジネスロジックに合わせる」
境界を守るとは
依存性の方向を正しく制御することで、各関心領域の「境界」が守られます。
┌──────────────────────────────────────────┐
│ 外側(インフラストラクチャ) │
│ ┌──────────────────────────────────┐ │
│ │ 中間(アプリケーション) │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ 内側(ドメイン) │ │ │
│ │ │ │ │ │
│ │ │ ビジネスルール │ │ │
│ │ │ エンティティ │ │ │
│ │ │ 値オブジェクト │ │ │
│ │ └──────────────────────────┘ │ │
│ │ ユースケース │ │
│ └──────────────────────────────────┘ │
│ DB, API, UI, Framework │
└──────────────────────────────────────────┘
矢印の方向: 外側 → 内側(常に内側に向かう)
ルール: 依存は常に外側から内側に向かう。内側は外側を知らない。
まとめ
| ポイント | 内容 |
|---|---|
| 関心の分離 | ドメイン、アプリケーション、インフラの3領域に分割 |
| 安定依存の原則 | 安定しているもの(ドメイン)に依存する |
| 依存性逆転 | インターフェースを使って依存の方向を逆転させる |
| 境界を守る | 依存は常に外側から内側に向かう |
チェックリスト
- 3つの関心領域とその変更頻度を説明できる
- 依存性の方向が逆転する仕組みを理解した
- インターフェースが境界を守る役割を果たすことを理解した
- 「内側は外側を知らない」ルールを把握した
次のステップへ
次は「アーキテクチャパターンの全体像」を学びます。ヘキサゴナル、クリーン、オニオンなど、さまざまなアーキテクチャパターンがどのような関係にあるのかを俯瞰しましょう。
推定読了時間: 25分