見出し画像

クリーンアーキテクチャの本質を考える

はじめに

アーキテクチャを突き詰める Online Conferenceの公募LT選手権で、「やってみてわかった クリーンアーキテクチャの勘所」というタイトルでトークをしました。LTということで、具体的な技術面でのメリットや、改善すべき点などについて話しました。(登壇資料はこちら
本稿では、少し視座を上げて、結局クリーンアーキテクチャ(以下、CAと表記)は一体何なのかということについて、現在の筆者の考えを整理したいと思います。

あの図

話が手っ取り早いので、例の同心円の図をまずは引用します。

図の出典: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

CAでは、レイヤー(層)間の依存関係は同心円の外側から内側へ向く方向のみ許容されます。内側にあるコードは上位レベルの方針を表すものであり、外側の詳細から内側の方針に対して依存させることがCAの大原則です。これを実現するために、抽象(インターフェース)を内側に配置し、DIP(依存性逆転の原則)を適用するのですが、その具体例としてWebのインターフェースとユースケースとの関係に着目して要素の構造と制御フローを図示したものが、図の右下部分になります。
そして、この具体的な構造を全体として描くと、CA本の図22-2にある「データベースを使ったウェブベースのJavaシステムの典型的なシナリオ」(p.204)のようになります。

これらのクラス図で表現される構造は、あくまでCAのコンセプトを体現する具体的なクラス設計の一つと捉えるべきものです。同心円図で表現されるCAのコンセプトが「方針」とするならば、その具体的なインスタンス(実例)としてのCAは「詳細」に過ぎません。
ただし、「詳細」としてのCAに意味がないというつもりはありません。まずは形から入って学ぶことも有効な方法ですし、筆者もそのようにしてCAに対する理解を深めました。重要なのはCAの本質を正しく理解し、見失わないことです。Do AgileではなくBe Agileであるように、Do CAではなくBe CAを目指しましょう。

境界線を引く(1)

CAの中核をなすコンセプトは、重要な「方針」と重要でない「詳細」との間に境界線を引き、方針とは無関係に詳細を決められるようなアーキテクチャの形状を作ることです。境界線をうまく引き、先に述べたDIPの活用により依存関係を制御すると、「詳細」が「方針」で定められた抽象に依存するようになり、安定性が高く、かつ変更が容易なアーキテクチャが生まれます。このようなアーキテクチャをプラグインアーキテクチャと呼びます。

ビジネスアプリケーションの場合、重要な方針となるのはビジネスルールです。CA本では、システムの有無に関わらず存在するようなルールやデータを最重要ビジネスルール、最重要ビジネスデータとし、それらを表現するオブジェクトをエンティティと呼んでいます。また、システムとして自動化する際の処理手順やアプリケーション固有のビジネスルールを実現するのがユースケースです。
注意しなければならないのは、CAのコンセプトに従い、エンティティ・ユースケース・インターフェースアダプターとレイヤーを分離し、レイヤー間の相互作用のルールを取り決めるだけでは、良いソフトウェアを作り出すには不十分だということです。
では、他に何が重要なのでしょうか。

境界線を引く(2)

筆者が最初にCA本を読んだ時に抱いた印象は、「クリーンアーキテクチャの話(第22章)に辿り着くまでが長いな」というものでした。第III部「設計の原則」ではSOLID原則が取り上げられ、第IV部「コンポーネントの原則」ではコンポーネントの凝集性や結合度に関するいくつかの原則が解説されます。その後、第V部からアーキテクチャ論に入っていく構成です。
しかし、このことはCA本において非常に重要な意味を持っています。
システムを安定的な部分と不安定な部分に分割し、依存関係をうまく制御することで保守や変更が容易なシステムの形状を作り出すことがCA本の主題と言えます。そのため、モジュール構造あるいはコンポーネント構造に適切な境界線を引いて正しい分割を行うことも、重要なアーキテクチャ設計行為なのです。

凝集性と結合度

適切なモジュール分割・コンポーネント分割の肝となるのは凝集性と結合度です。つまり設計原則に従って、モジュールやコンポーネントを、凝集性が高く疎結合となるように設計するのです。

用語の整理

ここでモジュールとコンポーネントの用語を整理しておきます。これらの用語に標準的な定義があるわけではなく、使う人や文脈によって様々な意味を取るからです。ボブおじさん(CAの提唱者であるロバート・C・マーチン氏の愛称)はCA本で以下のように定義しています。

モジュールとはソースファイルのことである。

CA本 p.82

モジュールという言葉は、SOLID原則の説明にて使用されていることからも、プログラムの最小単位を表すものとして使われています。

コンポーネントとは、デプロイの単位のことである。システムの一部としてデプロイできる、最小限のまとまりを指す。Javaならjarファイル、Rubyならgemファイル、.NETならDLLなどがそれにあたる。

CA本 p.109

コンポーネントは、上記の説明に続いて、warファイルにアーカイブされることもある、最終的なデプロイ形式に関わらず個別にデプロイできる状態を保っている、といった特徴が述べられています。
本稿では、コンポーネントの定義はCA本のそれを踏襲しますが、モジュールについてはコンポーネントよりも大きな粒度を指すものとして使用します。モジュラーモノリスを構成する一つ一つのモジュール、あるいはサブシステムと考えてください。

単一責任の原則を考慮する

設計の抽象レベルの違いはあれ、境界線の引き方に関する基本的な考え方は同じです。クラスレベルの(あるいはCA本における「モジュール」レベルの)設計では、SRP(単一責任の原則)を適用し、クラスに単一の責任を割り当てます。言い換えると、個々のクラスを変更する理由がたった一つだけになるようにします。単一の責任を持たせるようにすることで、凝集性が高まります。ちょうど必要なだけのインスタンス変数やメソッドだけがクラスに集められ、余分なものを含んでいない、ぎゅっとまとまっている状態というイメージです。凝集性と結合度には高い相関関係があります。個々のクラスが単一責任を持ち凝集性の高い状態であれば、他のクラスに対する不要な依存関係がなくなり、結合度が下がります。つまり疎結合となります。

コンポーネントレベルでは、第IV部で述べられているようなコンポーネントの原則を考慮する必要はあるものの、重要な点は変わりません。それぞれのコンポーネントの凝集性が高く、コンポーネント間の結合度が低くなるように境界線を引くということです。CA本には、SRPのコンポーネント版であるCCP(閉鎖性共通の原則)に従い、以下のようにコンポーネント分割をすべきだと書かれています。

同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること。変更の理由やタイミングが異なるクラスは、別のコンポーネントに分けること。

CA本 p.119

モジュールレベルではどうでしょうか。CA本のp.161に以下の記述があります。

注文入力システムに注文を追加するユースケースは、注文を削除するユースケースと比べると、明らかに異なる頻度と理由で変更される。ユースケースはシステムを分割する自然な方法である。

CA本 p.161

筆者はこの例はあまり良くないと思うのですが(注文の追加と削除は利用するアクターが同一であり、一つのモジュールにまとめるのが自然と考えられるため)、モジュール分割についてもクラスやコンポーネントと同じような考え方が適用できそうです(もちろん、それ以外に様々な要素を考慮する必要があり、モジュール分割は一筋縄ではいかないのですが)。

モジュラーモノリスへ

次に、分割したモジュール同士の結合について考えます。次の図は、筆者がLTで話した、あまりよくない結合の例です。ユースケース層でモジュールAのユースケースインタラクターから、モジュールBのユースケースインタラクターを呼び出しており、直接的な依存関係が発生しています。つまり、モジュール同士が密結合となっています(それ以外に、DBのテーブルレベルでの間接的な結合もあります)。

モジュール間の結合

CA的には、ユースケース層でモジュール境界をまたいて直接相互作用するではなく、ユースケースの入出力(のアダプター)を介して、外側のインターフェースアダプター層で通信をすべきです。上の図は、作図の都合で一つの円を分割する形でモジュール境界を表現してしまったのですが、実際にはモジュール毎にCAの同心円が存在するのが正しいイメージだと思います。それを図にしようとすると、ごちゃごちゃして見た目も気持ち悪いので、代わりにヘキサゴナルアーキテクチャのイメージで表したのが次の図です。

ヘキサゴナルアーキテクチャ

このように、インターフェースアダプター層で他のモジュールとつながる構造にすると、ドメイン層のユースケースからは実際の通信手段は隠蔽されますから、疎結合が実現されます。それに加えて、モジュール毎にDBのスキーマを分けるなどして、DBレベルでの依存を排除すれば、モジューラーモノリスの要件を満たすアーキテクチャのできあがりです。

まとめ

CAの重要なコンセプトは方針と詳細を分離したプラグインアーキテクチャの実現です。CAは良い設計へ向かうことを支援する土台となるものですが、CAを採用するだけで変更に強いソフトウェアが実現するわけではありません。設計原則を正しく理解し、あらゆる抽象レベルにおいて適切な境界線を引いて高凝集性と疎結合を実現することで、正しいアーキテクチャを形作る設計活動が重要なのです。

参考文献

CA本:『Clean Architecture 達人に学ぶソフトウェアの構造と設計』Robert C. Martin 著、アスキードワンゴ(2018)




この記事が気に入ったらサポートをしてみませんか?