|
| 1 | +# 設計判断記録 |
| 2 | + |
| 3 | +本ドキュメントは、EpisodicRAGプロジェクトにおける主要な設計判断とその根拠を記録する。 |
| 4 | +エンタープライズPython開発の教材として、各判断の「なぜ」を明示することを目的とする。 |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## アーキテクチャ決定 |
| 9 | + |
| 10 | +| 決定事項 | 検討した代替案 | 選択理由 | |
| 11 | +|---------|--------------|---------| |
| 12 | +| Clean Architecture 4層 | MVC, 単純なLayered | テスタビリティ、依存方向の制御 | |
| 13 | +| Strategy (LevelBehavior) | 継承階層, Switch文 | OCP準拠、新レベル追加時の変更最小化 | |
| 14 | +| Thin Facade (DigestConfig) | 直接アクセス, Thick Facade | シンプル化、重複排除、カプセル化 | |
| 15 | +| TypedDict | dataclass, NamedTuple | JSON互換性、段階的型付け、既存dictとの相互運用 | |
| 16 | +| Singleton (Registry) | DIコンテナ, グローバル変数 | ドメイン層のシンプルさ、テスト時のリセット可能性 | |
| 17 | +| Composite (ErrorFormatter) | 単一クラス, 関数群 | カテゴリ別責務分離、SRP準拠 | |
| 18 | +| Chain of Responsibility (TemplateLoader) | if-else連鎖, Switch | 戦略の追加・削除が容易、OCP準拠 | |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 各パターンの実装箇所 |
| 23 | + |
| 24 | +### Strategy Pattern |
| 25 | +- **実装**: `domain/level_registry.py` |
| 26 | +- **目的**: 8階層(weekly〜centurial)ごとの振る舞いを交換可能に |
| 27 | +- **SOLID**: OCP(新レベル追加時に既存コード変更不要) |
| 28 | + |
| 29 | +### Facade Pattern |
| 30 | +- **実装**: `config/facade.py` |
| 31 | +- **目的**: 複雑な設定サブシステムへのシンプルなインターフェース |
| 32 | +- **設計判断**: Thin Facade(プロバイダを直接公開、重複プロパティ排除) |
| 33 | + |
| 34 | +### Repository Pattern |
| 35 | +- **実装**: `infrastructure/json_repository/` |
| 36 | +- **目的**: ファイルI/Oの抽象化、ビジネスロジックからの分離 |
| 37 | +- **SOLID**: DIP(上位層がI/O詳細に依存しない) |
| 38 | + |
| 39 | +### Template Method Pattern |
| 40 | +- **実装**: `domain/error_formatter/base.py` |
| 41 | +- **目的**: パス正規化などの共通処理を基底クラスで定義 |
| 42 | +- **SOLID**: DRY(コード重複の排除) |
| 43 | + |
| 44 | +### Composite Pattern |
| 45 | +- **実装**: `domain/error_formatter/__init__.py` |
| 46 | +- **目的**: カテゴリ別フォーマッタを統合インターフェースで提供 |
| 47 | +- **SOLID**: SRP(各フォーマッタが単一カテゴリに責任) |
| 48 | + |
| 49 | +### Chain of Responsibility Pattern |
| 50 | +- **実装**: `infrastructure/json_repository/template_loader.py` |
| 51 | +- **目的**: テンプレートロード戦略の順次試行 |
| 52 | +- **SOLID**: OCP(新戦略追加が容易) |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +## SOLID原則の実践箇所 |
| 57 | + |
| 58 | +### Single Responsibility Principle (SRP) |
| 59 | +- `domain/error_formatter/`: エラーカテゴリごとに独立クラス |
| 60 | +- `infrastructure/json_repository/`: I/O、テンプレート、ユーティリティを分離 |
| 61 | +- `config/`: 各プロバイダが単一責務(閾値、パス、ソース) |
| 62 | + |
| 63 | +### Open/Closed Principle (OCP) |
| 64 | +- `domain/level_registry.py`: 新レベル追加時に既存コード変更不要 |
| 65 | +- `infrastructure/json_repository/template_loader.py`: 新戦略追加が容易 |
| 66 | + |
| 67 | +### Liskov Substitution Principle (LSP) |
| 68 | +- `domain/error_formatter/base.py`: 全サブクラスが基底クラスの契約を満たす |
| 69 | +- `infrastructure/json_repository/template_loader.py`: 全戦略がLoadStrategyを満たす |
| 70 | + |
| 71 | +### Interface Segregation Principle (ISP) |
| 72 | +- `domain/protocols.py`: 必要最小限のProtocol定義 |
| 73 | +- 各層の`__init__.py`: 必要なAPIのみをexport |
| 74 | + |
| 75 | +### Dependency Inversion Principle (DIP) |
| 76 | +- `interfaces/finalize_from_shadow.py`: コンストラクタインジェクション |
| 77 | +- 全層: 上位層は下位層の具象に依存しない |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## レイヤー構造 |
| 82 | + |
| 83 | +``` |
| 84 | +┌─────────────────────────────────────────────────────────┐ |
| 85 | +│ Interfaces Layer │ |
| 86 | +│ (finalize_from_shadow.py, save_provisional_digest.py) │ |
| 87 | +│ 外部からのエントリーポイント │ |
| 88 | +└─────────────────────────────────────────────────────────┘ |
| 89 | + ↓ 依存 |
| 90 | +┌─────────────────────────────────────────────────────────┐ |
| 91 | +│ Application Layer │ |
| 92 | +│ (shadow/, grand/, finalize/, tracking/) │ |
| 93 | +│ ユースケースの実装、ビジネスプロセスの調整 │ |
| 94 | +└─────────────────────────────────────────────────────────┘ |
| 95 | + ↓ 依存 |
| 96 | +┌─────────────────────────────────────────────────────────┐ |
| 97 | +│ Infrastructure Layer │ |
| 98 | +│ (json_repository/, file_scanner.py, logging_config.py) │ |
| 99 | +│ 外部リソースへのアクセス(ファイル、ログ) │ |
| 100 | +└─────────────────────────────────────────────────────────┘ |
| 101 | + ↓ 依存 |
| 102 | +┌─────────────────────────────────────────────────────────┐ |
| 103 | +│ Config Layer │ |
| 104 | +│ (facade.py, threshold_provider.py, path_resolver.py) │ |
| 105 | +│ 設定管理、パス解決 │ |
| 106 | +└─────────────────────────────────────────────────────────┘ |
| 107 | + ↓ 依存 |
| 108 | +┌─────────────────────────────────────────────────────────┐ |
| 109 | +│ Domain Layer │ |
| 110 | +│ (types.py, constants.py, exceptions.py, protocols.py) │ |
| 111 | +│ ビジネスルール、型定義、例外(外部依存なし) │ |
| 112 | +└─────────────────────────────────────────────────────────┘ |
| 113 | +``` |
| 114 | + |
| 115 | +**重要な制約**: 依存は常に下方向のみ。上位層が下位層に依存し、逆は許可されない。 |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## TypedDict vs dataclass の選択 |
| 120 | + |
| 121 | +| 観点 | TypedDict | dataclass | |
| 122 | +|-----|-----------|-----------| |
| 123 | +| JSON互換性 | ネイティブ対応 | 変換が必要 | |
| 124 | +| 既存dictとの相互運用 | シームレス | 明示的変換 | |
| 125 | +| 型チェック | 静的のみ | 静的+実行時 | |
| 126 | +| イミュータビリティ | 制御不可 | frozen=True | |
| 127 | +| デフォルト値 | total=Falseで対応 | 直接サポート | |
| 128 | + |
| 129 | +**選択理由**: EpisodicRAGはJSONファイルを多用するため、TypedDictの方が自然。 |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Singleton vs DI の選択 |
| 134 | + |
| 135 | +| 観点 | Singleton | Dependency Injection | |
| 136 | +|-----|-----------|---------------------| |
| 137 | +| シンプルさ | 高 | 中〜低 | |
| 138 | +| テスタビリティ | reset関数で対応 | 高 | |
| 139 | +| グローバル状態 | あり | なし | |
| 140 | +| 設定変更 | 難しい | 容易 | |
| 141 | + |
| 142 | +**選択理由**: ドメイン層は外部依存を持たないため、シンプルなSingletonで十分。 |
| 143 | +テスト用に`reset_level_registry()`を提供してテスタビリティを確保。 |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## 参考リンク |
| 148 | + |
| 149 | +- [Clean Architecture (Robert C. Martin)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) |
| 150 | +- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) |
| 151 | +- [Design Patterns (GoF)](https://en.wikipedia.org/wiki/Design_Patterns) |
| 152 | +- [Python typing.TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict) |
0 commit comments