Clean Architecture の概念を考察する

クリーンアーキテクチャについては MVC、MVP や MVVM などを学ぶ上で、ぼんやりとした理解をしている方が多いのではないでしょうか。そこで今回は、Robert C. Martin 氏のブログの解説を兼ねながら、噛み砕いた概念の考察を共有したいと思います。
![]()
from his blog
TL;DR ::before
前提として、これはあくまでも私が自分なりに概念を噛み砕いて具体的に開発に当てはめて考えたものであり、クリーンアーキテクチャにおける絶対的な真理だとは思っていません。
より深い洞察をお持ちの方には、是非ともその知見や貴重な経験を共有していただけたらと思います。私を含め、設計に悩み迷っているエンジニアは社会にあふれています。
明確な依存関係こそが鍵
Robert C. Martin 氏のブログによれば、最も大事とされている概念がこの『依存関係のルール』であるとされています。
We don’t want anything in an outer circle to impact the inner circles.
そのルールとは、『依存関係は外から内への一方通行』であること。つまり、内側のレイヤーは外のレイヤーのことを知っていてはいけない。MVP ならば、Model が View のコンポーネントなどについて知っていたり変更を加えたりしてはいけないということです。
逆に、Model は View の都合によってデータ構造を変えられたりしてはいけません。Model はあくまでもビジネスロジックを抽象化したものとして、データ構造はドメインに寄り添ったものとします。 View は渡されたデータを自分で使える形に変換するのが必要な場合がありますが、これを取り持つのが Presenter や ViewModel であり、データのフローおよび整形の責務を負います。
それでは、それぞれのレイヤーについて順番に紹介していきたいと思います。
四つのレイヤー
上の図からも見て取れるように、クリーンアーキテクチャは基本的に四つのレイヤーから成り立っています。また、Martin 氏は四つに限定する必要はなく、多くても良いとしていますが、あくまでもそれはそれぞれのレイヤーを細かい粒度で分けたものになるのではないかと思います。
それではアーキテクチャを構成する四つのレイヤーを紹介していきましょう。
エンティティ
これは「*企業( Enterprise )*にとってほとんど不変であるビジネスルール」を表します。アプリケーションの仕様によって左右されることのない不変の要素なので、例えば運送業で言えば受注先、運送業務、配達先、経理など、そのビジネスにとって必ず必要になる最も重要で根本的な概念を表すレイヤーを指します。
とは言え、そういったレイヤーを意識した基幹システムを作るわけでない場合も多いので、新規サービスの立ち上げなどの場合、そのアプリケーションで実現したいことにとって最も重要な、サービスの基軸となる概念とすればよいとしています。Twitter で考えると、ツイートやユーザーなどがそれに当たり、UI/UX やその他の機能は、外側のレイヤーに含まれます。
ユースケース
その「アプリだけに特有のビジネス」を指します。ユースケースは日本語でも使われているのでわかりやすいですが、UI 以外の細かい仕様を指します。具体的な業務の内容やフローをここで定義します。
この独立したレイヤーがエンティティや UI、DB の変更に影響されることはありません。ただし、アプリケーションの仕様変更などはユースケースに変更が加えられます。エンティティをより具体的なレベルで扱うロジックがこのレイヤーに置かれますが、次に紹介するインターフェースを交えての説明の方がわかりやすいと思います。
インターフェイスアダプター
このレイヤーは他の一般的な設計思想において ViewModel や Presenter、Controller などと呼ばれることもあります。データをエンティティに変換したり、ユースケースや UI が使いやすいように変換するような処理を行います。DB のデータの構造は必ずしもエンティティと一致するわけではない(例:インピーダンスミスマッチ)ので、これらの相互変換を取り持つのがこのレイヤーです。
例として、RDBMS に十分に正規化されたユーザーのデータが複数のテーブルに跨って保存されていた場合、そのテーブル定義をそのままエンティティとする(またはその逆のプロセスを行う)ことは出来ないことがほとんどであり、オブジェクト指向設計においては実際にはユーザエンティティが複数のテーブルをマージしたものとして定義されることの方が多いです(例:ユーザーとメンバーシップレベルなど)。このミスマッチを解消する手助けをする ORM モジ ュールや DAO はまさにこのレイヤーに当たります。データ構造とエンティティの表現が絶対的に一致しなくてはいけない訳ではないということを意識することによって、それぞれのレイヤーの独立性を高め、結合度を下げることが可能になります。
エンティティとユースケースを内側のバブルに入れておき、外側で起きていることを全く意識させないようにするためには、このレイヤーがとても重要です。データベースへのクエリや UI の表示項目の選択などは全てこのレイヤーで行い、エンティティとユースケースにはバブルの外の移ろいやすい世界を見せることのないように、アプリケーションを育てていくというイメージが出来ます。
フレームワークやドライバ
このレイヤーは一番外側に位置していて、UI やデータベースなど、変動の多い部分になります。その移ろいやすい性質上( UI もデータも良く仕様変更に晒される)、内側のレイヤーへの影響を抑えるためできる限り隔離する必要があります。このレイヤーのコンポーネントが話をできるのは一つ内側のインターフェイスであり、核となるレイヤーに直接関わることは出来ません。
レイヤーと抽象化
提唱者である Martin 氏は「内側のレイヤーほど抽象化が進む」としています。これはサークルの図を見ながらアプリケーションをイメージしてみればわかりやすいと思います。データや UI は最も具体的であり、エンティティは最も抽象度が高い。オブジェクト指向に慣れている人はすぐにイメージができると思いますが、『 Martin 』というデータは最も具体的で、それが抽象化されていくと、例として『ユーザー』というエンティティになります。
ユースケースはエンティティのより具体的な振る舞いやビジネスに関わるロジックを表現します。『振る舞い』というと、クラスに持たせるメソッドをイメージして、「クラスの振る舞いを全てユースケースに移譲する」と考えてしまうかもしれませんが、あくまでもそれは抽象度で分ける必要があり、アプリケーションの仕様によってその区切り方は決まるので、完全な正解はないと言えます。
例えば『ユーザー』というエンティティの抽象度では、changeName()
や usePoint()
など、単独のユーザで完結する振る舞いを持たせ、ユースケースに buyItemWithPoints()
などを持たせ、内部的にユーザの usePoint()
を呼び、外側のインターフェースを呼んで論理的なユーザデータを物理的なデータ構造に変換し、ドライバがデータベースに保存するというフローが考えられます。もちろん、同時に Presenter が UI を変更する通知をしていることも考えられます。
依存関係を維持するインターフェイス
単純に読み進めていくと、「思ったよりシンプルな構造じゃないか」と思ってしまうかもしれませんが、実際にアプリケーションを設計していくと、矛盾が生じます。そもそも依存関係の絶対的なルールは外側から内側への一方通行であり、内側のモジュールは外へのリファレンスを持ってはいけません。
上記の例で、「ユースケースが外側のインターフェースを呼ぶ」と書いた通り、インターフ ェースこそが逆方向へのコントロールを可能にし、かつ依存関係を保つものとされています。
We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to The Dependency Rule no matter what direction the flow of control is going in.
ここでいう『 dynamic polymorphism 』とはランタイムポリモーフィズムであり、実際にコードが実行されるまでは処理の内容がわからない、Java などのインターフェースを指します。十分に抽象化されたインターフェイスによって、内側のレイヤーが外側の要素に対して与える影響を最小限に抑えることができます。不要な情報や権限をユースケースやエンティティに与えることにより、内側の抽象度を下げてしまうと、仕様変更への対応で辛い思いをしたり、思わぬ脆弱性が判明したりすることになります。
レイヤー間で共有されるデータ
レイヤーを跨いでデータをやりとりする場合、Martin 氏が言うところの「列( row )の構造」を使うことはご法度とされています。「列の構造」とは「整形されていないデータ構造」のことで、データベースや API が直に返してくる JSON の配列や、エンティティなどです。
繰り返しになりますが、エンティティは外側のレイヤーであるデータベースが吐くデータの構造を知ってはいけません。仮に、もしデータベースが吐くデータの「列の構造」をエンティティがそのまま意識していたならば、その間のユースケース、インターフェイスもその構造を意識することになります。これはつまり、データベースに改修や変更が入った場合、その影響が全てのレイヤーに及ぶということです。
ゴールデンルールとして、共有する場合は必ず必要最低限の情報を持った DTO などに変換します。そうすることで、クリーンアーキテクチャの最大の武器である依存関係の明確化を守ることが出来るのです。
For example, many database frameworks return a convenient data format in response to a query. We might call this a RowStructure. We don’t want to pass that row structure inwards across a boundary. That would violate The Dependency Rule because it would force an inner circle to know something about an outer circle.
TL;DR ::after
MVP や MVC などをデフォルトの設計思想としているフレームワークがあふれている中で、クリーンアーキテクチャは何となくイメージしにくい印象を持つエンジニアは少なくないのではと思います。あなたもその一人で、この記事を読んで「理解が深まった」と思えたなら幸いです。
そもそも、下から上というレイヤー構造は見慣れていますが、内側から外側へという円状のレイヤー構造というのは最初は想像しにくいものです。「DB は一番下。UI は一番上」というイメージにとらわれてしまうと、なかなか理解しにくい設計思想だと思います。しかし、MVP などの構成がより「具体的」な実装構造だとすると、クリーンアーキテクチャは「 抽象度の面からレイヤーを分けたもの」だと考えると、より理解が深まるのではないでしょうか。
コンポーネントの設計で迷うことがあった時に、この「抽象度による分割」の概念というものが、暗い夜道を照らす灯火のようにより良い設計に導いてくれるのではないでしょうか。