Inversion of Control ─ 基盤主導 vs 利用者主導
API の抽象度を決めたら、次は 制御の所在 を決める。利用者が基盤を呼ぶのか、基盤が利用者を呼ぶのか。これは Library と Framework の本質的違い であり、共通基盤の設計で最も影響の大きい判断の一つ。
Library vs Framework
Library
利用者が library を呼ぶ。制御は利用者にある。
// 利用者のコード
import { sendEmail } from '@platform/notify';
// 利用者がいつ呼ぶか決める
async function onUserSignedUp(user: User) {
await sendEmail({ to: user.email, subject: 'Welcome', body: '...' });
}
利用者は自分の好きなタイミングで library を呼ぶ。library は受動的。
Framework
Framework が利用者を呼ぶ。制御は framework にある。
// 利用者は callback を登録するだけ
import { onSignUp } from '@platform/notify-framework';
@onSignUp()
sendWelcome(user: User) {
return { to: user.email, subject: 'Welcome', body: '...' };
}
// → framework が SignUp イベントを検知し、利用者の関数を呼ぶ
利用者は 拡張点(hook) に処理を書く。いつ呼ばれるかは framework が決める。
“Don’t call us, we’ll call you”
Inversion of Control(IoC)の原典は Ralph E. Johnson と Brian Foote の 1988 年の論文 “Designing Reusable Classes”。Hollywood Principle とも呼ばれる:
Don’t call us, we’ll call you.
「こちらから連絡しないで、必要なときにこちらから連絡する」── ハリウッドのオーディションでよく言われる文句。Framework と利用者の関係そのもの。
Martin Fowler bliki “Inversion of Control” の整理:
The question is: what aspect of control are they inverting? The flow of control.
制御フローの所在 が逆転する。これが framework と library の境界。
graph TB
subgraph "Library: 利用者が制御"
U1[Application Code] -->|call| L[Library]
L -->|return| U1
end
subgraph "Framework: Framework が制御"
F[Framework] -->|callback| U2[Application Code]
U2 -->|register| F
end
style L fill:#e1ffe1
style F fill:#ffe1e1
共通基盤での選択
共通基盤を Library として提供するか Framework として提供するかは、利用者にどれだけ制御を渡すか の判断。
Library 寄りの設計
// 通知基盤を library として提供
import { Notifier } from '@platform/notify';
const notifier = new Notifier({ apiKey: '...' });
// 利用者が好きなタイミングで呼ぶ
async function onSignUp(user: User) {
await notifier.send({ to: user.email, subject: 'Welcome', body: '...' });
}
// retry / error handling も利用者が決める
try {
await notifier.send(...);
} catch (e) {
// 利用者が再試行戦略を決める
}
pros: 利用者が完全に制御、柔軟 cons: 利用者が retry / error handling / scheduling を全部書く
Framework 寄りの設計
// 通知基盤を framework として提供
import { NotifyHandler, OnSignUp } from '@platform/notify-framework';
@NotifyHandler({ topic: 'user.signed_up' })
class WelcomeNotifier {
@OnSignUp()
async send(user: User): Promise<NotifyResult> {
return {
to: user.email,
subject: 'Welcome',
body: '...',
};
}
}
// → framework が SignUp イベントを検知して呼ぶ
// → retry / error handling / scheduling は framework 側で
pros: 利用者は宣言だけ、retry / scheduling は基盤が一括管理 cons: framework のルールに利用者が縛られる、抜け道が必要
判断軸
graph TB
Q1{利用者ドメインの<br/>制御フローに依存するか?}
Q1 -->|Yes| Lib[Library 寄り]
Q1 -->|No| Q2{横断的責務を<br/>基盤側で持ちたいか?}
Q2 -->|Yes| Frame[Framework 寄り]
Q2 -->|No| Lib2[Library 寄り]
style Lib fill:#e1ffe1
style Lib2 fill:#e1ffe1
style Frame fill:#fff4e1
Library が良いケース
- 利用者ドメインの中でいつ呼ぶかが多様
- 利用者が retry / error handling を自分で制御したい
- 抜け道が必要(特殊な使い方を許容したい)
例:通知 SDK、HTTP クライアント、認証トークン発行 API
Framework が良いケース
- イベント駆動で 基盤が起点
- 横断的な責務(retry、scheduling、observability)を基盤側で担いたい
- 利用者の認知負荷を最小限にしたい
例:ジョブ基盤(Temporal, Airflow)、API ゲートウェイ、サーバーレス関数
「両方」を提供する設計
実は library + framework のハイブリッド が現実的に多い:
// Framework として登録するコース
@NotifyHandler({ topic: 'user.signed_up' })
class WelcomeNotifier {
@OnSignUp()
async send(user: User) { /* ... */ }
}
// Library として直接呼ぶコース(緊急通知などの特殊ケース)
import { Notifier } from '@platform/notify';
await new Notifier().send({ to: '...', ... });
通常は framework のレールに乗る、例外は library で抜け道。これは前章の「Pit of Success + Sharp Tools」と整合する:
- Pit of Success = framework のレール
- Sharp Tools = library の抜け道
Framework の “重さ” の罠
Framework は強力だが、重く なりがち。
よくある重さ
| 問題 | 影響 |
|---|---|
| 起動時の魔法 | 何が初期化されているか分からない |
| 暗黙の依存 | 利用者が “なぜ動くか” 説明できない |
| Lock-in | framework から離脱できない |
| Schema 進化が難しい | callback の signature が break すると全利用者が壊れる |
Robert Martin『Clean Architecture』第 32 章 “Frameworks Are Details” は警告している:
Don’t marry the framework. Use it but don’t be wedded to it.
Framework に 結婚しすぎない。基盤を framework として提供する場合、利用者が壊滅的に依存しない設計 が必要。
Library から Framework への進化
実用上、library から始めて、framework に進化するパターンが多い:
graph LR
V0[v0: Library のみ] --> V1[v1: Library + 推奨パターン]
V1 --> V2[v2: + Framework オプション]
V2 --> V3[v3: Framework デフォルト + Library 抜け道]
style V0 fill:#e1ffe1
style V3 fill:#fff4e1
最初から framework を作ろうとしない。利用者の反復パターンを見てから、それを framework として抽出する。これは Team Topologies の TVP の精神とも整合する。
この章の要点
- Library = 利用者が制御 / Framework = 基盤が制御
- IoC(Inversion of Control)= 制御フローの所在の逆転、Hollywood Principle
- 判断軸:①利用者ドメインの制御フローに依存するか ②横断的責務を基盤で持つか
- ハイブリッド(library + framework)が実用的に多い
- Framework は強力だが重い。lock-in / 暗黙依存 / 進化困難の罠
- Library から始めて、Framework に進化 が現実的なパス
次章への問いかけ
制御の所在が決まれば、API の 形 が決まる。
次章で API 設計の原則 ── Boring API、進化容易性、idempotency、エラー設計を扱う。