リポジトリパターン(DDD)

英語名 Repository Pattern (DDD)
読み方 リポジトリ パターン
難易度
所要時間 30分〜1時間
提唱者 Eric Evans / Domain-Driven Design (2003)
目次

ひとことで言うと
#

ドメインモデル(集約)の保存・取得を抽象化し、永続化の詳細(SQL、ORM等)をドメインロジックから隠蔽することで、ビジネスルールの純粋さとテスタビリティを確保するパターン。

押さえておきたい用語
#

押さえておきたい用語
Repository(リポジトリ)
集約の保存・取得・削除を担うインターフェースのこと。ドメイン層に定義し、インフラ層で実装する。
Aggregate(アグリゲート)
トランザクション整合性を保つドメインオブジェクトの境界単位を指す。リポジトリは集約単位でアクセスする。
Aggregate Root(集約ルート)
集約の外部からの唯一のアクセスポイント。リポジトリが返すのは常に集約ルート。
Unit of Work
トランザクション内の変更を一括でコミットする仕組み。リポジトリと組み合わせて使う手法である。
In-Memory Repository
テスト用にメモリ上で動作するリポジトリ実装。DBなしでドメインロジックをテストできる。

リポジトリパターンの全体像
#

リポジトリパターン:ドメインと永続化の間に抽象レイヤーを挟む
Domain LayerAggregate Root (Order)Entity, Value Object永続化の技術を知らないRepository InterfacefindById(id): Ordersave(order): voidドメイン層に定義Infrastructure LayerPostgresOrderRepositorySQL/ORM の実装詳細Test用InMemoryOrderRepositoryDB不要でテスト可能実装を差し替え
リポジトリパターン適用の進め方フロー
1
集約の特定
リポジトリを作る単位(集約ルート)を決める
2
インターフェース定義
ドメイン層にRepositoryインターフェースを定義
3
実装の作成
インフラ層にDB実装、テスト用にInMemory実装
DI設定
本番とテストで実装を切り替える設定を行う

こんな悩みに効く
#

  • ドメインロジックの中にSQLが混在して読みにくい
  • DB接続が必要でドメインロジックのユニットテストが書けない
  • ORMの変更やDB移行の影響がビジネスロジックに波及する

基本の使い方
#

集約ルートを特定しリポジトリの単位を決める
リポジトリは集約ルート単位で作る。OrderRepositoryOrder集約全体(OrderItem、ShippingInfo等を含む)を一括で保存・取得する。Entityごとにリポジトリを作るのは間違い。
ドメイン層にインターフェースを定義する
findById(id), save(aggregate), delete(id) のようなメソッドを定義する。インターフェースのメソッド名はドメインの言葉で表現し、SELECT * FROMのようなDB用語は使わない。
インフラ層にDB実装、テスト層にInMemory実装を作る
本番用のPostgresOrderRepositoryはSQL/ORMで永続化し、テスト用のInMemoryOrderRepositoryはMapやListで動作する。DIコンテナで環境に応じて実装を切り替える。

具体例
#

例1:EC企業がリポジトリパターンでテスト速度を10倍にする

エンジニア35名のEC。注文ドメインのテストがすべてDB接続を必要とし、CI実行に 22分 かかっていた。テストの追加を避けるエンジニアも出始めていた。

OrderRepositoryインターフェースをドメイン層に定義し、PostgresOrderRepository(本番用)とInMemoryOrderRepository(テスト用)を実装。

テスト時はInMemory実装を注入する形に変更。ドメインロジックのユニットテスト実行時間が 22分 → 2分 に短縮。テストカバレッジが 45% → 78% に向上し、バグの早期発見率も改善した。

例2:SaaS企業がDB移行をドメインに影響なく実行する

エンジニア50名のBtoB SaaS。MongoDBの運用コストが高騰し、PostgreSQLへの移行を検討。しかし、MongoDBのクエリがサービス層に散在しており、移行の影響範囲が推定 200ファイル に及んだ。

リポジトリパターンでリファクタリング。各集約にRepositoryインターフェースを定義し、MongoDB実装をAdapter化。その後、PostgreSQL Adapterを新規実装。

Feature Flagで段階的に切り替えた結果、ドメインロジックの変更は ゼロファイル。移行期間は当初見積もり6ヶ月から 8週間 に短縮された。

例3:医療系スタートアップが複雑な集約の永続化を整理する

エンジニア15名の医療系スタートアップ。患者カルテの集約が複雑で、1つのカルテに診療記録・処方箋・検査結果が紐づく。ORMのリレーション設定が複雑化し、N+1問題やデータ不整合が月 8件 発生していた。

PatientRecordRepositoryを集約ルート単位で設計し、集約全体を1回のクエリで取得するカスタムSQLを実装。ORMのリレーション自動読み込みに頼らず、リポジトリ内で明示的にデータを組み立てる方式に変更。

N+1問題が ゼロ に。データ不整合も 月8件 → 月0.5件 に減少。リポジトリ内にSQLが集約されたことで、パフォーマンスチューニングも1箇所の修正で完結するようになった。

やりがちな失敗パターン
#

  1. Entityごとにリポジトリを作るOrderRepositoryOrderItemRepositoryを分けると、集約の整合性を呼び出し側が管理する必要が出る。集約ルート単位で1つ
  2. リポジトリのインターフェースが肥大化するfindByNameAndStatusAndCreatedAtBetweenのようなメソッドが増殖する。仕様パターン(Specification)を使って検索条件を分離する
  3. リポジトリ内でドメインロジックを実行する — WHERE句で複雑な条件判定をするのはドメインロジックの漏洩。取得後にドメインモデル内で判定する
  4. InMemory実装とDB実装の振る舞いが異なる — InMemoryでは通るがDB実装で落ちるテストは信頼性が低い。重要なケースは統合テストでDB実装も確認する

まとめ
#

リポジトリパターンは集約の永続化を抽象化し、ドメインロジックをDBの技術詳細から分離するDDDの基本パターン。ドメイン層にインターフェースを定義し、インフラ層にDB実装、テスト層にInMemory実装を配置する。集約ルート単位でリポジトリを設計することで整合性を担保し、DIによる実装の差し替えでテスタビリティとDB移行の柔軟性を同時に確保する。