クリーンアーキテクチャという言葉はよく聞くけれど、「わかったようでわからない」という人が多いのではないでしょうか?
クリーンアーキテクチャを解説した記事は、探せばすぐに出てきますが、あまり本質について語られている記事は少ないように感じます。
そこで、この記事でそれを簡潔に語っていこうと思います。
アーキテクチャとは
まず、そもそも「アーキテクチャ」とは何でしょうか?簡潔に説明したいので、どんどん答えを書いていきます。
- 目的
- 壊れにくいソフトウェアを作る
- チーム内で統一した方針でソフトウェアを作る
- 実現方法
- 部品 (※1) 同士の依存関係 (※2) を整える
- 部品が果たす役割・責務を明確にする
(※1) 「部品って具体的に何?」と思われた方は、一旦、クラスだと思っていただければ大丈夫です。 (※2) 後述します。
めちゃくちゃシンプルに書くとこうなると思います。
依存関係とは
クリーンアーキテクチャの本質を知るためには、このセクションがめちゃくちゃ大事です!! 「そんなの知ってるわ」という方も、クリーンアーキテクチャについての理解が「モヤッ」としている方は読んでください。
依存関係には、二種類の依存関係があります。
物理的な依存関係
まずは、物理的な依存関係について説明します。これは、多くの人が最初に思い浮かぶ依存関係の方だと思われます。
すなわち、「どの部品 (クラスなど) がどの部品のことを知っているか」という関係性のことです。
(このドキュメントのサンプルコードは Kotlin で記述させていただきます。ご了承ください。)
class A {
val b: B
}
class B {
// ...
}
上記のサンプルでは、クラス A がクラス B を知っているので、これを「 A が B に依存している」と言います。
これが、物理的な依存関係です。 (超ざっくり)
意味的な依存関係
次に、意味的な依存関係について説明します。ここで、クリーンアーキテクチャの記事でよく出てくる同心円状の図を見て下さい。 (探せばすぐに出てくるのでここには載せません。笑)
ドメインとは
中心にあるのが、 ドメインモデル や エンティティ ではないでしょうか?その一つ外側にあるのが、 ビジネスルール や ユースケース ではないでしょうか?
それらの中心の二つは、本質を理解する上では、同じものとみなして問題ないため、これらをまとめて ドメイン と呼ぶことにします。
ドメインとは、簡単に言うと、ソフトウェアで実現したい中心的な機能のことです。
例えば、おみくじアプリなら、以下のようなものがあります。
- くじを表すモデル
- ユーザーがくじを引いたときに、どのくじを選ぶかのロジック
ドメイン以外
クリーンアーキテクチャの本質を理解する上では、ドメインかドメイン以外かだけを区別すれば大丈夫です。
ドメイン以外のものの例を少し上げておきます。
- ユーザーインターフェース (つまり画面そのもの)
- データベース
- リポジトリ
同心円状の図の意味
同心円状の図を見ると、「ドメイン以外」のものから「ドメイン」へ向かっていく方向で、いくつもの矢印が書いてあるはずです。この矢印が意味的な依存関係を示しています。
意味的な依存関係がこうなっていると、ソフトウェアが壊れにくいよ!
めっちゃ簡単に言うと、この図が示している意味はそれだけです。
意味的な依存関係とクリーンアーキテクチャ
意味的な依存関係とは、コードには現れません。開発者の頭の中にだけ存在するものです。コードに現れるのは、物理的な依存関係だけです。
では、意味的な依存関係とは何なのか?
意味的な依存関係とは、「物理的な依存関係の本来あるべき姿」のことです。
「どの変更がどこに影響してほしいか/してほしくないか」によって、意味的な依存関係を定義します。
つまり、物理的な依存関係が A -> B となっているけれど、「 B の変更の影響は B に閉じていてほしい」という場合、意味的な依存関係は A <- B となります。
そして、本来あるべき依存関係を、ネットでよく見る「同心円状の図」のように思い描くことが クリーンアーキテクチャ とよばれる思想です。 これで、「クリーンアーキテクチャは思想だ」と言われる理由が分かったのではないでしょうか?
具体例
ここからは、具体例をあげていきます。
依存関係の逆転が必要なケース
多くのソフトウェアでは、
- ユーザー入力
- ビジネスロジックを実行
- 結果を保存
という流れを持っているのではないでしょうか?
この場合、普通に実装すると
UI -> ドメイン -> リポジトリ
という物理的な依存関係になると思います。
しかし、思い出してください。 クリーンアーキテクチャでは、こう
ドメイン -> リポジトリ
ではなく、こういう依存関係にしたかったはずです。
リポジトリ -> ドメイン
では、どうすれば依存関係が逆転できるのか?
依存関係を逆転させる具体的な方法
逆転させる方法は、多くのエンジニアが知っている簡単な方法です。インターフェースを挟めば良いのです。
インターフェースを挟む前:
class Domain {
val repository: Repository
}
class Repository {
// ...
}
インターフェースを挟んだ後:
class Domain {
val repository: Repository
interface Repository { // (※ 1)
// ...
}
}
class RepositoryImpl: Repository {
// RepositoryImpl は、具体的な実装を持っているため
// インターフェースを挟む前の Repository に相当します。
}
(※ 1) 実際のプロジェクトでは、Repository インターフェースは Domain パッケージ直下などに 定義されることも多いですが、ここでは「ドメインの持ち物である」ことを強調するため、 この形で示しています。
こうすることで、 Domain は
- 自分の持ち物である Repository インターフェースにだけ依存する
- RepositoryImpl (リポジトリ本体) には依存していない
RepositoryImpl は
- Domain の持ち物である Repository に依存する
- そのため、実質的に Domain に依存しているのと同じ
という状況ができました。
つまり、物理的な依存関係が意味的な依存関係である
リポジトリ -> ドメイン
という関係性になりました。
「 Domain が Repository インターフェースに依存するのはいいのか?」 という声が聞こえてきそうですが、いいんです。 なぜなら、自分の持ち物であるから。
自分の持ち物であるインターフェースに変更が発生したら、自分自身に影響が出るのはあたり前だからいいんです。
問題なのは、自分に関係のない修正が発生した場合に、自分に影響が出ることです。
それが起きると、ちょっと修正しただけで、影響が爆発的に増えるソフトウェアになってしまいます。
おすすめの記事
この記事では、「なぜドメインが図の中心にくると壊れにくいのか」には触れておりません。
その理由はこちらの記事( なぜクリーンアーキテクチャはドメインを守るのか )で解説しております。
もし、気になる方は、あわせてご参考にしていただけますと幸いです。
おすすめの本(無料)
クリーンアーキテクチャは、依存関係の話であり、 その本質は「どこで判断を終わらせるか」という話でもあります。
この「判断」という視点で、 状態・イベント・境界・UI・ViewModel が どこまで判断し、どこから判断を持ち越さないかを整理したものを、 『設計の本質』 という本にまとめ、無料公開しています。
クリーンアーキテクチャが 「図としてはわかるが、実装になると迷う」 と感じていた方には、 補助線として役に立つはずですので、 あわせてご参考にしていただけますと幸いです。