依存性注入とアーキテクチャリファクタリング¶
カスタムDIコンテナとGameUseCaseパターンを採用
日付: 2025-08-09
ステータス¶
2025-08-09 承認済み
関連する決定: - 002-状態管理アーキテクチャ.md を 非推奨 とし、この決定で置換
コンテキスト¶
Iteration 3完了後、以下の課題が明確になった:
既存アーキテクチャの問題¶
- App.tsx が332行の巨大なコンポーネント(神オブジェクト化)
- ビジネスロジックがプレゼンテーション層に散在
- サービス間の依存関係がハードコード
- テスタビリティの低下(モック注入困難)
- Clean Architectureからの乖離
評価した解決策¶
- カスタムDI + GameUseCase ← 採用
- React Context + useReducer(既存ADR)
- Redux Toolkit + RTK Query
- 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-状態管理アーキテクチャ は以下の理由で 非推奨 とする:
- 実装乖離: Context + useReducer は未実装
- アーキテクチャ進化: Clean Architecture準拠への移行
- スケーラビリティ: DIによる疎結合の優位性
移行方針:
- 既存のドメインモデル(Game, Field, Puyo等)は維持
- 状態管理は GameUseCase + Game オブジェクト直接操作
- サービス間連携は DI で管理
今後の拡張計画¶
- 自動サービス発見: アノテーションベース登録
- 非同期DI: Promise-basedサービス解決
- 設定外部化: JSON設定ファイルによるサービス構成
- マイクロサービス対応: サービス間通信の抽象化
コンプライアンス¶
この決定の遵守は以下により確認:
- DIコンテナ(Container.ts)の実装とテスト
- GameUseCaseクラスの存在と責任分離
- サービスインターフェースの定義
- 型安全性(any型使用禁止)の維持
- アーキテクチャ図とドキュメントの更新
備考¶
- 決定者: 開発チーム
- 影響範囲: アーキテクチャ全体
- 実装期間: Iteration 3-4 間
- レビュー予定: Iteration 4 完了後、パフォーマンス・保守性評価
- 前提条件: TypeScript、Clean Architecture知識
- 成功条件: テスト品質維持、開発効率向上、保守性改善