こちらに詳細に記載しています。宜しければご参照ください。
概要
一見複雑に見えるクリーンアーキテクチャをSOLID原則を用いて、成り立ちをひも解いていきます。
背景
クリーンアーキテクチャを調べていくと下記のような概念図や構成図を見かけます。
言いたいことは何となく分かるのですが、初見でメリットが理解できませんでした。
本記事ではレイヤードアーキテクチャの欠点を
SOLID原則に沿って補完していくことで、クリーンアーキテクチャをひも解いていきたいと思います。
SOLID原則とは
ソフトウェアの拡張性、保守性等を担保し、メンテナンスしにくいプログラムになることを防ぐための原則です。
S:SRP、単一責任の原則
O:OCP、開放閉鎖の原則
L:LSP、リスコフの置換原則
I:ISP、インタフェース分離の原則
D:DIP、依存性逆転の原則
SOLID原則の詳細はこちらの記事が参考になります。
イラストで理解するSOLID原則
クリーンアーキテクチャが必要な理由
こちらの最も基本的なレイヤードアーキテクチャの欠点からクリーンアーキテクチャの必要性を見ていきます。
依存性逆転の原則 (DIP: Dependency Inversion Principle) その①
上図のレイヤードアーキテクチャの欠点の一つにDomain層が、Infrastructure層に依存していることが挙げられます。
安定依存の原則(SDP:The Stable Dependencies Principle)によれば
安定しているDomain層が、変更が多く不安定なInfrastructure層に依存していることは安定依存の原則に反しています。
この依存関係を解決する考え方として依存性逆転の原則があります。
依存性逆転の原則に従うとDomainとInfrastructureの関係は下記のようになります。
DomainとInfrastructureの間にInterfaceを設けます。
この構成によりDomainからInfrastructureを抽象化します
依存性逆転の原則 (DIP: Dependency Inversion Principle) その②
依存性逆転の原則により、DomainとInfrastructureの関係を逆転させました。
しかし、まだ完全には依存関係を切り離せていません。
下記の実装を見て見ます
DataAccessInterface interface = new Infrastructure();
interface.getData();
実装を考えるとDomainが、Data Access Interfaceを呼び出すときにInfrastructureをインスタンス化する必要があります。
そこでDomainからInfrastructureのインスタンスを隠蔽するため、
Infrastructureをインスタンス化するData Accessを配置することでこの問題を解決します。
DataAccessInterface interface = new DataAccess();
interface.getData();
@Override
public void int getData(){
Infrastructure infra = new Infrastructure();
infra.getDataFromInfra();
}
この構成によってDamainからInfrastructureを抽象化します
DataAccessのようなデータアクセス手段、データの永続化を抽象化するオブジェクトをRepositoryと呼びます。デザインパターンのリポジトリパターンとして知られています。
開放閉鎖の原則 (OCP:Open/Closed Principle)
次に視点を変えてUI(Presentation)とApplicationの依存関係に目を向けてみます。
一般的に、UIはユーザーの目に最も触れるため比較的変更が多く発生します。
このため、UIの変更をApplicationに波及させたくなく、
また、Applicationの変更にUIも振り回されたくありません。
こうした事態を解決する考え方として開放閉鎖の原則の考え方があります。
拡張に対して開いていなければならず、 修正に対して閉じていなければならない。という
開放閉鎖の原則に基づきApplication Interfaceを設けます。
合わせて"依存性逆転の原則 (DIP: Dependency Inversion Principle) その②"と同様にインスタンス化のControllerを設けます。
この構成により、変更の多いUI(Presentation)は拡張に対して開いており、修正に対して閉じている状態に変更されます。
単一責任の原則 (SRP:Single Responsibility Principle) その①
今度は依存関係ではなく、各モジュールの責務に目を向けてみます。
- 1.ユースケースの実行
- Applicationの要求に基づき、Data Access Interfaceからデータ取得
- 2.ビジネスロジックの実行
- Data Access Interfaceからのデータを加工する等ビジネスロジック実行
このためDomainは、2つの変更理由によって修正が加えられます。
この問題として、一方の修正によってもう一方のコードに予期せぬ影響を及ぼすかもしれません。
こうした問題への考え方として単一責任の原則があります。
この考え方によると一つのモジュールは二つ以上の変更理由で変更されてはなりません。
この考えに基づいて、Domainを分割します。
ビジネスルールを提供するモジュールをEntitiesと、
Data Access Interfaceを介してデータを取得するモジュールをUseCase Interactorとします。
単一責任の原則 (SRP:Single Responsibility Principle) その②
次にUI(Presentation)について考えます。
UI(Presentation)も責務が2つ存在します。
- 1.Viewからのイベント通知
- UI(Presentation)からのUIイベントをControllerに通知
- 2.Viewへのデータ解釈
- ControllerからのデータをUI(Presentation)に渡す
"単一責任の原則 (SRP:Single Responsibility Principle) その①"と同じ考え方に基づき分割します。
Viewへのデータ解釈を担うモジュールをPresenterとして分割します
インターフェース分離の原則(ISP:Interface Segregation Principle)
先ほど分割したControllerとPresenterについて考えます。
Controllerの責務はViewの状態を受け取ってApplication Interfaceを介して、
Applicationが要求するデータ形式でデータを引き渡すことです。
一方でPresenterの責務は、Controllerを介してApplicationのデータを受け取ることです。
このとき一つの問題が生じます。
Controllerに本来責務でない実装が入ってしまいます。
Application InterfaceのリターンをPresentationに渡す実装が必要となります。
こうした問題を解決する考え方にインターフェース分離の原則があります。
Controllerは
責務に沿ったInterfaceのみを呼び出せるようにInput Boundaryを配置されました。
Presenterも
責務に沿ったInterfaceのみを実装できるように、Output Boundaryを配置されます。
ほとんどの構成が同じではないでしょうか?
こうして見て見るとクリーンアーキテクチャの構成がなぜこのような構成が理由をもって理解できるのではないでしょうか?
さいごに
クリーンアーキテクチャをSOLID原則の観点で見ていきました。
いきなりクリーンアーキテクチャだけを見てしまうと難解に思えてしまいますが、
順を追ってみていくと既存の考え方の組み合わせのように見えます。