Skip to content

ADR-011: ヘキサゴナルアーキテクチャリファクタリング

ステータス

承認済 (2025-08-13)

コンテキスト

初期実装では基本的なMVCパターンでアプリケーションを構築していたが、以下の課題が顕在化した:

課題

  1. 密結合の問題: React コンポーネントが直接ドメインモデルに依存
  2. 責務混在: ゲームロジックと表示ロジックが混在
  3. テスタビリティの低さ: ドメインロジックの単体テストが困難
  4. 拡張性の制限: 新機能追加時の影響範囲が広い
  5. 依存性の方向性: ドメイン層がインフラ層に依存

リファクタリングの目標

  • 真のヘキサゴナルアーキテクチャの実現
  • SOLID原則の徹底適用
  • ドメイン駆動設計の実践
  • 依存性注入による疎結合化

決定

ヘキサゴナルアーキテクチャ(ポート&アダプターパターン)への全面的なリファクタリングを実施する。

実装コンポーネント

1. ポート(インターフェース)層

// application/ports/
- GamePort.ts        // ゲーム状態管理の抽象化
- StoragePort.ts     // データ永続化の抽象化
- TimerPort.ts       // タイマー機能の抽象化
- InputPort.ts       // 入力処理の抽象化

2. アダプター(実装)層

// infrastructure/adapters/
- LocalStorageAdapter.ts  // ブラウザストレージ実装
- BrowserTimerAdapter.ts  // ブラウザタイマー実装

3. アプリケーションサービス層

// application/services/
- GameApplicationService.ts   // ゲームビジネスロジック調整
- InputApplicationService.ts  // 入力処理とアクション変換

4. ドメインサービス層

// domain/services/
- ChainDetectionService.ts  // 連鎖検出・スコア計算
- CollisionService.ts       // 衝突判定・境界チェック
- PuyoSpawningService.ts    // ゲームバランス考慮生成

5. 依存性注入層

// application/di/
- Container.ts  // 依存関係管理・オブジェクト注入

アーキテクチャの原則

  1. 依存性逆転の原則: 高レベルモジュールは低レベルモジュールに依存しない
  2. 単一責任の原則: 各クラス・モジュールは単一の責任を持つ
  3. 開放閉鎖の原則: 拡張に対して開かれ、修正に対して閉じている
  4. インターフェース分離の原則: クライアントが使用しないメソッドに依存させない
  5. リスコフの置換原則: 派生クラスは基底クラスと置換可能

結果

✅ 改善された品質指標

観点 リファクタリング前 リファクタリング後 改善度
結合度 高(密結合) 低(疎結合) ⬆️ 大幅改善
凝集度 低(責務混在) 高(責務分離) ⬆️ 大幅改善
テスタビリティ 困難 容易 ⬆️ 大幅改善
拡張性 制限あり 柔軟 ⬆️ 改善
保守性 困難 容易 ⬆️ 改善

🎯 実現した効果

  1. 真のヘキサゴナルアーキテクチャ
  2. ポート・アダプターパターンの完全実装
  3. ドメイン層の完全独立性確保

  4. SOLID原則の徹底適用

  5. 依存性逆転による疎結合
  6. 単一責任による凝集度向上

  7. ドメイン駆動設計の実践

  8. ドメインサービスの分離
  9. ビジネスロジックの純化

  10. 依存性注入の導入

  11. コンテナによる依存関係管理
  12. テストダブルの容易な挿入

  13. 責務の明確化

  14. 各層の役割と境界の明確化
  15. コードの可読性・保守性向上

📁 新しいディレクトリ構造

src/
├── application/
│   ├── ports/          # インターフェース定義
│   ├── services/       # アプリケーションサービス
│   └── di/             # 依存性注入
├── domain/
│   ├── models/         # ドメインモデル
│   └── services/       # ドメインサービス
├── infrastructure/
│   └── adapters/       # 具象実装
└── presentation/
    ├── components/     # React コンポーネント
    └── hooks/          # カスタムフック

トレードオフ

メリット

  • 保守性・拡張性・テスタビリティの大幅向上
  • ビジネスロジックの純粋性確保
  • 技術的負債の削減

デメリット

  • 初期実装の複雑性増加
  • ファイル数・コード量の増加
  • 学習コストの発生

リスク軽減策

  • 段階的リファクタリングによる影響最小化
  • 包括的テストによる品質保証
  • ドキュメンテーションによる知識共有

実装ガイドライン

1. 新機能開発時

// 1. ポートの定義
interface NewFeaturePort {
  executeFeature(): Promise<Result>
}

// 2. アダプターの実装
class NewFeatureAdapter implements NewFeaturePort {
  async executeFeature(): Promise<Result> { /* 実装 */ }
}

// 3. DIコンテナへの登録
container.register<NewFeaturePort>('NewFeaturePort', () => new NewFeatureAdapter())

2. テスト実装時

// テストダブルの使用
class MockNewFeatureAdapter implements NewFeaturePort {
  async executeFeature(): Promise<Result> { /* モック実装 */ }
}

// テストでのDI設定
container.register<NewFeaturePort>('NewFeaturePort', () => new MockNewFeatureAdapter())

3. 既存機能修正時

  • 既存のポート・アダプター構造を維持
  • 新しい要件は新しいポートとして分離
  • 段階的な移行で影響を最小化

関連するADR

参考資料