クリーンアーキテクチャに入門する
はじめに
クリーンアーキテクチャは、ソフトウェアの設計において非常に重要な考え方です。近年のソフトウェア開発ではこのアーキテクチャの理解が欠かせません。
本記事では、レイヤードアーキテクチャやヘキサゴナルアーキテクチャについて解説し、クリーンアーキテクチャの理解を深めていきます。
なぜアーキテクチャについて学ぶのか
ソフトウェアのアーキテクチャとは、ソフトウェアの全体の構造とその構成要素の関係を指します。
通常のWebアプリケーションなどのソフトウェア開発は、ひとりで完結するものではなく、チームで継続的に改善していくものです。
良いアーキテクチャを選んでも、直接的に新しい機能が増えるわけではありませんが、
継続的に開発を続けるには、コードが分かりやすく、修正しやすい状態であることが望ましいです(保守容易性)。
アーキテクチャを考えずに成長したソフトウェアは、まるで巨大な泥団子のようになり、少しの修正でも多くの影響が出てしまい、変更が難しくなります。
このような状態になると、新しく参加した開発者が理解しにくくなり、開発のスピードが落ち、不満が溜まっていきます。最終的には誰も触りたくないソフトウェアになってしまいます。
良いソフトウェアの構造を保つことは、開発者が機能の追加や修正を簡単にできるようし、結果としてユーザーに早く価値を届けられるようになります。
依存関係について理解する
アーキテクチャを理解するためには、まず「依存関係」について知ることが大切です。プログラムを書く上でも、依存関係を理解することは非常に重要です。
依存関係とは、あるコンポーネントが他のコンポーネントに依存している状態を意味します。依存先が変更された場合、依存元はその影響を受けることになります。
(コンポーネントとは、クラスやモジュールなど、プログラムを構成する塊のことです)
コンポーネント依存関係の例
図のように、コンポーネントCがコンポーネントDに依存している場合、Dに変更があるとCにも影響が出ます。
コンポーネントAは、BとCに依存していて、間接的にDにも依存しています。
コンポーネントBとDは、どのコンポーネントにも依存していない(独立している)ため、比較的安定しています。
ソフトウェアのコンポーネントが多くの他コンポーネントに依存していると、変更が困難になります。
安定したコンポーネントより、不安定なコンポーネントに依存した方が、コンポーネントの安定性が下がります。
ソフトウェア開発において、依存関係を完全に無くすことはできません。しかし、どのように依存関係を構築するかを理解し、管理することが、ソフトウェアの安定性と保守性を高めるために重要です。
ここで説明している依存とは、汎化や合成、集約なども含んでいます。
クリーンアーキテクチャとは
クリーンアーキテクチャは、Robert C. Martin氏(通称ボブおじさん)によって提唱されたアーキテクチャです。
ボブおじさんは、アーキテクチャの目的をソフトウェアシステムの開発・デプロイ・運用・保守を容易にし、最終的な目的は、システムのライフタイムコストを最小限に抑え、プログラマの生産性を最大にすること、としています。
多くのアーキテクチャは、関心の分離という共通の目的をもっています。
関心の分離とは、異なる役割や責任を持つ部分を分けることで、変更の影響を最小限に抑えることを指します。
これを実現するために、ソフトウェアをレイヤー(層)に分けます。(関心は例えば、UI、ビジネスルール、データベース、外部サービスとの通信になります)
クリーンアーキテクチャは、これらのアイデアを一般化し、どのように設計すべきかのガイドラインを提供する概念です。ただし、具体的な実装を強制するものではなく、柔軟に適用できるのが特徴です。
レイヤードアーキテクチャ
クリーンアーキテクチャを理解するための基本として、まずは「レイヤードアーキテクチャ」について学びましょう。
これは、ソフトウェアを関心ごとに分けた構造の一つで、一般的には3つのレイヤーで構成されています。
つまり、プレゼンテーション層、ビジネスロジック層、データアクセス層です。
レイヤードアーキテクチャに何層までという決まりはありません。関心ごとによってレイヤーを分けることと、依存のルールを守ることが重要です。
プレゼンテーション層
プレゼンテーション層は、エンドユーザーと直接対話する部分を担当します。例えば、WebアプリケーションのGUI(グラフィカルユーザーインターフェース)やCLI(コマンドラインインターフェース)などがこれに該当します。
この層は、外部からのリクエストを受け取り、その結果をユーザーに返す役割を持ちます。
プレゼンテーション層はソフトウェアの公開インターフェースです。
ビジネスロジック層
ビジネスロジック層は、ソフトウェアの「心臓部」であり、企業のビジネスルールをカプセル化しています。この層は、外部の変更による影響を受けにくい部分であり、ソフトウェアのコアな機能を提供します。
例えば、銀行アプリケーションでの口座間の資金移動のロジックはこの層に含まれます。
データアクセス層
データアクセス層は、データの永続化を提供するレイヤーです。
データベースやファイルシステム、あるいは外部サービスにデータを保存・取得する機能を提供します。
この層は、ビジネスロジック層が具体的なデータ保存方法に依存しないようにするためのカプセル化を行います。
レイヤードアーキテクチャの問題点
レイヤードアプリケーションでは、上のレイヤーは直下のレイヤーにのみ依存します。
例えば、ビジネスロジック層はデータアクセス層に依存しますが、その逆は許されません。
このルールを守ることで、各レイヤー間の独立性が保たれ、変更の影響を最小限に抑えることができます。
しかし、このアーキテクチャには、ビジネスロジック層がデータアクセス層に依存してしまうという課題があります。
例えば、データベースに依存していると、データベースの変更がビジネスロジックに大きな影響を与えてしまいます。
このアーキテクチャでは、ビジネスロジックがデータベースなどの技術に依存することになり、データアクセス層ができるまでビジネスロジックが実装できなかったり、ビジネスロジックのテストが面倒になる問題があります。データアクセス層が外部サービスとの通信だったりすると、テストは難しくなります。
書籍や記事によって、以下の用語で表現されることがあります。
- プレゼンテーション層 = ユーザーインターフェース層
- サービス層 = アプリケーション層
- ビジネスロジック層 = ドメイン層
- データアクセス層 = インフラストラクチャ層 = 永続化層
ヘキサゴナルアーキテクチャ
次に、レイヤードアーキテクチャの課題を解決するために提案された「ヘキサゴナルアーキテクチャ」について学びましょう。これは、Alistair Cockburn氏によって提唱されたアーキテクチャで、別名「ポート・アダプタアーキテクチャ」とも呼ばれます。
レイヤードアーキテクチャの欠点を解消し、ビジネスロジックを中心として依存関係を構築する構造になっています。
基本概念
レイヤードアーキテクチャと同じで、関心毎にレイヤーで分離されています。
ヘキサゴナルアーキテクチャでは、ビジネスロジックを中心に置き、その周りに「ポート」と「アダプタ」を配置します。これにより、ビジネスロジックが外部システムに直接依存しない構造を作ります。
ヘキサゴナルアーキテクチャのレイヤー図
ポートとアダプタ
ポートは、ビジネスロジックが外部システムと通信するためのインターフェースです。具体的な処理をビジネスロジックから切り離し、どのように外部とやり取りするかを定義します。これにより、ビジネスロジックがどのような技術に依存するかを知らずに済むようになります。
アダプタは、ポートで定義されたインターフェースを実装する具体的なコンポーネントです。例えば、データベースとのやり取りや、ユーザーインターフェースとの連携を行う部分です。アダプタを変更することで、異なるデータベースやUIに対応することが容易になります。
依存性注入(Dependency Injection: DI)
データアクセス層がアプリケーション層に依存するようになっていますが、しかしアプリケーションの中でデータアクセスロジックを呼び出すのでそのままではデータアクセス層に依存してしまいます。どのようにして依存関係を逆にするのでしょうか。
ここで使われるのが、依存性注入(Dependency Injection: DI)のテクニックです。
DIが使われているのが、図のポートとアダプタの部分です。
ビジネスロジックがどのアダプタを使用するかを外部から注入します。これにより、ビジネスロジックは外部の詳細を知らずに、ポートを通じて必要な機能を呼び出すことができます。
ポートはアプリケーション層にあり、アダプタはインフラストラクチャ層にあります。
アプリケーション層のサービスがインターフェースであるポートに依存し、インフラストラクチャ層のアダプタがポートを実装すると、インフラストラクチャ層がアプリケーション層に依存するようになり、依存方向を逆転できます。
依存性注入の図
例えば、あるアプリケーションがユーザー情報を保存する機能を持っているとします。この保存先が、データベースかファイルシステムかに関わらず、ビジネスロジックはその詳細を知る必要がありません。DIを使って、どのアダプタを使用するかを指定することで、ビジネスロジックはそのままのコードで動作します。
これにより、開発者はテスト時にデータベースを使わず、モックオブジェクト(擬似的なオブジェクト)を使用してテストを行うことができるため、テストの効率が大幅に向上します。
こちらも書籍や記事によって、以下の用語で表現されることがあります。
- アプリケーション層 = サービス層 = ユースケース層
- ビジネスロジック = ドメイン層 = コア層
クリーンアーキテクチャ
最後に、クリーンアーキテクチャについて学びましょう。これは、ヘキサゴナルアーキテクチャやオニオンアーキテクチャなど、様々なアーキテクチャのアイデアを統合し、一般化したものです。
クリーンアーキテクチャの中心的な考え方は、関心ごとを分離し、依存関係をビジネスロジックに向けることです。
これらのアーキテクチャは、以下のような特性のシステムを生み出します。
- フレームワークに依存しない:ビジネスロジックはフレームワークに依存しないため、フレームワークの変更が用意
- テスト可能:外部システムに依存しないため、ビジネスロジックのテストが容易
- UIに依存しない:UIの変更がビジネスロジックに影響を与えません
- データベースに依存しない: データベースの種類や構造が変更されても、ビジネスロジックには影響しません
- 外部エージェントに依存しない:外部サービスやライブラリの変更がビジネスロジックに影響を与えません
クリーンアーキテクチャの概念図「Clean Architecture 達人に学ぶソフトウェアの構造と設計」より
クリーンアーキテクチャでは、中心にビジネスルールがあり、外側に技術的な関心(UI、データベースなど)が配置されています。
円になっていますが、これらは、レイヤードアーキテクチャと同じで、関心毎で分離されています。
外側から矢印が内側に向かっているのは、依存のルールです。
ヘキサゴナルアーキテクチャと同じでこの構造により、ビジネスロジックは外部の技術的な詳細に依存せず、柔軟で保守しやすいシステムを実現します。
クリーンアーキテクチャのレイヤー
エンティティ層は、ソフトウェアの最重要ビジネスルールを含む層。外部の影響を受けずに独立しています。
ユースケース層は、インターフェースアダプタ層から受け取ったデータをもとに、エンティティ層のビジネスロジックを組み合わせて、アプリケーション特有のロジックを達成します。このレイヤーもフレームワークやデータベースなどの外部技術について知りません。
ユースケースは、ポートを介して外部システムを呼び出します。
インターフェースアダプタ層は、外部システムと内部システムのデータを変換する層です。
ヘキサゴナルアーキテクチャのアダプタに相当します。
たとえば、コントローラーは、リクエストをユースケースが利用するできる形式に変換し、
プレゼンターは、ユースケースの出力をユーザーや外部システムが理解できる形式に変換します。
最も外側のフレームワーク・ドライバーレイヤーは、データベースやウェブサーバーなどの外部エージェントとの通信を処理します。これらのレイヤーを独自で実装することは稀で、ライブラリやフレームワークを使うことが多いです。
クリーンアーキテクチャの実装例
右下図は、実装と制御の流れを例図したものです。
ユースケース層にインターフェース(入力ポートと出力ポート)があり、
入力ポート実装はユースケースインタラクターが実装し、出力ポートをプレゼンター(インターフェースアダプタ層)が実装しています。
制御の流れは、
コントローラー ⇨ ユースケースインタラクター ⇨ プレゼンター となっていますが、
依存の向きは
インターフェースアダプター層 ⇨ ユースケース層 にのみ向いています。
クリーンアーキテクチャは、ビジネスロジックを中心に据え、外部の技術的な関心ごとに依存しない構造を提供します。これにより、システムは柔軟性を持ち、将来の変更にも耐えられる設計が可能になります。
まとめ
クリーンアーキテクチャを理解し、その概念を実践に取り入れることは、ソフトウェアエンジニアにとって価値のあるスキルです。
特に、DIを使って依存関係を逆転させビジネスロジックを外部から独立した状態に保つように、ソフトウェア設計において依存を正しく制御できるようになると、開発に自信が持てるようになります。
参考文献
Discussion