ドメイン駆動設計におけるリポジトリとは?役割・集約との関係・実装方法を解説
ドメイン駆動設計におけるリポジトリは、単なるデータアクセス部品ではありません。リポジトリは、ドメインモデルをデータベースや永続化技術の詳細から守るための重要な戦術的パターンです。業務ルールを表すドメイン層がSQL、ORM、外部API、テーブル構造、トランザクション処理に直接依存してしまうと、ドメインモデルはすぐに技術的な都合に引きずられてしまいます。リポジトリは、そのような依存を切り離し、ドメインモデルを業務概念に集中させるために存在します。
特に企業システムでは、注文、顧客、口座、契約、請求、サブスクリプションのように、一貫性を守るべき重要な業務単位が存在します。ドメイン駆動設計では、これらを集約として設計し、その入口となる集約ルートを通じて状態変更を行います。リポジトリは、この集約ルートを取得し、保存し、必要な永続化処理を隠蔽する役割を持ちます。つまり、良いリポジトリ設計は、保守性、テスト容易性、境界の明確化、長期的な拡張性に大きく影響します。
1. ドメイン駆動設計におけるリポジトリとは
ドメイン駆動設計におけるリポジトリとは、ドメイン層とデータソースの間に置かれる抽象化です。アプリケーションは、リポジトリを通じて集約を取得・保存しますが、ドメインモデル自体はデータベース、SQL、ORM、NoSQL、外部API、ファイル保存などの具体的な永続化技術を知る必要がありません。これにより、ドメインモデルは業務ルールの表現に集中できます。
リポジトリは、オブジェクトの集合を扱うような感覚で、集約を取得したり保存したりするための入口です。重要なのは、リポジトリが「テーブル操作の便利クラス」ではなく、「ドメインモデルのための永続化境界」であることです。ドメイン駆動設計では、リポジトリはデータベース中心ではなく、ドメイン中心に設計されます。
| 観点 | リポジトリの意味 |
|---|---|
| 主な役割 | 集約の取得・保存を抽象化する |
| 守る対象 | ドメインモデル、業務ルール、一貫性 |
| 隠蔽するもの | SQL、ORM、テーブル構造、外部API、永続化方式 |
| 主な利用者 | アプリケーション層のユースケース |
| 主な実装場所 | インフラストラクチャ層 |
| 設計の中心 | 集約ルート |
| 避けるべき姿 | 単なるCRUD操作の寄せ集め |
1.1 リポジトリが表す考え方
リポジトリは、ドメイン側から見ると「必要な集約を取り出せる場所」です。たとえば注文処理では、アプリケーション層が注文リポジトリに対して「この注文IDの注文を取得したい」と要求し、取得した注文集約に対して業務操作を行い、最後に保存します。このとき、注文集約は自分がどのテーブルから読み込まれたのか、どのORMで保存されるのかを知りません。
この考え方により、ドメインモデルは永続化の都合から独立できます。データベースをSQLからNoSQLへ変える場合、またはORMを変更する場合でも、理想的にはリポジトリ実装の変更に閉じ込められます。もちろん現実には完全な独立は難しい場合もありますが、リポジトリを置くことで変更の影響範囲を明確にできます。
1.2 リポジトリの目的
リポジトリの目的は、ドメイン層をインフラストラクチャ層から切り離すことです。ドメイン層にSQLやORM固有のコードが入り込むと、業務ルールを読むために技術的な詳細を理解しなければならなくなります。これでは、ドメインモデルが業務概念を表すというドメイン駆動設計の目的から外れてしまいます。
また、リポジトリはテスト容易性を高めます。アプリケーション層やドメインサービスをテストするとき、本物のデータベースを使わずにテスト用リポジトリを差し替えられます。これにより、ユースケースのテストが速くなり、永続化技術の状態に依存しにくくなります。
1.3 エリック・エヴァンスのDDDにおける位置づけ
ドメイン駆動設計の文脈では、リポジトリはエンティティや値オブジェクト、集約、ドメインサービスと並ぶ戦術的パターンの一つとして扱われます。特にリポジトリは、集約と強い関係を持ちます。なぜなら、リポジトリが扱う対象は、基本的に集約ルートだからです。
リポジトリの考え方は、単にデータベースアクセスを隠すことではありません。ドメインモデルの整合性を守るために、どの単位でオブジェクトを取得し、どの単位で保存するのかを明確にすることが重要です。その単位が集約であり、集約への入口が集約ルートです。
2. なぜリポジトリはDDDに必要なのか
リポジトリが必要になる理由は、ドメイン層が永続化の詳細を直接扱うと、設計が壊れやすくなるからです。業務ルールを表すコードの中にSQL、ORMの遅延読み込み、テーブル結合、トランザクション制御、外部API呼び出しが入り込むと、ドメインモデルは技術的な都合に支配されます。その結果、モデルの意味が薄くなり、保守しにくくなります。
ドメイン駆動設計では、ドメインモデルをできるだけ業務概念に近い形で表現することが重視されます。リポジトリは、データを保存するための技術的な処理をドメインの外側へ押し出し、ドメイン層が業務ルールに集中できるようにします。これは、クリーンアーキテクチャやヘキサゴナルアーキテクチャの考え方とも相性が良い設計です。
2.1 ドメインがデータベースへ直接アクセスする問題
ドメインオブジェクトが直接SQLを発行したり、ORMのDbContextやSessionに依存したりすると、ドメイン層とデータアクセス層の結合度が高くなります。最初は実装が速く見えても、ビジネスルールが複雑になるにつれて、どこに業務ロジックがあるのか分かりにくくなります。
たとえば、注文エンティティの中にSQLが書かれていると、注文の業務ルールと保存方法が混ざります。後から保存先を変えたい、テストしたい、トランザクション境界を見直したいときに、ドメインコードそのものを変更しなければならなくなります。これは長期運用では大きな負債になります。
2.2 ORMの詳細が漏れる問題
ORMは便利ですが、ORMの概念がドメイン層に漏れると問題になります。遅延読み込み、トラッキング、プロキシ、ナビゲーションプロパティ、永続化状態などがドメインモデルの設計に影響しすぎると、ドメインモデルが業務ではなくORM都合で作られてしまいます。
リポジトリは、ORMを完全に消すものではありませんが、ORMの詳細をインフラストラクチャ層に閉じ込めるための境界になります。ドメイン層では、注文を取得する、顧客を保存する、契約を再構築するという業務的な操作に集中し、ORM固有の操作はリポジトリ実装側で扱います。
2.3 リポジトリが解決すること
リポジトリは、永続化非依存、境界の明確化、ドメインの隔離、テスト容易性の向上を実現します。アプリケーション層は、リポジトリインターフェースを通じて必要な集約を取得し、業務操作を実行し、保存します。この流れにより、ユースケースのコードも読みやすくなります。
また、リポジトリは永続化技術の差し替えを助けます。SQL、NoSQL、外部API、イベントストアなど、実際の保存先が変わっても、ドメイン層の考え方を保ちやすくなります。もちろん差し替えは設計次第で難しくなる場合もありますが、少なくとも依存方向を制御するための重要な仕組みになります。
3. リポジトリと集約
リポジトリを正しく理解するには、集約を理解する必要があります。ドメイン駆動設計において、集約とは、一貫性を守るためにまとめられたエンティティや値オブジェクトのまとまりです。集約には入口となる集約ルートがあり、外部から集約内部を変更する場合は、原則として集約ルートを通じて行います。
リポジトリは、この集約ルートを扱います。つまり、リポジトリはデータベース上のすべてのテーブルやすべてのエンティティに対して作るものではありません。集約単位で取得・保存し、業務ルールの整合性を守るための入口として設計します。
| 概念 | 日本語表現 | リポジトリとの関係 |
|---|---|---|
| Aggregate | 集約 | 一貫性を守る業務単位 |
| Aggregate Root | 集約ルート | リポジトリが取得・保存する対象 |
| Entity | エンティティ | 集約内部に含まれる場合がある |
| Value Object | 値オブジェクト | 集約内部の値として扱われる |
| Consistency Boundary | 一貫性境界 | 集約が守る整合性の範囲 |
| Business Rules | 業務ルール | 集約ルートが保護する |
3.1 集約が表すもの
集約は、ビジネス上の一貫性を守る境界です。たとえば注文システムでは、注文、注文明細、配送先、割引、支払い状態などが一つの注文集約に含まれる場合があります。このとき、外部のコードが注文明細だけを勝手に変更できると、注文全体のルールが壊れる可能性があります。
集約は、その内部で守るべきルールを持ちます。注文金額が負にならない、キャンセル済み注文には商品を追加できない、支払い済み注文の請求情報は勝手に変更できない、といったルールは集約の中で保護されるべきです。リポジトリは、このような集約を取得し、保存するための入口になります。
3.2 リポジトリは集約ルートだけを扱う
ドメイン駆動設計では、リポジトリは原則として集約ルートを扱います。集約内部の子エンティティを直接リポジトリから取得したり保存したりすると、集約ルートが守るべき一貫性を迂回してしまいます。その結果、ビジネスルールが破られやすくなります。
たとえば、注文集約の中に注文明細がある場合、注文明細リポジトリを作って注文明細だけを直接更新すると、注文全体の合計金額や状態遷移ルールと矛盾する可能性があります。そのため、注文に関する変更は注文集約ルートを通じて行い、注文リポジトリで保存するのが自然です。
3.3 一つの集約に一つのリポジトリ
一般的には、一つの集約に対して一つのリポジトリを設計します。たとえば、注文集約には注文リポジトリ、顧客集約には顧客リポジトリ、商品集約には商品リポジトリを用意します。リポジトリ名も、ドメインの言葉に合わせることが重要です。
ここで重要なのは、データベースのテーブル数とリポジトリ数を一致させないことです。テーブルが10個あるからリポジトリも10個作るのではなく、集約として意味のある単位でリポジトリを設計します。これにより、データ中心ではなくドメイン中心の設計になります。
4. DDDアーキテクチャにおけるリポジトリの位置
リポジトリは、ドメイン層、アプリケーション層、インフラストラクチャ層の境界に関わる重要な要素です。インターフェースはドメイン層またはアプリケーション層に置かれ、実装はインフラストラクチャ層に置かれることが多いです。これにより、上位層は永続化の詳細に依存しなくなります。
アプリケーション層は、ユースケースの流れを調整します。必要な集約をリポジトリから取得し、ドメインオブジェクトに業務操作を依頼し、最後にリポジトリや作業単位を通じて保存します。リポジトリは、ユースケースと永続化技術の間にある橋渡しとして機能します。
| 層 | 主な責務 | リポジトリとの関係 |
|---|---|---|
| ドメイン層 | 業務ルール、集約、値オブジェクト | リポジトリインターフェースを持つ場合がある |
| アプリケーション層 | ユースケースの調整 | リポジトリを利用して集約を取得・保存する |
| インフラストラクチャ層 | DB、ORM、外部API、永続化実装 | リポジトリ実装を持つ |
| プレゼンテーション層 | API、UI、入力受付 | 直接リポジトリへ依存しないのが望ましい |
4.1 ドメイン層における役割
ドメイン層にリポジトリインターフェースを置く場合、そのインターフェースは永続化技術ではなく、ドメインの要求を表現します。たとえば、注文をIDで取得する、顧客を保存する、有効な契約を取得する といった操作です。
このとき、インターフェースにSQLやORMの概念を漏らしてはいけません。IQueryable やデータベース固有の条件をそのまま返すと、ドメイン層が永続化技術に依存してしまいます。リポジトリインターフェースは、ドメインの言葉で表現されるべきです。
4.2 インフラストラクチャ層における役割
インフラストラクチャ層には、リポジトリの具体的な実装を置きます。ここでは、SQL、ORM、NoSQL、外部API、ファイルストレージ、イベントストアなど、実際のデータアクセス処理を扱います。ドメイン層が知らなくてよい技術的な詳細は、この層に閉じ込めます。
たとえば、注文リポジトリの実装では、Entity FrameworkやHibernateを使って注文集約を読み込み、必要な関連データを復元し、変更をトランザクション内で保存します。アプリケーション層から見れば、具体的な永続化方式は隠蔽されています。
4.3 アプリケーション層における役割
アプリケーション層は、リポジトリを使ってユースケースを実行します。たとえば「注文をキャンセルする」ユースケースでは、注文リポジトリから注文集約を取得し、注文集約のキャンセルメソッドを呼び出し、変更を保存します。
ここで重要なのは、業務ルールをアプリケーション層に書きすぎないことです。アプリケーション層は手順を調整する場所であり、注文がキャンセル可能かどうかの判断は注文集約に置くべきです。リポジトリは、あくまで集約の取得と保存を支える役割です。
5. リポジトリの基本構成
リポジトリは、主にインターフェース、実装、マッピングの三つで構成されます。インターフェースはアプリケーションやドメインから見える契約であり、実装はデータベースやORMを使って実際に処理を行う部分です。マッピングは、永続化モデルとドメインモデルを対応付ける役割を持ちます。
良いリポジトリ設計では、インターフェースがシンプルで、ドメインの言葉に沿っており、実装の詳細を漏らしません。逆に、インターフェースが巨大化し、あらゆる検索条件やCRUD操作を詰め込むと、リポジトリは保守しにくくなります。
| 構成要素 | 役割 | 注意点 |
|---|---|---|
| リポジトリインターフェース | ドメイン側から見える契約 | 永続化技術を漏らさない |
| リポジトリ実装 | DBや外部APIにアクセスする | インフラ層に置く |
| ドメインモデル | 業務ルールを表す | ORM都合に引きずられすぎない |
| 永続化モデル | テーブルやドキュメント構造に近いモデル | ドメインモデルと分ける場合がある |
| マッピング | 永続化モデルとドメインモデルを変換 | 複雑になりすぎないようにする |
5.1 リポジトリインターフェース
リポジトリインターフェースには、集約を取得・保存するための最小限のメソッドを定義します。よくある例としては、IDによる取得、保存、削除、存在確認などがあります。ただし、すべてのリポジトリに同じメソッドを機械的に入れる必要はありません。
重要なのは、その集約にとって意味のある操作を定義することです。注文リポジトリであれば、注文IDで取得する、顧客の未完了注文を取得する のような業務的な意味を持つメソッドが自然です。反対に、汎用的な GetAll を安易に置くと、不要な大量取得やドメイン境界の崩れにつながることがあります。
5.2 リポジトリ実装
リポジトリ実装は、インフラストラクチャ層に置かれ、実際のデータアクセスを担当します。SQLを使う場合もあれば、ORMを使う場合もあり、NoSQLや外部APIから集約を再構築する場合もあります。実装は技術的な詳細を持ちますが、その詳細を上位層へ漏らさないことが大切です。
実装では、集約を正しく再構築することが重要です。集約内部のエンティティや値オブジェクトが不完全な状態で復元されると、業務ルールが正しく動かない可能性があります。保存時にも、集約の一貫性を壊さないようにトランザクションや作業単位と連携する必要があります。
5.3 ドメインモデルと永続化モデルのマッピング
ドメインモデルと永続化モデルは、必ずしも同じ形である必要はありません。ドメインモデルは業務ルールを表現するために設計され、永続化モデルはデータベースや保存形式に合わせて設計されます。両者を分けることで、ドメインモデルをより純粋に保てる場合があります。
ただし、分離しすぎるとマッピングコストが高くなります。小規模なシステムでは、ORMエンティティとドメインモデルをある程度共有する判断もあります。重要なのは、どちらが正しいかを固定的に考えるのではなく、ドメインの複雑さ、保守性、チームのスキル、変更頻度を見て判断することです。
6. リポジトリと集約ルート
リポジトリが集約ルートだけを扱うという原則は、ドメイン駆動設計において非常に重要です。集約ルートは、集約内部の整合性を守る入口です。外部のコードが集約内部の子エンティティを直接取得・変更できると、集約ルートが持つ業務ルールを迂回してしまいます。
この原則を守ることで、状態変更の経路が明確になります。注文集約を変更するなら注文集約ルートを通じて行う、口座集約を変更するなら口座集約ルートを通じて行う、という形になります。リポジトリは、その入口となる集約ルートを永続化するための仕組みです。
6.1 なぜ集約ルートだけを取得するのか
集約ルートだけを取得する理由は、一貫性を守るためです。集約内部のオブジェクトには、単独では意味が不十分なものがあります。たとえば注文明細は、注文全体の状態、合計金額、キャンセル状態、在庫引当などと関係します。注文明細だけを独立して変更すると、注文全体の整合性が壊れる可能性があります。
集約ルートを通じて変更することで、業務ルールを一箇所に集められます。注文に商品を追加する、注文をキャンセルする、支払いを確定する、といった操作は注文集約ルートに置かれます。リポジトリは、この集約ルートを取得し、保存することでルールの適用を支えます。
6.2 よくある誤り
よくある誤りは、すべてのエンティティにリポジトリを作ることです。注文リポジトリ、注文明細リポジトリ、配送先リポジトリ、割引リポジトリを個別に作ると、集約の境界が曖昧になります。結果として、どこからでも内部状態を変更できる設計になってしまいます。
もう一つの誤りは、値オブジェクトにリポジトリを作ることです。値オブジェクトは識別子ではなく値で意味を持つため、通常は単独でリポジトリから取得する対象ではありません。値オブジェクトは集約内部の一部として扱われるべきです。
6.3 リポジトリが守るべき境界
リポジトリは、集約境界を崩さないように設計する必要があります。外部のコードが集約内部の構造に依存しすぎると、集約の設計変更が難しくなります。リポジトリは集約を丸ごと扱い、内部の永続化詳細は外側へ漏らさないようにします。
この境界が守られていると、ドメインモデルの変更に強くなります。内部の子エンティティや値オブジェクトの構成が変わっても、外部から見たリポジトリの契約が安定していれば、影響範囲を小さくできます。
7. リポジトリと作業単位
作業単位は、複数の変更を一つのトランザクションとして管理する考え方です。オブジェクトの変更を追跡し、最後にまとめて保存することで、整合性を保ちます。ORMでは、作業単位に相当する仕組みが内部に用意されている場合があります。
リポジトリと作業単位は相性が良い組み合わせです。リポジトリは集約の取得・保存を担当し、作業単位は変更の確定、取り消し、トランザクション管理を担当します。特に複数の集約や複数のリポジトリが同じユースケースで使われる場合、作業単位の考え方が重要になります。
| 要素 | 役割 |
|---|---|
| リポジトリ | 集約の取得・保存の入口 |
| 作業単位 | 変更追跡とトランザクション管理 |
| Commit | 変更を確定する |
| Rollback | 変更を取り消す |
| ORM Context / Session | 作業単位の実装に近い役割を持つことがある |
7.1 作業単位が表すもの
作業単位は、ユースケース内で発生した変更を一つのまとまりとして扱います。たとえば、注文を作成し、在庫を引き当て、支払い予約を行う処理では、途中で失敗した場合に全体を取り消す必要があるかもしれません。このような場合、作業単位は整合性を保つための重要な役割を持ちます。
ただし、すべての処理を一つの巨大なトランザクションにすればよいわけではありません。特に分散システムでは、複数サービスをまたぐ強いトランザクションは難しくなります。作業単位は、単一サービス内や単一境界内での変更管理として考えるのが現実的です。
7.2 リポジトリと作業単位の連携
リポジトリは集約を読み込み、アプリケーション層は集約に業務操作を行い、作業単位が最終的に変更を確定します。この流れにより、ユースケースの処理が明確になります。保存処理を各メソッド内でバラバラに実行するよりも、最後にまとめて確定するほうがトランザクション境界を管理しやすくなります。
たとえば、注文を取得する → 注文をキャンセルする → 返金情報を記録する → 変更を確定する という流れでは、リポジトリと作業単位が協力します。リポジトリは対象の集約を扱い、作業単位は一連の変更をまとめて保存します。
7.3 ORMと作業単位
Entity Framework、Hibernate、NHibernateなどのORMは、変更追跡やトランザクション管理の仕組みを持っています。そのため、ORMのContextやSessionが作業単位に近い役割を果たすことがあります。これにより、明示的な作業単位クラスを作らなくてもよい場合があります。
ただし、ORMの仕組みをそのままドメイン層に漏らすと、ドメインモデルがORMに依存してしまいます。作業単位を抽象化するか、アプリケーション層で扱うか、ORMのContextをどこまで見せるかは、アーキテクチャ全体の方針に合わせて決める必要があります。
8. リポジトリとCQRS
CQRSは、書き込み側と読み取り側のモデルを分ける設計です。複雑な業務ルールを持つ書き込み処理では集約とリポジトリを使い、読み取り処理では画面表示やレポートに最適化された読み取りモデルを使うことがあります。これにより、書き込み側の整合性と読み取り側の性能を別々に最適化できます。
リポジトリは、CQRSにおいて主にコマンド側で使われます。つまり、状態を変更する処理で集約を取得し、業務ルールを実行し、保存するために利用されます。一方、クエリ側では、リポジトリではなく専用の読み取りクエリ、ビュー、読み取りモデル、SQL、検索インデックスなどを使うことがあります。
| 領域 | 主な目的 | リポジトリの必要性 |
|---|---|---|
| コマンド側 | 状態変更、業務ルール、一貫性 | 高い |
| クエリ側 | 表示、検索、集計、レポート | 必ずしも必要ではない |
| 読み取りモデル | UIやAPIに最適化されたデータ | 専用クエリの方が自然な場合がある |
| 集約 | 書き込み時の一貫性を守る | リポジトリで取得・保存する |
8.1 CQRSが表す考え方
CQRSは、コマンドとクエリの責務を分離する考え方です。コマンドは状態を変更する操作であり、クエリは状態を読み取る操作です。単純なシステムでは同じモデルで両方を扱っても問題ありませんが、複雑なシステムでは読み取りと書き込みの要求が大きく異なることがあります。
たとえば、注文の作成やキャンセルには厳密な業務ルールが必要ですが、注文一覧画面では高速な検索や集計が必要です。この二つを同じモデルで無理に扱うと、どちらにも中途半端な設計になることがあります。CQRSは、この問題を分けて考えるための選択肢です。
8.2 コマンドモデルにおけるリポジトリ
コマンドモデルでは、リポジトリは集約の永続化に使われます。アプリケーション層はリポジトリから集約を取得し、集約に対して業務操作を行い、変更を保存します。ここでは、一貫性と業務ルールが最も重要です。
たとえば、銀行口座から出金する処理では、口座集約を取得し、残高不足でないかを確認し、出金を記録し、保存します。このような処理では、リポジトリが集約ルートを取得・保存する役割を持ちます。
8.3 クエリ側にリポジトリは必要か
クエリ側では、必ずしもリポジトリが必要とは限りません。読み取り専用の画面表示やレポートでは、集約を復元するよりも、必要な列だけをSQLで取得したり、検索インデックスやビューを使ったりする方が効率的な場合があります。
これはリポジトリを否定するものではありません。書き込み側で集約の整合性を守るためにリポジトリを使い、読み取り側では専用の読み取りモデルを使うという役割分担です。複雑なシステムでは、この分離が保守性と性能の両方に役立つことがあります。
9. リポジトリとORM
ORMは、オブジェクトとリレーショナルデータベースを対応付ける仕組みです。Entity Framework、Hibernate、NHibernateなどが代表例です。ORMを使うと、SQLを直接書かずにオブジェクトとしてデータを扱えるため、リポジトリ実装を作りやすくなります。
一方で、ORMを使っているからリポジトリが常に不要になるとは限りません。ORMのContextやSessionがすでにリポジトリや作業単位に近い役割を持つため、追加のリポジトリが重複になる場合もあります。しかし、ドメイン境界を明確にしたい場合や、集約単位の保存を表現したい場合には、リポジトリが有効です。
| 観点 | ORMのみ | リポジトリ併用 |
|---|---|---|
| 実装速度 | 速い | 少し設計が必要 |
| ドメイン境界 | 漏れやすい場合がある | 明確にしやすい |
| テスト容易性 | ORM依存になりやすい | 差し替えやすい |
| 複雑な集約 | 設計が曖昧になりやすい | 集約単位で扱いやすい |
| 単純CRUD | 十分な場合が多い | 過剰設計になる場合がある |
9.1 Entity Frameworkとリポジトリ
Entity Frameworkは、.NETでよく使われるORMです。DbContextは変更追跡やトランザクション管理を行うため、作業単位に近い役割を持ちます。そのため、単純なCRUDアプリケーションでは、DbContextを直接使う方がシンプルな場合があります。
一方で、ドメイン駆動設計を重視する場合は、DbContextをドメイン層へ直接漏らさないようにすることが重要です。リポジトリを通じて集約を取得・保存することで、アプリケーション層やドメイン層がEntity Frameworkの詳細に依存しにくくなります。
9.2 Hibernateとリポジトリ
Hibernateは、Java系の代表的なORMです。Sessionを通じてオブジェクトを読み書きし、変更追跡や遅延読み込みを扱います。Hibernateを使う場合も、ドメインモデルと永続化の境界をどう保つかが重要です。
Hibernateの機能をそのまま便利に使うと、ドメインモデルが遅延読み込みや永続化状態に依存しやすくなります。リポジトリを設けることで、ドメイン側から見える操作を限定し、集約単位の取得・保存を明確にできます。
9.3 ORM利用時にリポジトリは常に必要か
ORMを使う場合、リポジトリが常に必要とは限りません。単純なCRUDアプリケーションや管理画面では、ORMを直接使った方が分かりやすく、余計な抽象化を避けられる場合があります。リポジトリを作ることで、かえってコード量が増えることもあります。
しかし、業務ルールが複雑で、集約、一貫性、テスト容易性、永続化非依存を重視する場合は、リポジトリが有効です。判断基準は、技術的な好みではなく、ドメインの複雑さと長期保守の必要性です。
10. 汎用リポジトリはDDDに向いているのか
汎用リポジトリとは、Add、Update、Delete、FindById、GetAll のような共通操作を型ごとに使い回すリポジトリです。一見するとコードの重複を減らせる便利な仕組みに見えますが、ドメイン駆動設計では注意が必要です。なぜなら、汎用リポジトリはドメイン固有の意味を薄めやすいからです。
DDDにおけるリポジトリは、単なるCRUD操作ではなく、集約を業務的な意味で扱うためのものです。注文リポジトリ、顧客リポジトリ、契約リポジトリには、それぞれ異なる業務上の意味があります。すべてを汎用メソッドに押し込むと、ユビキタス言語が失われ、ドメインモデルが貧血化しやすくなります。
| 比較項目 | 汎用リポジトリ | ドメイン特化リポジトリ |
|---|---|---|
| 主な目的 | CRUD共通化 | 集約の意味を表現 |
| メソッド名 | Add, Update, Delete, GetAll | FindActiveSubscription, FindPendingOrderなど |
| ドメイン表現 | 弱くなりやすい | 強く表現しやすい |
| 初期実装 | 速い | 設計が必要 |
| 複雑な業務ルール | 不向きになりやすい | 向いている |
| DDDとの相性 | 注意が必要 | 良い |
10.1 汎用リポジトリが表すもの
汎用リポジトリは、データアクセス操作を共通化するための仕組みです。多くのエンティティに対して同じようなCRUDメソッドを提供し、実装の重複を減らします。単純な管理画面やデータ中心のアプリケーションでは有効な場合があります。
しかし、ドメイン駆動設計では、すべてのオブジェクトを同じCRUD操作で扱うことが問題になる場合があります。集約には固有のルールや意味があるため、汎用リポジトリだけでは表現力が不足しがちです。
10.2 汎用リポジトリの問題
汎用リポジトリの問題は、ドメインの言葉が消えることです。GetAll、Update、Delete のようなメソッドだけでは、なぜその集約を取得するのか、どの業務条件で探すのかが分かりません。結果として、業務ルールがアプリケーション層やサービス層に流れ出しやすくなります。
また、汎用リポジトリはORMのラッパーになりがちです。ORMがすでに提供している機能を薄く包むだけなら、抽象化の価値は低くなります。むしろ、余計な層が増えてデバッグや最適化が難しくなることがあります。
10.3 ドメイン特化リポジトリの利点
ドメイン特化リポジトリは、ユビキタス言語を反映できます。たとえば、FindUnpaidOrders、FindActiveSubscriptions、FindCustomerByEmail のように、業務上の意味を持つメソッドを定義できます。これにより、コードを読む人が意図を理解しやすくなります。
もちろん、すべてのメソッドをリポジトリに詰め込むべきではありません。複雑な読み取り処理はクエリサービスや読み取りモデルに分けることもあります。重要なのは、リポジトリをドメインの言葉で設計し、単なるCRUD部品にしないことです。
11. DDDにおけるリポジトリのベストプラクティス
良いリポジトリ設計には、いくつかの原則があります。集約単位で設計する、インターフェースをシンプルに保つ、業務ロジックを入れない、複雑な読み取りクエリをドメインに持ち込まない、ユビキタス言語を使う、といった点です。これらを守ることで、リポジトリはドメインモデルを支える健全な境界になります。
リポジトリは、便利だから何でも入れる場所ではありません。データアクセス、検索条件、業務判断、DTO変換、画面都合の整形をすべて詰め込むと、すぐに肥大化します。リポジトリの責務を明確にし、必要に応じてクエリサービス、アプリケーションサービス、ドメインサービスと役割を分けることが重要です。
| ベストプラクティス | 意図 |
|---|---|
| 集約単位で設計する | 一貫性境界を守る |
| インターフェースを小さくする | 依存をシンプルにする |
| 業務ロジックを入れない | ドメインモデルの責務を守る |
| 複雑な読み取りは分ける | CQRSやクエリサービスと役割分担する |
| ユビキタス言語を使う | 業務意図をコードに反映する |
| ORM詳細を漏らさない | 永続化非依存を保つ |
11.1 集約単位で設計する
リポジトリは集約単位で設計します。データベースのテーブル単位や画面単位で作るのではなく、業務上一貫性を守るべき単位を基準にします。これにより、状態変更の入口が集約ルートに集まり、業務ルールが保護されます。
たとえば、注文に関する変更は注文リポジトリと注文集約ルートを通じて行います。注文明細や配送先を個別のリポジトリで直接更新しないことで、注文全体の整合性を保ちやすくなります。
11.2 インターフェースをシンプルに保つ
リポジトリインターフェースは、必要最小限に保つべきです。あらゆる検索条件や更新操作を詰め込むと、インターフェースが肥大化し、使い方が分かりにくくなります。メソッドは、その集約にとって意味のあるものに絞ります。
特に GetAll のようなメソッドは注意が必要です。集約をすべて取得する必要が本当にあるのか、読み取り専用のクエリとして分けるべきではないかを検討します。DDDでは、リポジトリは便利なデータ取得窓口ではなく、集約を扱う境界です。
11.3 業務ロジックを入れない
リポジトリに業務ロジックを入れると、ドメインモデルが貧血化します。リポジトリは、集約を取得・保存するための仕組みであり、注文がキャンセル可能か、口座から出金できるか、契約を更新できるかといった判断は集約やドメインサービスに置くべきです。
リポジトリに条件分岐が増え、業務ルールが入り始めたら注意が必要です。そのロジックは本当に永続化に関するものなのか、それともドメインルールなのかを見直しましょう。
11.4 複雑な読み取りクエリをドメインに入れない
画面表示やレポート用の複雑な読み取りクエリを、リポジトリやドメインモデルに詰め込みすぎると、設計が複雑になります。特に一覧画面、検索、集計、ダッシュボードは、集約を完全に復元するよりも専用の読み取りモデルを使った方が効率的な場合があります。
CQRSを採用する場合、書き込み側ではリポジトリと集約を使い、読み取り側では専用クエリや読み取りモデルを使います。これにより、ドメインモデルを業務ルールに集中させながら、読み取り性能も確保できます。
11.5 ユビキタス言語を使う
リポジトリの名前やメソッド名には、ユビキタス言語を反映するべきです。技術的な GetData や UpdateRecord ではなく、業務的な FindActiveSubscription や FindPendingOrders のように、ドメインの言葉で表現します。
これにより、コードが業務会話に近づきます。開発者だけでなく、業務担当者と会話するときにも、同じ言葉でモデルを説明しやすくなります。DDDにおけるリポジトリは、技術部品であると同時に、ドメインの言葉を保つための部品でもあります。
12. よくあるアンチパターン
リポジトリは便利なパターンですが、誤って使うと設計を悪化させます。代表的なアンチパターンには、すべてのエンティティにCRUDリポジトリを作る、汎用リポジトリを乱用する、DTOを返す、業務ロジックを入れる、UIに依存する、ORMの薄いラッパーにする、といったものがあります。
アンチパターンの多くは、リポジトリを「データアクセスをまとめる箱」として考えすぎることから生まれます。DDDにおけるリポジトリは、集約を保護するための境界です。この目的を忘れると、リポジトリはすぐに責務過多になります。
| アンチパターン | 問題 |
|---|---|
| CRUDリポジトリ乱立 | 集約境界が壊れる |
| 汎用リポジトリ乱用 | ドメインの意味が消える |
| DTOを返す | ドメインモデルを迂回する |
| 業務ロジックを含む | ドメイン層の責務が漏れる |
| UIに依存する | プレゼンテーション都合が混ざる |
| ORMの薄いラッパー | 抽象化の価値が低い |
12.1 CRUDリポジトリだらけにする
すべてのエンティティにCRUDリポジトリを作ると、集約の考え方が失われます。注文、注文明細、配送先、割引、支払い情報のそれぞれにリポジトリを作り、どこからでも更新できるようにすると、業務ルールを守る場所が曖昧になります。
単純なCRUDアプリケーションなら問題にならない場合もありますが、DDDを採用するほど複雑なドメインでは危険です。リポジトリはエンティティ単位ではなく、集約ルート単位で設計するべきです。
12.2 汎用リポジトリを乱用する
汎用リポジトリを使いすぎると、ドメイン固有の表現が消えます。すべてが Add、Update、Delete、GetAll になってしまうと、その操作が業務上どのような意味を持つのか分かりにくくなります。
コード量を減らすための抽象化が、ドメイン理解を妨げるなら本末転倒です。共通化は重要ですが、ドメインの言葉を失わない範囲で行う必要があります。
12.3 DTOを返すリポジトリ
リポジトリがDTOを返すと、ドメインモデルを通らずにデータだけを扱う流れになりやすくなります。特に書き込み側でDTOを返して処理すると、集約が持つ業務ルールを迂回してしまう可能性があります。
読み取り専用の画面表示ではDTOや読み取りモデルを使うことがあります。しかし、その場合はリポジトリではなくクエリサービスや読み取りモデルとして分ける方が設計が明確になります。
12.4 業務ロジックを含むリポジトリ
リポジトリに業務判断が入りすぎると、ドメインモデルが薄くなります。たとえば、注文がキャンセル可能かどうか、支払いが有効かどうか、契約を更新できるかどうかをリポジトリで判断すると、業務ルールの置き場所が分散します。
リポジトリは、業務ルールを実行するために必要な集約を取得し、保存する役割にとどめるべきです。判断は集約、値オブジェクト、ドメインサービスに置く方が自然です。
12.5 UIに依存するリポジトリ
画面表示に必要な形に合わせてリポジトリメソッドを増やしすぎると、リポジトリがUIに依存します。たとえば、特定画面専用の検索条件や表示項目をリポジトリに詰め込むと、UI変更のたびにリポジトリが変わるようになります。
このような読み取り処理は、クエリサービスや読み取りモデルに分ける方が適しています。リポジトリは、ドメインの状態変更に必要な集約を扱う役割に集中させましょう。
13. マイクロサービスと分散システムにおけるリポジトリ
マイクロサービスや分散システムでは、リポジトリの役割はさらに重要になります。各サービスは境界づけられたコンテキストを持ち、その中で有効なドメインモデルを管理します。リポジトリは、そのコンテキスト内で集約を保存・取得するための仕組みとして機能します。
一方で、分散システムでは単一データベース内のトランザクションだけでは解決できない問題が増えます。複数サービスにまたがる更新、イベント連携、結果整合性、分散トランザクションの回避などを考える必要があります。リポジトリは重要ですが、分散システム全体の整合性を一つで解決するものではありません。
13.1 境界づけられたコンテキスト内のリポジトリ
境界づけられたコンテキストは、特定のモデルが意味を持つ範囲です。たとえば、販売コンテキストの顧客と、請求コンテキストの顧客は、同じ名前でも異なる意味を持つ場合があります。リポジトリは、そのコンテキスト内の集約を扱います。
これにより、サービスごとのモデルを独立させやすくなります。他のサービスのテーブルへ直接アクセスするのではなく、自分のコンテキスト内のデータをリポジトリで扱い、他コンテキストとはAPIやイベントで連携するのが自然です。
13.2 イベントソーシングとの関係
イベントソーシングでは、現在の状態を保存するのではなく、状態変化のイベントを保存します。この場合、リポジトリはイベントストアからイベントを読み込み、集約を再構築し、新しいイベントを保存する役割を持つことがあります。
通常のリポジトリが行う「状態を読み込む・保存する」という処理とは少し異なりますが、ドメイン側から見ると、集約を取得し、変更を保存するという抽象は維持できます。永続化方式がイベントストアに変わっても、ドメイン層がその詳細を直接知る必要はありません。
13.3 分散トランザクションとの関係
マイクロサービスでは、複数サービスをまたぐ分散トランザクションは複雑になりやすく、避けられる場合は避けることが多いです。その代わりに、イベント、補償処理、結果整合性、サガパターンなどを使って整合性を保ちます。
リポジトリは、基本的には自分のサービス内、自分の境界内の集約を保存します。他サービスのデータを直接更新するのではなく、イベントを発行し、他サービスがそれを受け取って自分のリポジトリで処理する形が一般的です。
13.4 クラウドネイティブ環境でのリポジトリ
クラウドネイティブ環境では、データベース、メッセージキュー、オブジェクトストレージ、検索エンジン、外部APIなど、複数のデータソースを使うことがあります。リポジトリは、これらの技術的な違いをドメイン層から隠蔽するために役立ちます。
ただし、すべてを一つのリポジトリに隠せばよいわけではありません。検索エンジン、分析基盤、読み取りモデルなどは、書き込み用のリポジトリとは分ける方が自然な場合があります。クラウドネイティブでは、リポジトリの責務をより明確に分けることが重要です。
14. 実システムにおけるリポジトリ例
リポジトリは、業種やシステムによって形が変わります。EC、銀行、SaaSのようなシステムでは、それぞれ異なる集約が存在し、それに対応するリポジトリが設計されます。重要なのは、テーブルや画面ではなく、業務上の一貫性境界から考えることです。
実システムでは、リポジトリの粒度を誤ると問題が起きます。小さすぎると集約の整合性が壊れ、大きすぎると取得や保存が重くなります。ドメイン理解を深めながら、どの単位で一貫性を守るべきかを見極める必要があります。
| 領域 | 集約例 | リポジトリ例 |
|---|---|---|
| EC | 注文、顧客、商品 | 注文リポジトリ、顧客リポジトリ、商品リポジトリ |
| 銀行 | 口座、取引、顧客 | 口座リポジトリ、取引リポジトリ |
| SaaS | サブスクリプション、テナント、請求 | サブスクリプションリポジトリ、テナントリポジトリ |
| 予約 | 予約、リソース、利用者 | 予約リポジトリ、リソースリポジトリ |
| 教育 | コース、受講者、課題 | コースリポジトリ、受講者リポジトリ |
14.1 ECシステム
ECシステムでは、注文リポジトリ、顧客リポジトリ、商品リポジトリなどが考えられます。注文集約では、注文ステータス、注文明細、配送先、支払い状態、キャンセル可否などを扱います。注文に関する変更は、注文集約ルートを通じて行うのが自然です。
ただし、商品検索や注文一覧表示のような読み取り処理は、必ずしも集約を完全に復元する必要はありません。検索画面では、専用の読み取りモデルや検索インデックスを使う方が効率的な場合があります。
14.2 銀行システム
銀行システムでは、口座集約が重要な一貫性境界になります。出金、入金、残高確認、凍結、限度額チェックなど、強い業務ルールが存在します。口座リポジトリは、口座集約を取得し、業務操作後の状態を保存します。
このような領域では、リポジトリに業務ロジックを入れるのではなく、口座集約にルールを持たせることが重要です。リポジトリは、口座を正しく再構築し、永続化する役割に集中します。
14.3 SaaSシステム
SaaSでは、サブスクリプション、テナント、請求、ユーザー、権限などが重要な集約になります。たとえばサブスクリプション集約では、プラン変更、更新、解約、支払い状態、利用制限などを管理します。
テナントリポジトリは、マルチテナント環境で特に重要です。テナントごとのデータ境界を守り、誤って他テナントのデータにアクセスしないように設計する必要があります。SaaSでは、リポジトリ設計がセキュリティにも関係します。
15. リポジトリを使わない方がよい場合
リポジトリは強力なパターンですが、すべてのシステムで必要ではありません。単純なCRUDアプリケーション、読み取り専用システム、レポーティングシステム、データパイプラインのように、複雑なドメインルールや集約の一貫性が中心でない場合、リポジトリを導入すると過剰設計になることがあります。
設計パターンは、問題を解決するために使うべきです。リポジトリを使うこと自体が目的になると、コード量が増え、抽象化が増え、かえって理解しにくいシステムになります。ドメインの複雑さと設計コストのバランスを見て判断することが重要です。
| システム種別 | リポジトリの必要性 | 理由 |
|---|---|---|
| 単純CRUD | 低い場合がある | ORM直接利用で十分なことが多い |
| レポーティング | 低い | 読み取り最適化クエリが中心 |
| 読み取り専用 | 低い | 集約の状態変更がない |
| データパイプライン | 低い | ドメイン集約よりデータ変換が中心 |
| 複雑な業務システム | 高い | 集約、一貫性、業務ルールを守る必要がある |
15.1 単純なCRUDアプリケーション
単純なCRUDアプリケーションでは、リポジトリが不要な場合があります。管理画面でデータを登録・更新・削除するだけで、複雑な業務ルールがない場合、ORMを直接使った方がシンプルです。
このようなケースで無理にリポジトリ、作業単位、集約、ドメインサービスを導入すると、学習コストとコード量が増えます。ドメイン駆動設計は、複雑なドメインを扱うための設計であり、すべてのアプリに同じ重さで適用する必要はありません。
15.2 レポーティングシステム
レポーティングシステムでは、データの読み取り、集計、可視化が中心になります。集約を復元して業務操作を行うよりも、SQL、ビュー、データウェアハウス、検索エンジンを使って読み取りに最適化する方が自然です。
この場合、リポジトリよりもクエリサービスや読み取りモデルの方が適しています。書き込み側のドメインモデルと読み取り側のレポートモデルを分けることで、性能と保守性を両立しやすくなります。
15.3 読み取り専用システム
読み取り専用システムでは、状態変更がほとんどありません。業務ルールによる状態遷移がない場合、集約ルートを通じて保存する必要もありません。そのため、リポジトリの価値は限定的です。
ただし、読み取りデータの取得方法を抽象化したい場合は、クエリオブジェクトやデータアクセスサービスを作ることはあります。重要なのは、それをDDDのリポジトリと混同しないことです。
15.4 データパイプライン
データパイプラインでは、データの抽出、変換、ロードが中心になります。ここでは、ドメイン集約の一貫性よりも、データ量、変換処理、スケジューリング、再実行性、監視が重要です。
このような領域では、リポジトリパターンよりもETL/ELT設計、ストリーミング処理、ジョブ管理、データ品質チェックが重要になります。DDDのリポジトリを無理に適用すると、処理の本質とずれることがあります。
16. リポジトリと現代アーキテクチャ
リポジトリは、ドメイン駆動設計だけでなく、クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ、イベント駆動システム、AIネイティブアプリケーションとも関係します。共通する考え方は、ドメインやユースケースを外部技術の詳細から守ることです。
現代のアプリケーションでは、データベースだけでなく、外部API、検索エンジン、メッセージブローカー、AIサービス、クラウドストレージなど、多くの外部依存があります。リポジトリは、そのうち永続化に関わる依存を整理するための重要なパターンです。
16.1 クリーンアーキテクチャにおけるリポジトリ
クリーンアーキテクチャでは、依存関係は外側から内側へ向かいます。ドメインやユースケースは、データベースやUI、外部サービスの詳細に依存しません。リポジトリインターフェースは内側に置かれ、実装は外側に置かれます。
この構造により、ドメインやユースケースをテストしやすくなります。本物のデータベースを使わずに、テスト用リポジトリを差し替えてユースケースを検証できます。リポジトリは、依存性逆転の具体的な適用例として機能します。
16.2 ヘキサゴナルアーキテクチャにおけるリポジトリ
ヘキサゴナルアーキテクチャでは、アプリケーションの中心にドメインがあり、外部システムとはポートとアダプターで接続します。リポジトリインターフェースはポートとして機能し、データベース実装はアダプターとして機能します。
この考え方により、データベースを外部依存として扱えます。ドメイン側は、リポジトリというポートを通じて必要な集約を扱い、具体的なSQLやORMはアダプターに閉じ込められます。
16.3 イベント駆動システムにおけるリポジトリ
イベント駆動システムでは、状態変更後にドメインイベントを発行し、他のコンポーネントやサービスがそれを購読することがあります。リポジトリは、集約の保存とイベントの発行タイミングに関係する場合があります。
ただし、イベント発行をリポジトリに直接詰め込みすぎると責務が曖昧になります。ドメインイベント、作業単位、アウトボックスパターンなどを組み合わせ、保存とイベント連携の整合性を設計することが重要です。
16.4 AIネイティブアプリケーションにおけるリポジトリ
AIネイティブアプリケーションでは、リポジトリの対象が従来のリレーショナルデータだけではなくなる可能性があります。会話履歴、埋め込みベクトル、ドキュメント、検索インデックス、ユーザー行動データなど、複数のデータソースを扱うことがあります。
この場合でも、ドメインの中心にある集約や業務ルールを守る考え方は有効です。ただし、検索や推薦、ベクトル検索のような読み取り処理は、通常の集約リポジトリとは分けて設計する方が自然です。AI時代でも、リポジトリは「何を永続化し、どの境界を守るのか」を明確にするために役立ちます。
おわりに
ドメイン駆動設計におけるリポジトリは、ドメインと永続化をつなぐ重要な境界です。リポジトリを通じて集約を取得・保存することで、ドメインモデルはSQL、ORM、データベース構造、外部APIの詳細から独立しやすくなります。特に複雑な業務ルールを持つ企業システムでは、リポジトリはドメインモデルの純度、保守性、テスト容易性を支える重要な役割を持ちます。
ただし、リポジトリは万能ではありません。単純なCRUDアプリケーション、読み取り専用システム、レポーティング基盤では、リポジトリが過剰設計になる場合があります。重要なのは、集約、一貫性境界、業務ルール、永続化の複雑さを見極め、必要な場所にだけ適切に導入することです。良いリポジトリは、データベース操作を隠すだけでなく、ドメインの言葉と境界を守り、システムの長期的な進化を支えます。
EN
JP
KR