クリーンアーキテクチャとはアイデアである
クリーンアーキテクチャについて、最近自分が学んだことを雑なメモとして書き留めたものを公開します。ツッコミ歓迎。
Clean Architecture (クリーンアーキテクチャ) とは、 Robert C. Martin (Uncle Bob) 氏が提唱したアーキテクチャである。
クリーンアーキテクチャに関する記事がたくさん出ていて、殆どの記事がそれをどのように実装するかやパッケージ(名前空間)を分けるかなどについて議論されていて、そもそもクリーンアーキテクチャの考え方とはどのようなものかというところに焦点があまり当てられていないと思った。
クリーンアーキテクチャとはアイデアである。どのようにプログラムを実際にコーディングすればよいかということを指示したり、どのようなディレクトリ構成でどのような命名規則でクラス名を決定すれば良いかという話はあまり関係ない。
クリーンアーキテクチャは、本当に重要な決定のために些末な決定を遅らせることを目指している。 些末な決定とは、どのような Web サーバを使用するかや、どのような DB を使用するかといった、今作成しようとしているアプリケーションのロジックにあまり関係のない決定のことを指す。
アプリケーションの設計で最も大切なのは、そのアプリケーションが行うこと(=ビジネス)を正確にモデリングすることである。 どのような要素がどのように振る舞い、どのような出力を得るのかを考えることである。 このアプリケーションが行うビジネスの範囲のことを、事業領域としてドメインと呼んでいる。
ビジネスのモデリングが終われば、あとはそれを実際のコードに落とし込むだけである。 そしてその際、このドメインモデルをコードで実現することに最も注力すべきである。
そしてこのアプリケーションの振る舞いというのは、特定の Web サーバの詳細や DB に依存しているはずがない。 その特定の Web サーバではなくとも、それが提供する機能さえあれば別に実際はなんでもいいはずだからだ。
具体的な Web サーバや DB などを決定するのを遅らせられれば、アプリケーションがどのようなことを行うかをちゃんと設計してからそれにふさわしいものを後から選ぶことができる。
そのためクリーンアーキテクチャではコードの依存方向を単方向に制限している。
それがあの同心円の図形の外側から内側に向かう方向である。
中心となるのは、このドメインこのビジネスをモデリングしたときに現れたエンティティたちである。 例えば自動車修理受付システムであれば自動車や受付データ、通販システムであれば商品やカートなどである。
それらのエンティティに依存するのは、ビジネスを達成するためのロジック、つまりユースケースや、エンティティのデータを永続化するためのリポジトリやエンティティを外部サービスから取得するためのサービスなどである。
これらは同じ層である。そのためこれらが相互に依存することはクリーンアーキテクチャのルールを破らない。
ユースケースはサービスからデータを取得し、多少改変したりしてリポジトリに保存したり、反対にリポジトリから取得したデータをサービスを使用して外部のシステムに送信したりすることができる。
ただし、リポジトリやサービスは実際に使用する DB や HTTP クライアントに依存してはいけない。それらは今作っているアプリケーション自体ではないからだ。 全く外部のものなので、もっとも外側に位置するものだ。
ではそれらの外側のものに依存することなくリポジトリやサービスを定義することができるだろうか? できる。リポジトリやサービスを「何ができるか」だけを定義したインタフェースとして作成すれば良い。
たとえば自動車修理受付システムなのであれば、 受付リポジトリ
は 受付データを保存する
ことと 保存してある受付データを取得する
ことができれば良いわけだ。
もし外部の自動車カタログ API を使って名前から自動車の情報を取得するということが決まっているのであれば、 自動車検索サービス
というサービスを定義し、そのサービスが 指定した名前に似ている自動車の情報を返す
ことができると定義しておけば良い。
この2つがあれば、 指定した名前の自動車の修理受付を作成する
というアプリケーションの仕様を実現するために、 自動車検索サービス
から名前に一致する自動車の情報を受け取り、 受付リポジトリ
に新しく受付を保存するようなユースケースを作成すれば、画面のボタンが押下されたときにそのユースケースを実行して1つの機能を実行できるわけだ。
おそらく画面のボタンが押下されたときにそのユースケースを叩くのは MVP パターンで言うプレゼンターなどになるだろう。
当然プレゼンターはユースケースよりも外側のレイヤーだ。どのような画面にどのようなボタンが有るかということは、このアプリケーションが自動車修理の受付を行えるかどうかに関係ない。
ここで出したのは飽くまでも例である。クリーンアーキテクチャはエンティティクラスとリポジトリインタフェースとサービスインタフェースとユースケースクラスでコーディングしなければいけないということではない。これは単なる一例だ。
本質は、その依存関係だ。プレゼンターはユースケースに依存し、ユースケースはリポジトリとサービスに依存する。その逆はありえない。このアプリケーションのドメインモデルの中核をなすエンティティを中心として、それを受け取ったり返したりするリポジトリやサービス、そのリポジトリやサービスを使用してビジネスロジックを実現するユースケースクラスがその外側にある。そしてそのユースケースクラスを実行するプレゼンターがさらに外側にあるのだ。
では受付データを保存するための実際の DB や自動車カタログ API を使用するための HTTP クライアントはどこにいったのか?
これらは、リポジトリやサービスの外側にある。そして定義した 受付リポジトリ
を実装し、実際に DB にアクセスして値を読み書きする。 HTTP クライアントを使用する 自動車検索サービス
を実装するクラスもその層に置く。外側の層が内側の層のインタフェースを実装しているのだから、外側から内側への依存だ。
ちなみに、この 層
というのは、ディレクトリのことでもパッケージや名前空間のことでもない。
理論的な境界を引いた領域のことを比喩して層と呼んでいるに過ぎない。
そのためディレクトリやパッケージを切らなくてもクリーンアーキテクチャを採用したコードは書ける。
ちなみにこの実装による依存によって内側のコードが外側のコードに依存しないようにするテクニックを、 Dependency Inversion Principle (DIP; 依存性逆転の原則) と呼ぶ。
そしてもう一つ重要なことがある。それは、誰がインスタンスを生成するのか問題である。
たとえば先程の 指定した名前の自動車の修理受付を作成するユースケース
を使用するプレゼンターをインスタンス化するには、そのユースケースのインスタンスが必要になる。このユースケースは 受付リポジトリ
と 自動車検索サービス
を使用するので、インスタンス化するためにはこれらのリポジトリのサービスの実際のインスタンスが必要となる。そして実際のインスタンスは実クラスを知っていないと作成できない。 受付リポジトリ
だけを知っていては、 受付リポジトリのMySQL実装
のインスタンスを入手できないのである。
そのインスタンスを得るには、かなり外側の層にある 受付リポジトリのMysQL実装クラス
を知っている(=依存する)必要がある。
つまり、すべての層のコードに依存しなければならないタイミングがあるのだ。
すべてのインスタンスを DI コンテナなどで注入させるとしても、では DI コンテナを初期化するのはだれかという問題が残る。
これは main 関数が行う。アプリケーションのエントリーポイントだ。
アプリケーションを実行したとき、必ず何らかのコードが実行される。ここで行えば良い。 アプリケーションには必ずエントリーポイントが必要である。これは仕方のないことである。これを回避することはできないし、する意味もない。