🗄️
リポジトリ(Repository)
集約をコレクションとして扱う抽象。インフラの詳細をドメインから隠蔽する
解決する課題
集約を永続化・復元する処理がドメインモデルに混入すると、ドメインが DB・ORM・クエリ言語に依存してしまう。ドメインロジックのテストに DB 接続が必要になる。
概念
リポジトリは、集約をまるでインメモリのコレクションかのように扱う抽象レイヤー。SQL・NoSQL・外部APIなどの永続化詳細をドメインから隠蔽する。
# ドメイン層(インターフェース)
class OrderRepository(ABC):
@abstractmethod
def find_by_id(self, order_id: OrderId) -> Optional[Order]: ...
@abstractmethod
def save(self, order: Order) -> None: ...
@abstractmethod
def find_by_customer(self, customer_id: CustomerId) -> list[Order]: ...
# インフラ層(実装)
class SqlOrderRepository(OrderRepository):
def find_by_id(self, order_id: OrderId) -> Optional[Order]:
row = self._db.execute(
"SELECT * FROM orders WHERE id = ?", [str(order_id)]
).fetchone()
return self._mapper.to_entity(row) if row else None
def save(self, order: Order) -> None:
# INSERT or UPDATE
...
設計の原則
- 1集約につき1リポジトリ — 集約ルートに対してのみリポジトリを作る。
OrderItemの単独リポジトリは不要 - クエリは集約ルートから — 集約の内部オブジェクトを直接検索するクエリは避ける
- トランザクションはリポジトリの外 — トランザクション制御はアプリケーションサービスが持つ
CQRS との関係
複雑な検索(一覧表示・検索フィルタ・集計)をドメインリポジトリで満たそうとすると、「1集約1リポジトリ」の原則と衝突する。
CQRS では、変更(コマンド)用のリポジトリと読み取り(クエリ)用のリードモデルを分離することでこの問題を解消する。
コマンド側: OrderRepository(集約の取得・保存)
クエリ側: OrderQueryService(ビューモデルの構築、JOINあり)
テストのしやすさ
インターフェースを使うことで、テスト時はインメモリ実装に差し替えられる。
class InMemoryOrderRepository(OrderRepository):
def __init__(self):
self._store: dict[str, Order] = {}
def find_by_id(self, order_id: OrderId) -> Optional[Order]:
return self._store.get(str(order_id))
def save(self, order: Order) -> None:
self._store[str(order.id)] = order
関連概念
- → 集約(リポジトリが管理する対象)
- → 六角形アーキテクチャ(リポジトリはポートとアダプターの典型例)
- → CQRS
- 1. 🗺️サブドメイン(コア・サポート・汎用)
- 2. 🗾コンテキストマップ
- 3. ⬡六角形アーキテクチャ(ポートとアダプター)
- 4. 🪪エンティティ(Entity)
- 5. ⚙️ドメインサービス(Domain Service)
- 6. 📣ドメインイベント(Domain Event)
- 7. 🫧集約(Aggregate)
- 8. 📐集約の設計原則(Vernon の4ルール)
- 9. 🗄️リポジトリ(Repository)
- 10. 🏭ファクトリ(Factory)
- 11. 📦モジュール(Module)
- 12. 🎛️アプリケーションサービス(Application Service)
- 13. 🔌境界づけられたコンテキストの統合
- 14. ⚡CQRS(コマンドクエリ責任分離)
- 15. 📜イベントソーシング(Event Sourcing)
出典: 実践ドメイン駆動設計(Vaughn Vernon)第12章