目次を表示する

共通基盤の設計軸 2026 ─ 抽象・責務・非機能要件を設計する 15 章

Inversion of Control ─ 基盤主導 vs 利用者主導

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-inframework から離脱できない
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、エラー設計を扱う。