ドメイン駆動設計におけるアーキテクチャの比較(レイヤードアーキテクチャ vs クリーンアーキテクチャ)
近年ドメイン駆動設計が、驚くほど流行しています。
ドメイン駆動設計の登場当時、原典たるエヴァンス本ではレイヤードアーキテクチャを中心に解説されていましたが、 現在では、クリーンアーキテクチャが人気を集めているようです。(筆者はレイヤードアーキテクチャを好んでいます。)
レイヤードアーキテクチャの登場から、クリーンアーキテクチャの登場に至るまで、 ヘキサゴナルアーキテクチャ、オニオンアーキテクチャなど、様々なアーキテクチャが誕生しましたが、 いずれも、ドメイン中心の設計を支援するという目的は共通しています。
それではなぜ、クリーンアーキテクチャが生まれなければならなかったのか? 本稿では、その理由を考えてみます。
レイヤードアーキテクチャとクリーンアーキテクチャの実装における違い
各コンポーネントを、層状に配置するか、同心円状に配置するか。 これはそれぞれのアーキテクチャの違いを考えるうえで、本質的な違いではありません。
一度図については、頭から消してください。
それぞれを実践した方にとっては、ある程度イメージが湧くかと思いますが、実装上の大きな違いは以下の3点です。
- アプリケーション層(ユースケース層)の表現
- リポジトリの解釈
- DTOの要否
アプリケーション層(ユースケース層)の表現
アプリケーション層の表現に対する違いは、UMLのユースケース図を使用すると理解しやすいです。
<<ユースケース図を追加>>
レイヤードアーキテクチャにおけるアプリケーション層の表現
レイヤードアーキテクチャでは、ユースケース図における、サブジェクトをクラスとして表現し、ユースケースをメソッドとして表現します。
関連するコンテキストのユースケースが1つのクラスにまとまり、非常に見通しが良くなる一方で、 ユースケースの定義・分類が誤っていた場合、FatServiceを引き起こしてしまう可能性があります。
このような設計は、ユースケース設計の精度に強く依存し、クラス設計や責務分離に対する設計的成熟度が求められるため、 オブジェクト指向設計の原則を重視する文化圏との親和性が高いです。
一方で、オブジェクト指向設計の原則よりも、生産性を重視している仕組み(ActiveRecordなど)を前提とした 言語やフレームワークを中心としている文化圏とは、親和性はやや低くなります。
クリーンアーキテクチャにおけるアプリケーション層の表現
クリーンアーキテクチャでは、ユースケース図における各ユースケースを、個別のクラスとして表現します。
この設計方針は、処理ごとにクラスを分離するという明確なルールによって構造の一貫性を保ちやすくなる一方で、 同一コンテキストに属するユースケースのまとまりが失われやすく、アプリケーション層全体の見通しを悪化させる可能性があります。
このような構造的分断は、ドメイン貧血症(ドメインに関連する操作が凝集しておらず、散在している状態)の アプリケーション層版とも言える現象を引き起こしやすくなります。
リポジトリの解釈
リポジトリの解釈は、レイヤードアーキテクチャとクリーンアーキテクチャで根本的に異なります。
レイヤードアーキテクチャにおけるリポジトリの解釈
レイヤードアーキテクチャにおけるリポジトリは、ドメインオブジェクトの集合(集約)に対するアクセスを抽象化したインターフェースです。 リポジトリはドメインの一部とみなされ、**ドメインモデルと密接に結びついた振る舞い(例:特定条件での検索、保存、削除)**を持ちます。
そのため、リポジトリのインターフェースはドメイン層に配置されます。 具体的な永続化処理は、インフラ層で実装されます。
代表的なパッケージ構成の一例は、以下のとおりです。
src/
+- application/
+- domain/
| +- (ここにリポジトリインターフェースが配置されます)
+- infrastructure/
| +- jdbc/
| +- jpa/
+- presentation/
しかし、レイヤードアーキテクチャにおけるリポジトリの解釈は、原典であるエヴァンス本のドメイン駆動設計には忠実ですが、欠点もあります。 それはドメインオブジェクトの集合に対するアクセスしか取り扱えない、という点です。
例えば、ファイル入出力・Emailの送信・外部APIの呼び出しなど、インフラ層の実装が必要ではあるものの、 ドメインオブジェクトには適さない永続化を扱いたい場合に、この制約が課題となります。
レイヤードアーキテクチャでは、アプリケーション層からインフラ層の呼び出しは許容されているため、表現自体は可能です。 ただし、技術詳細が露出してしまうインフラ層への直接的な依存はできるだけ避けたいところです。
クリーンアーキテクチャにおけるリポジトリの解釈
クリーンアーキテクチャにおけるリポジトリは、アプリケーションサービスがインフラ層の処理に依存しないためのポート(インターフェース)として機能します。 これは永続化や外部システムとの連携といった入出力処理を、アプリケーション層から疎結合に呼び出すための手段です。
そのため、リポジトリのインターフェースはアプリケーション層に配置されます。 具体的な永続化処理は、レイヤードアーキテクチャ同様、インフラ層で実装されます。
したがって、クリーンアーキテクチャにおけるリポジトリは、 オブジェクトの集合としての意味やドメインの表現とは切り離され、インフラ抽象の一つとして位置づけられる傾向があります。
代表的なパッケージ構成の一例は、以下のとおりです。
src/
+- application/
| +- port/
| +- (ここにリポジトリインターフェースが配置されます)
+- domain/
+- infrastructure/
| +- jdbc/
| +- jpa/
+- presentation/
この構成は、原典であるエヴァンス本のドメイン駆動設計におけるリポジトリの解釈とは少々異なる部分がありますが、非常に合理的でもあります。
例えば、先に挙げたファイル入出力・Emailの送信・外部APIの呼び出しといった、 ドメインオブジェクトの集合ではないものに対する永続化表現として、理に適った答えの一つと言えるでしょう。
DTOの要否
DTOの要否は、その使用目的から実装上の違いにつながっている部分があります。
レイヤードアーキテクチャにおけるDTOの要否
レイヤードアーキテクチャでは、DTOを必ずしも必要とはしていません。
DTOは、異なるパラメーター構造を持ったオブジェクト同士の間で、データをやり取りするのに使用されます。
クリーンアーキテクチャにおけるDTOの要否
クリーンアーキテクチャでは、DTOがほぼ必須のものとなっています。 これは各層同士の境界を明確に表現する目的で使用されているためです。
プレゼンテーション層とアプリケーション層との間をRequest, Response、 アプリケーション層とインフラ層との間をInput, Outputという形で、DTOをそれぞれ定義します。
これは構造上の規則であるため、必要がないと感じられる場面でも、原則作成する必要があります。
ハイブリッド構成の提案
ここまで整理してきたように、レイヤードアーキテクチャにもクリーンアーキテクチャにも評価すべき点は存在し、またそれぞれに課題もあります。
レイヤードアーキテクチャはドメインを中心に据える以外の制約は少なく、 オブジェクト指向設計に精通したプログラマーがそれぞれで適切に考え、設計していくことを前提としています。
これに対してクリーンアーキテクチャは、一貫した構造規則を適用することで、 プログラマーの成熟度に影響されづらく、考える余地を極力減らした形になっています。
それぞれの違いのうち、メリットとして抽出したいポイントを整理すると、以下のような点が挙げられます。
- レイヤードアーキテクチャでは、UMLのユースケース図に対応し、アプリケーション層の凝集度を高め、見通しを良くするためのアプローチ
- クリーンアーキテクチャでは、ドメイン集合としての表現が可能かどうかに依らず、永続化に対する合理的かつ一貫したアプローチ
実際のプロジェクトにおいて、レイヤードアーキテクチャ・クリーンアーキテクチャという形式に縛られすぎて、 プロジェクトやドメインを見通しよく保つといった点を損なうべきではありません。
そこで、これらアプローチを組み合わせたハイブリッド構成を、以下のとおり提案します。
src/
+- application/
| +- (ApplicationServiceはユースケースをメソッドとして表現します)
| +- port/
| +- (ここにドメイン以外の永続化を担当するリポジトリインターフェースが配置されます)
+- domain/
| +- (ここにドメインの集合を操作するリポジトリインターフェースが配置されます)
+- infrastructure/
| +- jdbc/
| +- jpa/
+- presentation/
なお今回のハイブリッド構成は、レイヤードアーキテクチャをベースに、レイヤードアーキテクチャで具体的な解決策が示されていない部分を、 クリーンアーキテクチャのアイデアで補った形になっています。
プロジェクトやチームの性質に応じて、クリーンアーキテクチャをベースにするもよし、適切に取捨選択を心がけるようにしてください。