依存性逆転の原則
SOLID の D - 上位モジュールは下位モジュールに依存せず、両者とも抽象に依存すべきという設計原則
SOLID設計原則
依存性逆転の原則とは
依存性逆転の原則 (Dependency Inversion Principle, DIP) は、SOLID 原則の D にあたる設計原則で、Robert C. Martin が 1996 年に提唱した。
- 上位モジュールは下位モジュールに依存してはならない。両者とも抽象に依存すべきである
- 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである
Before / After
// ❌ DIP 違反: OrderService が DynamoDBRepo に直接依存
// DynamoDB を PostgreSQL に変更すると OrderService も変更が必要
class OrderService {
private repo = new DynamoDBOrderRepo(); // 具体的な実装に依存
async getOrder(id: string) { return this.repo.findById(id); }
}
// ✅ DIP 準拠: OrderService は抽象 (interface) に依存
// DynamoDB → PostgreSQL の変更は、アダプターの差し替えだけで済む
interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
}
class OrderService {
constructor(private readonly repo: OrderRepository) {}
async getOrder(id: string) { return this.repo.findById(id); }
}
// 具体的な実装は interface に依存する
class DynamoDBOrderRepo implements OrderRepository { /* ... */ }
class PostgresOrderRepo implements OrderRepository { /* ... */ }
依存の方向
DIP 違反 (依存が下向き):
OrderService → DynamoDBOrderRepo → DynamoDB
DIP 準拠 (依存が抽象に向く):
OrderService → OrderRepository (interface) ← DynamoDBOrderRepo
← PostgresOrderRepo
「逆転」の意味: 通常は上位が下位に依存するが、DIP では下位 (DynamoDBOrderRepo) が上位の定義した抽象 (OrderRepository) に依存する。依存の方向が逆転している。
DIP と DI の関係
| 概念 | 種類 | 役割 |
|---|---|---|
| DIP (依存性逆転の原則) | 設計原則 | 「何に依存すべきか」を定義 |
| DI (依存性注入) | 実装パターン | 「どう依存を渡すか」を実現 |
| IoC (制御の反転) | アーキテクチャ概念 | 「誰が依存を組み立てるか」を決定 |
DIP は「抽象に依存せよ」という原則、DI は「依存をコンストラクタで渡す」という実装手法だ。DIP を実現するために DI を使う。
Lambda での実践
// interface (上位モジュールが定義)
interface NotificationPort {
send(userId: string, message: string): Promise<void>;
}
// 本番: SES アダプター
class SESAdapter implements NotificationPort {
async send(userId: string, message: string) { await ses.sendEmail(/* ... */); }
}
// テスト: インメモリアダプター
class InMemoryAdapter implements NotificationPort {
sent: { userId: string; message: string }[] = [];
async send(userId: string, message: string) { this.sent.push({ userId, message }); }
}
DIP を適用すべきケース
- 外部サービス (DB、メール、API) との境界
- テスト時にモックに差し替えたい依存
- 将来的に実装が変わる可能性がある箇所
すべてのクラスに interface を定義する必要はない。変更の可能性が低い内部ロジックには過剰だ。
全体像を把握するには関連書籍も有用。