Think Clean Architecture in terms of the purpose
単にアーキテクチャパターンを適用するのではなく、それの目的や他のパターンと比較することで、適切に利用することができます。普段、Clean Architectureに従って開発しているため、改めてClean Architectureについての理解を整理することにしました。
しかし、Clean Architectureはドメイン駆動設計(DDD)で紹介されているHexagonal(Ports & Adapters)Architectureと本質的に同じなので、まずはその基本的な考え方を調べました。そして、最後に普段どういったディレクトリ構造で開発をしているのかを紹介したいと思います。
Layered Architecture
まず、ここで紹介するアーキテクチャパターンは技術的関心事に応じてコードベースを水平方向の層にモジュール化し、それらがどう相互作用するのか規定します。凝集度の高いモジュールに分割することによって、実装時の影響範囲を明確化したり、限定的にすることができます。
このパターンで代表的なのがLayered Architectureです。これはコードベースをプレゼンテーション層、アプリケーション層、ドメイン層、インフラストラクチャ層に分割します。
以下の図のように、これらの層は真下の層へのみ依存することができます。これによって、各層に技術的な関心事を分離し、他の層について持つべき知識を小さくすることができます。結果的にドメイン関連のコードに集中することができます。
後で図を追加します
Presentation Layer
最上位のプレゼンテーション層はプログラムの呼び出し側へのインターフェースの実装になります。これはAPIやCLI(command-line interface)、メッセージブローカーのコンシューマなどの実装になります。
この層の責務は外部から受け取った入力を内部で扱いやすいデータ構造に変換してアプリケーション層を呼び出し、受け取った出力を外部向けに変換することになります。
Application Layer
アプリケーション層はプレゼンテーション層とドメイン層の仲介をします。この層は必須ではないものの、プレゼンテーション層とドメイン層のさらなる分離に寄与します。プレゼンテーション層が直接、ドメイン層を扱うとビジネスロジックがプレゼンテーション層に漏れ出すことがあります。そこで、この層がシステム要件を実現するドメイン層への呼び出しをまとめたインターフェースをプレゼンテーション層に提供します。これはドメイン層のファサードとなります。
Domain Layer
ドメイン層はビジネスの概念とそのような概念間の相互作用や手順(ビジネスロジック)を表現する責務を持ちます。ドメイン駆動設計ではこの層にドメイン知識を凝集させることを目的としてLayered Architectureが紹介されています。
Infrastructure Layer
インフラストラクチャ層はデータの永続化に関する実装になります。データベースへのアクセス手段の提供だけでなく、ファイルストレージやメモリへの操作も含みます。
ドメイン駆動設計の実装上の課題
Layered Architectureは依存関係が一方向であり、ドメイン層がインフラストラクチャ層を呼び出します。しかし、ドメイン駆動設計におけるドメインモデル(エンティティや値オブジェクト、集約など)はソフトウェアが対象とするユーザーに纏わる活動や関心事を概念化したものです。したがって、データベースなどの基盤への知識を含むべきではありません。ドメイン層で純粋なドメインモデルを実装しつつ、インフラストラクチャ層を利用するのが難しい場合があります。
Hexagonal Architecture
Layered Architectureの依存関係を改良したものがHexagonal Architecture(Ports & Adapters Architectureとも呼ばれる)です。Layered Architectureのプレゼンテーション層もインフラストラクチャ層も外部のコンポーネントとの連携を担います。また、ドメイン層がそれら二つの層に挟まれます。しかし、この二つの層はどちらもビジネスロジックの実装ではないため、Hexagonal Architectureは1つのインフラストラクチャ層として関心事をまとめます。
そして、ドメイン層がより具体的なインフラストラクチャ層に依存しないように、 依存性逆転の原則(Dependency Inversion Principle)に基づいて依存関係を逆にします。ドメイン層はインフラストラクチャ層を直接呼び出すのではなくportsというインターフェースを定義し、インフラストラクチャ層はそれを実装します。この実装はadaptersと呼ばれます。よってドメイン層はインフラストラクチャ層の実装に関知することなく、インフラストラクチャ層が期待されたインターフェースについての知識を持つことになります。
さらに間にアプリケーション層を挟みます。こうすることで、この層がシステム外へのファサードとなります。
後で図を追加します
Clean Architecture
概要
Clean Architectureとは、アーキテクチャパターンの一つで、ソフトウェアコンポーネントを層ごとに分類することで関心の分離をし、層ごとの依存関係の方向を一つにすることで、抽象に依存できるようにすることを目的としています。
後で図を追加します
Clean Architectureは概要図が有名ですが、あくまで例であり決められた種類や数の層があるわけではありません。Clean Architectureは以下のルールを遵守することを求めるだけです。
- 依存関係を一方向にする
- 抽象に依存する
同心円上に広がる層があり、外側が内側に依存するということをルールとしています。つまり内側の層のソフトウェアコンポーネントは、外側の層について何も知らないということを保証します。そして、内側の層になるに従って抽象度が高い責務を担います。つまり、特定のフレームワークやツールに依存しない、ソフトウェアの問題領域におけるビジネスロジックが表現されます。
依存関係を依存性逆転の原則で一方向にするのは、Hexagonal Architectureと同じです。結局どちらもビジネスロジックをシステム外部のコンポーネントに依存しないようにし、ドメインモデルの設計やテストをクライアントやストレージが確定しないうちから行えるようにすることが利点になります。
構成のサンプル
ここでは今までのプロジェクトでClean Architectureに基づいてどのような構成で実装してきたのかを共有します。過去のプロジェクトではドメイン駆動設計の戦術的パターンを各層に割り当て、Adapter→UseCase→Domainという依存関係を規定することが多かったです。これはドメインモデルを中央にカプセル化し、他のどの層にも依存しないことで凝集度を高めることを狙いとしてます。
後で図を追加します
ディレクトリ構成は以下です。
.
├── adapter ... アダプターの実装
│ ├── job
│ ├── repository
│ └── rpc
├── domain ... ドメインモデルの実装
│ ├── entity
│ ├── service
│ └── port
├── usecase ... アプリケーションサービスの実装
├── config ... 環境変数を管理
|
...
Domain
この層はLayered Architectureと同じで、ビジネスの概念とそのような概念間の相互作用や手順(ビジネスロジック)を表現する責務を持ちます。
ドメイン駆動設計におけるドメインモデルを定義します。つまり、業務ドメイン固有の概念や操作を表現したモデルやサービスが定義されます。これらのデータを扱う技術的な詳細(DBへの永続化など)はport
配下にインターフェースだけ定義して、その実装はAdapter層(adapter
配下)が行います。これはHexagonal Architecture(Ports & Adapters Architecture)と同じです。
また、ドメインサービスの実装も含みます。オブジェクトとしてエンティティや値オブジェクトとしてモデル化しづらい場合に、ドメインロジックを実装するサービスです。サービスとは、状態を保持せずにいくつかのロジックを実装するオブジェクトです。主に複数のモデルの相互関係を扱うことが多いです。
UseCase
システム要件を達成するためのタスクの調整を行います。ここではビジネスルールやドメイン知識を含みません。ドメイン駆動設計におけるアプリケーションサービスの実装が該当します。アプリケーションサービスはシステムのユースケースを実現するために受け取った値からリポジトリを用いて永続化されたドメインモデルを取り出し、トランザクションの制御も行い、ビジネスルールの実行をドメイン層に移譲します。あくまでやるべき作業を調整するだけで、UIやデータベースなどのフレームワークによって変更されないものです。
アプリケーションサービスはドメインに関する知識を持たない点がドメインサービスと異なります。しかし、普段の実装でその境界に悩むことはよくあります。その場合は、この実装を他のユースケースでも利用したいかどうかで考えます。なぜなら、アプリケーションサービスはドメインモデルへのファサードであり、ドメインロジックを再利用可能にする役割があるからです。
Adapter
データベースや画面の表示などのデータをUseCaseやDomain層が扱いやすいように変換します。またその逆の流れも同様です。SQLなどもこのレイヤーで記述されます。このようにアダプターが仲介することによって、UseCaseとDoaminはフレームワークや外部のシステムについて知る必要がなくなります。また、データベースやフレームワークなどの設定も含まれます。環境変数などを指定して外部のサービスやストレージへの接続設定が格納されます。(Clean Architectureの資料ではFramework&Driver層として切り出されることが多いですが変更されることがほとんどないので、実装の簡略化を優先して同じレイヤーにまとめています。)
内側から外側の層の呼び出し
Hexagonal Architecture(Ports & Adapters Architecture)と同じように依存性逆転の原則に基づいて、port
にインターフェースを定義し、adapter
にその実装します。したがって、Adapter層がDomain層へ依存することになるので、Domain層は内部でport
のインターフェースに依存するだけでモデルの永続化や外部のシステムとの通信が結果的にできるようになります。