ADR-011: ヘキサゴナルアーキテクチャリファクタリング¶
ステータス¶
✅ 承認済 (2025-08-13)
コンテキスト¶
初期実装では基本的なMVCパターンでアプリケーションを構築していたが、以下の課題が顕在化した:
課題¶
- 密結合の問題: React コンポーネントが直接ドメインモデルに依存
- 責務混在: ゲームロジックと表示ロジックが混在
- テスタビリティの低さ: ドメインロジックの単体テストが困難
- 拡張性の制限: 新機能追加時の影響範囲が広い
- 依存性の方向性: ドメイン層がインフラ層に依存
リファクタリングの目標¶
- 真のヘキサゴナルアーキテクチャの実現
- 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 // 依存関係管理・オブジェクト注入
アーキテクチャの原則¶
- 依存性逆転の原則: 高レベルモジュールは低レベルモジュールに依存しない
- 単一責任の原則: 各クラス・モジュールは単一の責任を持つ
- 開放閉鎖の原則: 拡張に対して開かれ、修正に対して閉じている
- インターフェース分離の原則: クライアントが使用しないメソッドに依存させない
- リスコフの置換原則: 派生クラスは基底クラスと置換可能
結果¶
✅ 改善された品質指標¶
観点 | リファクタリング前 | リファクタリング後 | 改善度 |
---|---|---|---|
結合度 | 高(密結合) | 低(疎結合) | ⬆️ 大幅改善 |
凝集度 | 低(責務混在) | 高(責務分離) | ⬆️ 大幅改善 |
テスタビリティ | 困難 | 容易 | ⬆️ 大幅改善 |
拡張性 | 制限あり | 柔軟 | ⬆️ 改善 |
保守性 | 困難 | 容易 | ⬆️ 改善 |
🎯 実現した効果¶
- 真のヘキサゴナルアーキテクチャ
- ポート・アダプターパターンの完全実装
-
ドメイン層の完全独立性確保
-
SOLID原則の徹底適用
- 依存性逆転による疎結合
-
単一責任による凝集度向上
-
ドメイン駆動設計の実践
- ドメインサービスの分離
-
ビジネスロジックの純化
-
依存性注入の導入
- コンテナによる依存関係管理
-
テストダブルの容易な挿入
-
責務の明確化
- 各層の役割と境界の明確化
- コードの可読性・保守性向上
📁 新しいディレクトリ構造¶
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¶
- ADR-001: アーキテクチャ選定 - 初期アーキテクチャ決定
- ADR-003: ドメイン駆動設計採用 - DDD原則の適用
- ADR-006: 関数型プログラミング採用 - 関数型パラダイムとの整合