DDDのリポジトリの外部依存とオブジェクト指向の原則の適用について

DDDのリポジトリがORMコンポーネントへ依存することの是非について、オブジェクト指向の原則の面から解説します。

リポジトリ(repository)とは、収納場所・倉庫・貯蔵庫を表す言葉です。

DDD(ドメイン駆動設計)では、リポジトリはモデル駆動設計でドメインをモデリングする際のビルディングブロックの1つになっています。ビルディングブロックとは基本構成要素のことで、ドメインをモデリングする際の基本部品として使います。

DDDのリポジトリの役目は、ドメインレイヤーのオブジェクトから永続化レイヤーを隠蔽することです。リポジトリ="エンティティの貯蔵庫"という抽象化されたオブジェクトを持ち込み、ドメインレイヤーの内部では貯蔵庫からエンティティを取り出すように設計・実装します。

構築するシステム(ここでは何か1つのシステムのみをイメージしてください)においてアーキテクチャが決定すると、その段階では永続化レイヤーに使われるコンポーネントが1つ選定されているでしょう。DDDのリポジトリは、ドメインレイヤーの中で永続化コンポーネントとのやり取りを担当します。根本的には、リポジトリは何らかの永続化コンポーネントに依存することになります。

依存関係逆転の原則

ドメインレイヤーはピュアに保つ。これがドメイン駆動設計における基本ポリシーです。しかし、外部とのやり取りを責務として持つオブジェクトについては、根本的に外部への依存を無くすことはできません。次の図のように、何らかのORMコンポーネントを利用した実装になるのは必然で、つまり依存が持ち込まれます。例えばSymfony Standard Editionの構成であれば、ORMコンポーネントにDoctrineが使われます。リポジトリは、Doctrineの提供するリポジトリ用の基底クラスであるEntityRepositoryを継承した実装になります。ドメインレイヤーの実装をドメインパッケージ、ORMはORMパッケージとしてまとめられていることを想定しています。

image

このように外部への依存を持ってしまった部分に対して、オブジェクト指向のクラス設計原則(SOLID原則)の1つである依存関係逆転の原則(DIP:Dependency Inversion Principle)を適用するとどうなるでしょうか。

依存関係逆転の原則では、直接依存しあう2つのクラスについて、間にインターフェイスを設け、2つのクラスそれぞれがインターフェイスに依存するように変更します。リポジトリの例では、ドメインパッケージには特定のリポジトリのインターフェイス(例:UserRepositoryInterface)のみを配置するようにします。リポジトリの実装は、ドメインパッケージの外に配置した実クラス(UserRepository)にて行います。このクラスが、ドメインパッケージにあるインターフェイスを実装し、ORMコンポーネントの基底クラスを継承します。こうすることで、リポジトリの実装をドメインパッケージの外に出すことはできました。

それでは、ドメインパッケージの外に出たリポジトリの実装は、どこに属するのでしょうか?

リポジトリの実装は、リポジトリパッケージに配置することにします。このパッケージはドメインパッケージから依存関係の理由で分割したというだけであり、依然としてドメインレイヤーに属するものだということに注意してください。リポジトリパッケージを切り離したドメインパッケージは、外部依存を持たなくなり、凝集度が高くなっています。

image

再利用・リリース等価の原則

しかし、ドメインレイヤーのオブジェクトは、それぞれ変更されていく可能性が高いものです。リポジトリの持つメソッドを追加したいということも出てくるでしょう。この場合、ドメインパッケージに属するリポジトリのインターフェイスと、リポジトリパッケージに属するリポジトリ実クラスの両方を修正しなくてはなりません。つまり、個別のリポジトリのインターフェイスというのは不安定(=変更が発生しやすい)なものだったわけです。不安定なものを抽象化することは、無理やり安定させようとするため矛盾・ひずみを生みます。結局ドメインの問題についての変更要求でドメインパッケージとリポジトリパッケージの両方に変更を加え、両方を同時にリリースするようになるでしょう。したがって、再利用・リリース等価の原則(REP:Reuse-Release Equivalency Principle)と照らしあわせても、これらは1つのパッケージにある方が自然と言えます。

また、現実のプロジェクトでは、リポジトリの操作には特定の汎用知識(問い合わせ方法)が必要になるでしょう。この用語をリポジトリごと・プロジェクトごとに再定義するのは手間であり、また、多くの場合この問い合わせ言語はドメインの本質部分ではありません。ORMの提供する問い合わせ用メソッドを「問い合わせドメインの言語」として自ドメインに輸入してしまうのが現実的です。

Doctrineの提供するリポジトリ基底クラス(EntityRepository)のインターフェイスが安定しているということも前提になります。

まとめ

DDDのリポジトリとORMコンポーネントの関係に着目して依存について解説しました。依存の排除に固執しすぎると、本来注力すべき部分から外れてしまう場合があります。パッケージの安定度を見極め、それに合わせた抽象化にとどめておくことで、「変更しやすさ」を維持していくことが肝要です。

参考

パッケージの安定度・不安定度といった尺度については、Robert C. Martin著『アジャイルソフトウェア開発の奥義 第2版』20.5.1「安定性」、20.5.2「安定性の尺度(目安)」を参照してください。