2018/9/17 旧記事としてアーカイブ
2018/8/6 更新
最新記事リンク
最新記事: https://nrslib.com/practice-of-clean-architecture/
この記事の内容をもっと詳細に説明しています。
概要
クリーンアーキテクチャについての解説記事です。
クリーンアーキテクチャは iOS clean architecture として発表された設計思想です。
iOS となっていますが、iOS のプロジェクトに限らず様々なプロジェクトに適用できます。
元々は HexagonalArchtecture(ヘキサゴナルアーキテクチャ) の考えを一歩推し進めた設計になりますので、先にヘキサゴナルアーキテクチャの解説を読むと理解の助けになります。
この設計の特徴は疎結合な作りになり、変化に強く拡張しやすい設計になります。
特にヘキサゴナルアーキテクチャにおける問題点だった、ポートの分割方針については非常にわかりやすいアプローチになっています。
クリーンアーキテクチャ関連記事
◆実践クリーンアーキテクチャ(最新)
記事リンク: https://nrslib.com/clean-architecture/
◆クリーンアーキテクチャの概要(イマココ)
記事リンク: https://nrslib.com/clean-architecture-old/
◆クリーンアーキテクチャの右下の図について
記事リンク: https://nrslib.com/clean-flow-of-control/
◆ ClArc.CLI : CleanArchitecture のクラスを生成して登録まで行うツール
記事リンク: https://nrslib.com/clarc-csharp/
github: https://github.com/nrslib/ClArc.CLI
◆ ClArc : ClArc.CLI のコア部分だけを抜き出したライブラリ
github: https://github.com/nrslib/ClArc-CSharp
nuget: https://www.nuget.org/packages/ClArc/
サンプル
https://github.com/nrslib/CleanArchitectureSample
解説
まずはクリーンアーキテクチャを表した図をご覧ください。
この図は矢印が表しているのは依存の方向で、外側のレイヤーは内側のレイヤーに依存してもよい、ということを示しています。
この依存の方向を一方向にすることで何を目的にしているかというと、ドメイン領域(業務領域、ビジネスロジック)を中心に見据え、変化が起きやすいもの(UI 等)ほど外側に押しやることで、UI を変更するなどの些末な変更をドメイン領域まで波及させないようにするということを目的にしています。
また同時にすべてのレイヤーでテストが可能になります。
ヘキサゴナルアーキテクチャの解説記事をご覧になった方は恐らく気づかれたかと思いますが、クリーンアーキテクチャは根本的にはヘキサゴナルアーキテクチャと全く同じことを目的としています。
殆どヘキサゴナルアーキテクチャのガイドラインと思っても問題はないでしょう。
クリーンアーキテクチャはヘキサゴナルアーキテクチャのポートやアダプターの実装ガイドラインです。
反対にヘキサゴナルアーキテクチャの Application 部分の実装ガイドラインも存在します。それがオニオンアーキテクチャです。
レイヤー
クリーンアーキテクチャの図では層が複数重なっています。
内側の層から解説をしていきます。
Enterprise Business Rules
ビジネスルールとあるように、ここは完全にビジネスロジックが実装されるレイヤーです。
ドメイン駆動設計を採用しているとしたらこのレイヤーで展開します。
このレイヤーはビジネスルールが展開されるため一概にこのように実装すればいいという指針はありません。
各々が持つソフトウェアで解決したいビジネスルールを表現したものをこのレイヤーに納めるためのレイヤーです。
ビジネスルールの実装がトランザクションスクリプトであったり、ドメインモデルであったりしても、それらをこの層に納めて他の層に知識を流出しないようにすることが重要です。
Entities
Enterprise Business Rules レイヤーに登場する Entities はドメイン駆動設計のエンティティや一部フレームワークにおけるデータモデルのことを指しているわけではありません。
Entities については原文においても言及されており、ビジネスルールをカプセル化したものを指し、つまりビジネスロジックです。
つまりトランザクションスクリプトそれ自体やドメイン駆動設計のエンティティなどを指しています。
Application Business Rules
ビジネスロジックのインターフェースにあたるレイヤーです。
ビジネスロジックを直接利用させるのではなく、その利用の仕方をカプセル化することでそのアプリケーションが何ができるのかを表現します。
Use Cases
ユースケースはドメイン駆動設計のアプリケーションサービスに似ています。
アプリケーションサービスと異なるのは実行したい処理、つまりユースケース一つ一つを分離して実装するという点です。
Interface Adapters
アダプターという名前の通りヘキサゴナルアーキテクチャにおけるアダプターの実装です。
主に入力や出力等の副作用を伴う処理を扱うレイヤーです。
Controllers
コントローラはユーザの入力をユースケースが必要とする入力値に適合させる処理を行います。
Presenters
プレゼンターはユースケースの結果を出力する処理です。
非同期処理を行う場合は有用です。
同期処理を行う場合はプレゼンターを用意せず、レスポンスをコントローラに返却する方が素直なプログラムになります。
GateWays
ユースケースがビジネスルールを実行する際に利用する永続化機構です。
API やリポジトリ等が該当します。
Controllers と Presenters と GateWays の関係
これらは以下のように利用されます。
- ユーザの入力は Contoller に伝えられる
- Controller はユーザの入力を UseCase が利用できる形に変換し UseCase に引き渡す
- UseCase は入力データに従い処理を実行する
- UseCase が処理中にデータを読み出すときや保存するときは GateWay を利用する
- UseCase が実行した結果を Presenter に通知する
- Presenter は結果を通知する
この流れはクリーンアーキテクチャの右下の図に沿ったものになっています。
詳しくはそちらを参照ください。
◆クリーンアーキテクチャの右下の図について
記事リンク: https://nrslib.com/clean-flow-of-control/
Framework & Drivers
データベース処理や UI が所属するレイヤーです。
コードを見ながら
冒頭でもリンクを張りましたが、サンプルコードをご用意しました。
クリーンアーキテクチャに倣ってブログに記事を投稿する Web アプリケーションです。
Web アプリケーション部分は ASP.NET Core を利用しています。
もちろんそれ以外のフレームワークやネイティブアプリなどにも適用ができます。その場合は Web アプリケーション部分を適宜置き換えてください。
https://github.com/nrslib/CleanArchitectureSample
それでは早速プロジェクト毎に見ていきましょう。
Presentation
このプロジェクトは MVC フレームワークで Interface Adapters レイヤーに相当します。
ユーザの入力をユースケースが求めるデータに変換をすることを主としています。
ArticleController を確認してみましょう。
UseCaseBus というクラスのインスタンスをコンストラクタで受け取っています。
UseCaseBus を端的に説明すると、リクエストを投げるとそれに紐づいたレスポンスを戻してくれる機構です。
この Web アプリケーションは起動時にリクエストに対して処理する UseCase を登録しています。
実際の登録個所は Startup.cs です。
メソッドの前半部分では IArticleRepository や IUserRepository といったリポジトリを Dependency Injection しています。
この IArticleRepository や IUserRepository は GateWays にあたります。
後半部分の RegisterUseCase というメソッドが呼ばれているところがそれぞれ
- ArticleCreateRequest が UseCase に引き渡されたら ArticleCreateInteractor にデータを引き渡し処理を実行する
- ArticleGetDetailRequestが UseCase に引き渡されたら ArticleDetailGetInteractorにデータを引き渡し処理を実行する
- ArticleGetByAutherRequestが UseCase に引き渡されたら ArticleGetByAutherInteractorにデータを引き渡し処理を実行する
という登録処理を行っています。
Interactor は後述しますが、UseCase の実装でありビジネスルールです。
Request を生成して UseCase に引き渡すと、それぞれ Request に紐づいた Interactor が呼ばれるという動作になっています。
Repository
リポジトリはデータの永続化に関心を置いたオブジェクトです。
もっと簡単にいえば、エンティティの保存と読み込みを請け負うオブジェクトです。
リポジトリを抽象化してコンストラクタで受け取るようにすることで、デバッグ時はインメモリーなデータを使い、プロダクション時はデータベースに接続するようにするといったことが可能です。
詳しくはリポジトリの記事をご覧ください。
◆ Repository
記事リンク: https://nrslib.com/repository/
UseCase
このプロジェクトは Application Business Rules レイヤーにあたります。
アプリケーションのインターフェースをユースケース毎に定義し、必要とするパラメータや実行結果の型だけを用意しています。
記事を作成するためのインターフェースを見てみます。
単純にリクエストのパラメータとそれを処理することだけが明示された interface 、そして結果のパラメータだけが用意されています。
このようにインターフェースをユースケース毎に分けておくことで他の処理への影響を考慮せずに変更することができます。
各ユースケースは一つの処理を行うことだけに特化するようになるため、クラスの凝集度が非常に高くなります。
結果としてユースケースへの変更が他の処理に波及することがなくなったり、コードリーディング時に他の処理を見なくても良いようになります。
また、ユースケース自体が抽象ですので、テストの際にスタブに入れ替えて Presentation レイヤーで任意のデータを受け取るようにすることが可能です。
これで ArticleCreateRequest を UseCaseBus に引き渡すと MockArticleCreateInteractor の処理が実行されるので、任意のデータを受け取ることができます。
Domain
Enterprise Business Rules レイヤーにあたります。
ドメイン駆動設計を意識した実装です。
例として単純な ArticleCreateInteractor を見てみます。
たった一つの処理だけを行うクラスですので凝集度が高くなっています。
ユースケース毎に分けるのではなく、関心事毎に分けるアプリケーションサービスで実装した場合はどうしても凝集度が下がる場合があります。
実際に見ていきましょう。
以下のように Article を扱うアプリケーションサービスをモデルケースとします。
例えばこのアプリケーションサービスに、記事一覧取得の際にフレンドでないと閲覧できないようにする機能を追加してみましょう。
以下のように IFriendRepository のような Repository をコンストラクタで受け取りフレンドかどうかの確認ロジックが必要です。
ほとんどこの方法で問題ないですが、注目すべきは CreateArticle メソッドです。
オブジェクト指向において、あるメソッドでは利用されるが他のメソッドでは利用されないフィールドが存在することは好ましくありません。
friendRepository が CreateArticle メソッドで利用されないのはあまり好ましいものではないでしょう。
さて、ここで UseCase にした場合も見てみます。
こちらの記事一覧取得に対して、フレンド閲覧権限の機能を追加するには以下のように変更を加えます。
もちろん ArticleGetByAutherInteractor には CreateArticle のような他のユースケースを実行するロジックは存在しないので一部メソッドでフィールドが利用されない、ということはありません。
オブジェクト指向の観点からより良い凝集度を持ったクラスになっていると言えるでしょう。
その反面、ソースファイルはユースケースの数だけ作られることになるので、ファイル数は増えることになります。
InMemoryDataStore
このプロジェクトはリポジトリの実装なのでレイヤーは GateWays にあたります。
このプロジェクトはテスト用でインメモリー上でリポジトリの機能を実現しています。
プロダクションモード用の GateWay を用意するときは MySQL や Oracle などのデータベース等を活用した DAO を駆使したリポジトリなどを利用するようになるでしょう。
その他の話題
Dependency Injection
クリーンアーキテクチャを実践すると各クラスでインターフェースなどの抽象をコンストラクタで受け取るように記述します。
何故ならロジック内でインスタンス化を行ってしまうと、そのインスタンス化したものに依存してしまうためです。
ここで問題になるのが、このコンストラクタに渡す具象クラスは誰が決めるのかです。
こういった際に利用するのが DIContainer です。
DIContainer で抽象クラスが要求されたときに引き渡す具象クラスを設定します。
このように設定された場合 IArticleRepository を要求するコンストラクタには ArticleRepository が渡されます。
たったこれだけのことですが、この登録スクリプトを差し替えたりすることで、テスト時はインメモリーなリポジトリを利用するようにし、プロダクション時は本番データベースに繋ぐようにする、といったことが可能になります。
まとめ
クリーンアーキテクチャの解説記事でした。
クリーンアーキテクチャ単体で見ると難しい図に思えるのですが、ヘキサゴナルアーキテクチャと照らし合わせながら確認していくと非常に理解しやすい設計であるということがわかったかと思います。
ユースケースごとにクラスを分け、肥大化しがちなアプリケーションサービスをコンパクトにまとめる。
結果として非常に疎結合な作りとなり、テスト可能性、保守性、拡張性に優れたアーキテクチャであると言えるでしょう。