依存の組み立て
このドキュメントの目的
依存関係をどこで・どうやって組み立てるかを理解することが目標です。
目次
誰が実装を選ぶのか
コアは選べない
┌─────────────────────────────────────────────┐
│ コアが実装を選べない理由 │
├────────────────────────── ───────────────────┤
│ │
│ 依存ルール: │
│ 「コアは外側を知らない」 │
│ │
│ つまり: │
│ ├── コアはどんな実装があるか知らない │
│ ├── PostgreSQL? MySQL? 知らない │
│ ├── Email? Slack? 知らない │
│ └── 選びようがない │
│ │
└─────────────────────────────────────────────┘
最も外側が選ぶ
┌─────────────────────────────────────────────┐
│ 実装を選ぶ場所 │
├─────────────────────────────────────────────┤
│ │
│ アプリケーションの「最も外側」 │
│ │
│ 具体的には: │
│ ├── main関数 │
│ ├── アプリケーションの起動処理 │
│ └── 設定ファイル + 初期化コード │
│ │
│ ここは全てを知っている │
│ → どの実装を使うか決められる │
│ │
└─────────────────────────────────────────────┘
Composition Root
Composition Rootとは
┌─────────────────────────────────────────────┐
│ Composition Root │
├─────────────────────────────────────────────┤
│ │
│ 「依存関係を組み立てる唯一の場所」 │
│ │
│ 役割: │
│ ├── どの実装を使うか決める │
│ ├── 依存関係を組み立てる │
│ └── コアに実装を渡す │
│ │
│ アプリケーションのエントリーポイント付近 │
│ │
└─────────────────────────────────────────────┘
なぜ一箇所に集めるのか
┌─────────────────────────────────────────────┐
│ 一箇所に集める理由 │
├─────────────────────────────────────────────┤
│ │
│ バラバラだと: │
│ ├── どこで何が決まっているかわからない │
│ ├── 設定を変えたいとき探し回る │
│ └── テスト時の差し替えが困難 │
│ │
│ 一箇所だと: │
│ ├── 構成が一目でわかる │
│ ├── 変更箇所が明確 │
│ └── テスト時の差し替えが容易 │
│ │
└─────────────────────────────────────────────┘
Composition Rootの例
┌─────────────────────────────────────────────┐
│ 起動時の組み立て │
├─────────────────────────────────────────────┤
│ │
│ main() { │
│ // 1. プラグイン(実装)を選ぶ │
│ userRepository = new PostgreSQLUserRepo() │
│ notificationSender = new EmailSender() │
│ paymentGateway = new StripeGateway() │
│ │
│ // 2. コアに渡す │
│ userService = new UserService( │
│ userRepository, │
│ notificationSender │
│ ) │
│ │
│ // 3. アプリケーション起動 │
│ app.start(userService, paymentGateway) │
│ } │
│ │
│ コアは「何を渡されたか」知らない │
│ インターフェースとして受け取るだけ │
│ │
└─────────────────────────────────────────────┘
組み立ての方法
方法1: 直接インスタンス化
┌─────────────────────────────────────────────┐
│ 最もシンプルな方法 │
├─────────────────────────────────────────────┤
│ │
│ コードで直接newする │
│ │
│ repository = new PostgreSQLUserRepository() │
│ │
│ メリット: │
│ ├── シンプルで理解しやすい │
│ ├── コンパイル時に型チェック │
│ └── IDEの補完が効く │
│ │
│ デメリット: │
│ └── 切り替えにはコード変更が必要 │
│ │
└────────────────────────────────────────── ───┘
方法2: 設定ファイル + ファクトリ
┌─────────────────────────────────────────────┐
│ 設定で切り替える方法 │
├─────────────────────────────────────────────┤
│ │
│ 設定ファイル: │
│ database.type = postgresql │
│ notification.type = email │
│ │
│ ファクトリで生成: │
│ repository = RepositoryFactory.create( │
│ config.database.type │
│ ) │
│ │
│ メリット: │
│ └── コード変更なしで切り替え可能 │
│ │
│ デメリット: │
│ └── 設定ミスが実行時エラーになる │
│ │
└─────────────────────────────────────────────┘
方法3: DIコンテナ
┌─────────────────────────────────────────────┐
│ DIコンテナとは │
├─────────────────────────────────────────────┤
│ │
│ 「インターフェースと実装の対応表」を持ち │
│ 依存関係を自動で組み立てる仕組み │
│ │
└─────────────────────────────────────────────┘
Step 1: 登録(対応表を作る)
container.register(UserRepository, PostgreSQLUserRepository)
container.register(NotificationSender, EmailSender)
container.register(UserService, UserService)
対応表:
┌─────────────────────┬─────────────────────────┐
│ インターフェース │ 実装 │
├─────────────────────┼─────────────────────────┤
│ UserRepository │ PostgreSQLUserRepository │
│ NotificationSender │ EmailSender │
│ UserService │ UserService │
└─────────────────────┴─────────────────────────┘
Step 2: 解決(自動で組み立てる)
userService = container.resolve(UserService)
コンテナの動き:
1. UserServiceを作りたい
2. UserServiceのコンストラクタを見る
→ UserRepository と NotificationSender が必要
3. 対応表を見て実装を探す
→ PostgreSQLUserRepository, EmailSender
4. それぞれをインスタンス化
5. UserServiceに渡して完成
手動で書くとこうなる部分を自動化:
userRepo = new PostgreSQLUserRepository()
sender = new EmailSender()
userService = new UserService(userRepo, sender)
メリット・デメリット
メリット:
├── 依存が深くても自動で解決
│ A → B → C → D のような連鎖も対応
├── ライフサイクル管理
│ シングルトン、リクエストごとなど
└── 登録を変えるだけで実装を切り替え
デメリット:
├── 「魔法」になりがち
│ 何がどう組み立てられているか見えにくい
├── 実行時エラー
│ 登録忘れがコンパイル時に検出できない
└── 学習コスト
DIコンテナの詳細は IoCとDI を参照
どれを選ぶか
┌─────────────────────────────────────────────┐
│ 選択の指針 │
├─────────────────────────────────────────────┤
│ │
│ 小規模・シンプル: │
│ └── 直接インスタンス化で十分 │
│ │
│ 環境ごとに切り替えたい: │
│ └── 設定ファイル + ファクトリ │
│ │
│ 大規模・依存関係が複雑: │
│ └── DIコンテナを検討 │
│ │
│ どれを選んでも: │
│ 「Composition Rootに集める」原則は同じ │
│ │
└─────────────────────────────────────────────┘
テスト時の差し替え
なぜ差し替えが必要か
┌─────────────────────────────────────────────┐
│ テスト時の実装 │
├─────────────────────────────────────────────┤
│ │
│ 本番: │
│ ├── PostgreSQLUserRepository │
│ ├── StripeGateway │
│ └── EmailSender │
│ │
│ テスト: │
│ ├── InMemoryUserRepository │
│ ├── MockGateway │
│ └── FakeSender │
│ │
│ 外部依存なしでテストできる │
│ │
└─────────────────────────────────────────────┘
テスト用Composition Root
┌─────────────────────────────────────────────┐
│ テスト用の組み立て │
├─────────────────────────────────────────────┤
│ │
│ setupTest() { │
│ // テスト用の実装を使う │
│ userRepository = new InMemoryUserRepo() │
│ notificationSender = new FakeSender() │
│ │
│ userService = new UserService( │
│ userRepository, │
│ notificationSender │
│ ) │
│ } │
│ │
│ コアのコードは本番と同じ │
│ プラグインだけが違う │
│ │
└─────────────────────────────────────────────┘
まとめ
依存組み立ての原則
┌─────────────────────────────────────────────┐
│ 覚えておくべきこと │
├─────────────────────────────────────────────┤
│ │
│ 1. コアは実装を選べない(知らないから) │
│ │
│ 2. 最も外側(Composition Root)で選ぶ │
│ │
│ 3. 組み立ては一箇所に集める │
│ │
│ 4. テスト時は別の実装を渡すだけ │
│ │
└─────────────────────────────────────────────┘
組み立て方法の選択
・直接インスタンス化: シンプルで十分な場合
・設定 + ファクトリ: 環境ごとに切り替えたい場合
・DIコンテナ: 複雑な依存関係がある場合
どれでも「Composition Root」の考え方は同じ
次のステップ
- プラグイン機構 で動的な拡張の仕組みを学ぶ