DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Claude CodeでDIコンテナを設計する:依存性注入・テスト可能な設計・スコープ管理

はじめに

「テストのためにモックを渡せない」「グローバルなサービスインスタンスが増えて依存関係が追えない」——DIコンテナでモジュール間の依存を明示化し、テスト可能な設計をClaude Codeに生成させる。


CLAUDE.mdに設計ルールを書く

## 依存性注入(DI)設計ルール

- new ServiceName() を直接呼び出さない(DIコンテナから取得)
- コンストラクタインジェクション: 依存は全てコンストラクタで宣言
- singleton: DBクライアント等(1インスタンス)
- transient: ステートレスユーティリティ(毎回新規)
- テスト時はcontainer.override()でモックをSwap
Enter fullscreen mode Exit fullscreen mode

生成される実装(抜粋)

export class Container {
  private readonly registry = new Map();
  private readonly resolving = new Set(); // 循環依存検出

  singleton(token, factory) { this.registry.set(token, { factory, scope: 'singleton' }); return this; }
  register(token, instance) { this.registry.set(token, { factory: () => instance, scope: 'singleton', instance }); return this; }

  async resolve(token) {
    const reg = this.registry.get(token);
    if (!reg) throw new Error('No registration for: ' + String(token));
    if (reg.scope === 'singleton' && reg.instance !== undefined) return reg.instance;
    if (this.resolving.has(token)) throw new Error('Circular dependency: ' + String(token));

    this.resolving.add(token);
    try {
      const instance = await reg.factory(this);
      if (reg.scope === 'singleton') reg.instance = instance;
      return instance;
    } finally { this.resolving.delete(token); }
  }
}

// 依存を明示したサービス(コンストラクタインジェクション)
export class OrderService {
  constructor(
    private readonly db,
    private readonly userService,
    private readonly paymentService,
  ) {}
}

// テスト時: インフラだけモック、サービスロジックは本物
const testContainer = new Container();
testContainer.register(TOKENS.Database, createMockPrismaClient());
testContainer.register(TOKENS.PaymentService, { charge: jest.fn().mockResolvedValue({ id: 'pay_test' }) });
testContainer.singleton(TOKENS.UserService, async (c) =>
  new UserService(await c.resolve(TOKENS.Database)) // 実際のUserService
);
Enter fullscreen mode Exit fullscreen mode

まとめ

  1. CLAUDE.md にコンストラクタインジェクション必須・singleton/transientスコープ・テスト時はcontainer.override()でモック登録を明記
  2. 循環依存検出 で解決中のトークンをSetで追跡——循環が発生した瞬間に明示的エラーで通知
  3. container.override() でテスト時にモックをSwap——本番コードを変更せずにDB/Redis/外部APIだけ置き換え可能
  4. 実サービスロジックはそのままテスト ——モックはインフラのみ。UserServiceのロジックは本物を使い統合テストに近い品質を確保

アーキテクチャ設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*

prompt-works.jp

みょうが (@myougatheaxo) — ウーパールーパーのVTuber。

Top comments (0)