🐧
UseCaseが“手順”を持つべき理由 ─ Repositoryにオーケストレーションを書かない
この記事で言いたいこと
- ビジネスの“手順(オーケストレーション)”は UseCase(アプリケーション層)が持つ。
- Repository は 1 集約の永続化に専念し、他の Repository を呼ばない。
- これにより 依存方向(DIP) が守られ、差し替え・テスト・保守が圧倒的に楽になる。
補足:DIP=Dependency Inversion Principle(依存関係逆転の原則)とは(SOLID の “D” )
- 高水準モジュール(ビジネスロジック)は 低水準モジュール(DB/HTTP/FS)に依存しない。両者は抽象(インターフェース)に依存する。
- 抽象は詳細に依存しない。詳細(実装)が抽象に依存する。
要するに、内側(ユースケース/ドメイン)→ 外側(DB/外部サービス)への依存の向きを“抽象”で反転させて、差し替えやすく・テストしやすくする考え方です。
きっかけ(よくある状況)
業務処理で、次のような実装になりがちです。
- 「A集約の永続化」を担当する Repository メソッドの中で、B や C といった別集約の更新や副作用(在庫引当、残高更新、通知送信、監査ログ追加など)まで 一緒に実行してしまう。
- つまり Repository A が Repository B/C を呼ぶ 構造。
一見「実装が短くまとまって楽」に見えますが、差し替えやテストがつらく、設計上の負債になりやすいです。
※集約≠テーブル
- 集約(Aggregate)=一緒に整合性を守りたい塊。変更や保存を 1トランザクションで扱う境界。入口は 集約ルート。
- テーブル=RDBの保存形式。正規化や検索都合で分割・結合(JOIN)される。
オーケストレーションって何?
オーケストレーション(orchestration) とは、
複数のドメイン操作を、正しい順序・条件・トランザクション境界で組み立てて実行する役割 のこと。
- やる人:UseCase(アプリケーション層)
- やらない人:Repository(永続化の抽象)
なぜ Repository に書かないのか
1) 依存方向(DIP)が崩れる
UseCase → Repository(IF) → Repository(実装) の 一方向 を保ちたい。
Repository 同士が呼び合うと「横の依存」が発生して差し替えやモックが難しくなる。
2) 単一責任の破壊(Fat Repository)
Repository は「1 集約の CRUD」に集中。
複数集約の手順 は Repository の責務ではない。
3) トランザクション境界が曖昧になる
「どこからどこまで 1 トランザクションか」は手順の設計。
Repository 内で別 Repository を叩くと境界が散る。
4) テスト容易性が下がる
- UseCase から repoA, repoB を 個別にモックできるのが理想。
- RepoA 内部で RepoB を呼ばれると結合度が上がりユニットテストが難化。
設計の分担早見表
責務 | 置き場所 |
---|---|
手順(順序・条件・再試行) | UseCase / Domain Service |
トランザクション境界の決定 | UseCase |
1 集約の CRUD | Repository |
横断処理(監査ログ、メトリクス、イベント発行) | UseCase から呼ぶ(ミドルウェア化も可) |
テスト戦略
- UseCase テスト:RepoA と RepoB を モックし、手順と Tx 境界を検証
- Repository テスト:DB 統合テストで CRUD の正しさだけに集中
まとめ
- オーケストレーション=手順の責務。これは UseCase が持つ。
- Repository は 1 集約の永続化に集中し、横依存を作らない。
- DIP(依存関係逆転の原則)・SRP(単一責任の原則)・テスト容易性・Txの明確化が得られ、差し替えやすく保守コストが下がる。
Discussion