依存性注入(DIコンテナ)
何をする手法か
依存性注入(DI)は、あるクラスが使う相手を、自分で作らずに外から渡してもらう設計です。
// 注入しない書き方 — 相手を自分で作っている
class OrderService {
private mailer = new SmtpMailer('smtp.example.com') // 固定されている
confirm(order: Order) { this.mailer.send(order.email, '注文を受け付けました') }
}
// 注入する書き方 — 相手を外から受け取る
class OrderService {
constructor(private mailer: Mailer) {} // 何が来るかは呼び出し側が決める
confirm(order: Order) { this.mailer.send(order.email, '注文を受け付けました') }
}
違いは1か所ですが、結果は大きく変わります。前者は SmtpMailer 以外を使えません。後者は同じ形(Mailer)を満たすものなら何でも渡せます。
テストが書けるようになる
前者を [[自動テスト]] にかけると、テストのたびに本物のメールが飛びます。ネットワークが無ければ失敗し、実行時間も伸びます。
後者なら、[[テストダブル(モック・スタブ)]] を渡せます。
const sent: string[] = []
const service = new OrderService({ send: (to, body) => sent.push(to) })
service.confirm(order)
expect(sent).toEqual(['a@example.com'])
「テストしにくい」と感じるコードの多くは、依存を自分で作っています。 DIは設計の理屈より先に、この実務的な効き目で理解するのが早いです。
依存の向きを逆転させる
もう一段の狙いは依存の向きの制御です。業務ロジックが具体的な技術(SMTP・特定のDB)に依存していると、技術を変えるたびに業務ロジックを触ることになります。
- 業務ロジック側が「こういう形のものが要る」とインターフェースを定義する
- 技術側がそれを実装する
- 結果として、技術側が業務ロジックに依存する(向きが逆転する)
この考え方は依存性逆転と呼ばれ、[[デザインパターン]] の Strategy などと組み合わせて使われます。
DIコンテナ
注入する相手が増えると、組み立てのコードが長くなります。
const db = new PgClient(url)
const repo = new OrderRepository(db)
const mailer = new SmtpMailer(host)
const service = new OrderService(repo, mailer) // 手で配線する
DIコンテナは、この配線を宣言から自動で行う道具です。多くの [[フレームワーク]](Spring・NestJS・ASP.NET Core など)が標準で備えています。
ただしコンテナはDIの本質ではありません。上のような手作業の配線でもDIは成立します。規模が小さいうちは手で配線するほうが、何がどこから来ているか追いやすくなります。
やりすぎの兆候
- すべてにインターフェースを作る — 実装が1つしかないものに抽象を挟むと、追う手間だけが増えます
- コンテナが何を注入したか追えない — 実行時に解決する仕組みは、静的に読めなくなります。設定の誤りが起動時まで分かりません
- 循環依存が出る — AがBを、BがAを必要とする状態は、責務の分け方が間違っているサインです。コンテナで解決せず設計を直します
判断は「差し替える理由があるか」です。テストで差し替える、環境で実装を変える、といった理由が無いなら、直接使って構いません。
関連技術とのつながり
- [[テストダブル(モック・スタブ)]] — 注入できるからこそ差し替えられる。DIの効き目が最も分かる場面
- [[自動テスト]] — 外部依存を持つコードをテスト可能にする前提条件
- [[デザインパターン]] — Strategy や Factory と組み合わせて使われる
- [[フレームワーク]] — DIコンテナを標準で備えるものが多い
- [[クリーンコード]] — 依存の向きを制御することは可読性と変更容易性に直結する
Q: 依存性注入がテストで効く理由はどれ?
- [ ] テストコードの記述量が減るから
- [x] 使う相手を外から渡せるため、テスト用の実装に差し替えられるから
- [ ] テストの実行順序を制御できるから
解説: 依存を自分で作っているコードは、本物の外部サービスを呼んでしまうためテストが不安定になります。
Q: 依存性逆転で起きることはどれ?
- [x] 業務ロジックがインターフェースを定義し、技術側がそれを実装する
- [ ] 業務ロジックが技術側のクラスを直接呼ぶ
- [ ] すべての依存が無くなる
解説: 向きが逆転することで、技術を差し替えても業務ロジックを触らずに済みます。
Q: DIコンテナについて正しい説明はどれ?
- [ ] コンテナが無いとDIはできない
- [x] 配線を自動化する道具であり、手で配線してもDIは成立する
- [ ] コンテナはテストを不要にする
解説: 規模が小さいうちは手で配線するほうが、何がどこから来ているか追いやすくなります。