🐧

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