Skip to content

依存性注入とアーキテクチャリファクタリング

カスタムDIコンテナとGameUseCaseパターンを採用

日付: 2025-08-09

ステータス

2025-08-09 承認済み

関連する決定: - 002-状態管理アーキテクチャ.md を 非推奨 とし、この決定で置換

コンテキスト

Iteration 3完了後、以下の課題が明確になった:

既存アーキテクチャの問題

  • App.tsx が332行の巨大なコンポーネント(神オブジェクト化)
  • ビジネスロジックがプレゼンテーション層に散在
  • サービス間の依存関係がハードコード
  • テスタビリティの低下(モック注入困難)
  • Clean Architectureからの乖離

評価した解決策

  1. カスタムDI + GameUseCase ← 採用
  2. React Context + useReducer(既存ADR)
  3. Redux Toolkit + RTK Query
  4. Zustand + immer

決定

カスタムDIコンテナ + GameUseCaseパターン を採用する

アーキテクチャ変更点

1. 5層アーキテクチャへの進化

Presentation Layer
      ↓
Application Layer (GameUseCase + DI)
      ↓
Service Layer
      ↓
Domain Layer
      ↓
Infrastructure Layer

2. DIコンテナの導入

  • 型安全なサービス解決
  • シングルトン・ファクトリパターン
  • Symbol-basedトークン

3. GameUseCaseパターン

  • App.tsxからビジネスロジックを分離
  • ドメインモデルへの統一インターフェース
  • サービス層との調整役

理由

DIコンテナ採用理由:

  • 疎結合の実現: インターフェースベースの依存関係
  • テスタビリティ: モック注入による単体テスト強化
  • 拡張性: 新サービス追加時の既存コード非影響
  • 型安全性: TypeScript Genericsでコンパイル時チェック

GameUseCaseパターン採用理由:

  • 責任の明確化: プレゼンテーション層の責任をView表示に限定
  • 複雑性の管理: ビジネスロジックの中央集約
  • ドメイン保護: ドメインモデルの直接操作を制限

他選択肢の除外理由:

  • Context + useReducer: ボイラープレート増加、深いネストの問題
  • Redux Toolkit: 小規模アプリには過剰、状態正規化の複雑さ
  • Zustand: 型安全性とテスタビリティの懸念

影響

ポジティブな影響

  • 保守性向上: 明確な層分離、責任分担
  • テスト品質: 345テストケース、DI によるモック注入
  • 型安全性: any型完全排除、コンパイル時エラー検出
  • 拡張性: SOLID原則遵守、新機能追加コストの削減
  • 開発効率: 関心事の分離による並行開発可能性

ネガティブな影響

  • 学習コスト: DIパターン、Clean Architectureの習得
  • 初期複雑性: 設定とボイラープレート増加
  • 実行時コスト: サービス解決のわずかなオーバーヘッド

軽減策

  • 段階的導入: 既存機能を壊さない漸進的リファクタリング
  • 包括的ドキュメント: DI設計書、アーキテクチャガイドの充実
  • テスト戦略: 18単位テストによるリグレッション防止

実装詳細

DIコンテナアーキテクチャ

// サービストークン (Symbol-based)
export const GAME_USE_CASE = Symbol.for('GameUseCase')
export const SOUND_EFFECT_SERVICE = Symbol.for('SoundEffectService')

// DIコンテナ
export class Container {
  resolve<T>(token: symbol): T
  registerSingleton<T>(token: symbol, instance: T): void
  registerFactory<T>(token: symbol, factory: () => T): void
}

// 型安全なサービス解決
const soundService = container.resolve<SoundEffectService>(SOUND_EFFECT_SERVICE)

GameUseCaseパターン

export class GameUseCase {
  private game: Game

  constructor() {
    this.game = new Game()
  }

  // ドメインオペレーションの統一インターフェース
  public startNewGame(): void
  public moveLeft(): boolean
  public rotate(): boolean
  public getScore(): { current: number }
  public isPlaying(): boolean
}

App.tsxの簡素化

function App() {
  // DIコンテナ初期化
  useState(() => {
    initializeApplication()
    return null
  })

  // ユースケース取得
  const [gameUseCase] = useState(() => 
    container.resolve<GameUseCase>(GAME_USE_CASE)
  )

  // プレゼンテーション責任のみ
  return <GameBoard game={gameUseCase.getGameInstance()} />
}

品質指標

コード品質

  • 複雑度削減: App.tsx 332行 → 分離により責任明確化
  • 型安全性: any型 0件(ESLintエラー 0件)
  • テストカバレッジ: 345テストケース、全テスト通過

アーキテクチャ適合性

  • Clean Architecture: 内向き依存関係の遵守
  • SOLID原則: 全5原則の適用確認
  • DDD実践: ドメインモデルの保護と集約

ADR002との関係

002-状態管理アーキテクチャ は以下の理由で 非推奨 とする:

  1. 実装乖離: Context + useReducer は未実装
  2. アーキテクチャ進化: Clean Architecture準拠への移行
  3. スケーラビリティ: DIによる疎結合の優位性

移行方針:

  • 既存のドメインモデル(Game, Field, Puyo等)は維持
  • 状態管理は GameUseCase + Game オブジェクト直接操作
  • サービス間連携は DI で管理

今後の拡張計画

  1. 自動サービス発見: アノテーションベース登録
  2. 非同期DI: Promise-basedサービス解決
  3. 設定外部化: JSON設定ファイルによるサービス構成
  4. マイクロサービス対応: サービス間通信の抽象化

コンプライアンス

この決定の遵守は以下により確認:

  • DIコンテナ(Container.ts)の実装とテスト
  • GameUseCaseクラスの存在と責任分離
  • サービスインターフェースの定義
  • 型安全性(any型使用禁止)の維持
  • アーキテクチャ図とドキュメントの更新

備考

  • 決定者: 開発チーム
  • 影響範囲: アーキテクチャ全体
  • 実装期間: Iteration 3-4 間
  • レビュー予定: Iteration 4 完了後、パフォーマンス・保守性評価
  • 前提条件: TypeScript、Clean Architecture知識
  • 成功条件: テスト品質維持、開発効率向上、保守性改善