クリーンアーキテクチャ(The Clean Architecture翻訳)

Robert Martin (a.k.a. ボブおじさん) による、 The Clean Architecture の翻訳です。似たようなアーキテクチャである ヘキサゴナルアーキテクチャ も翻訳したので参考にしてください。

この記事を翻訳して公開したことは 8th Light, Inc. に報告済みです。いまのところ苦情は来ていません。


The Clean Architecture

ここ数年以上、システムのアーキテクチャに関する実にさまざまなアイデアを見てきた。これには、次のものが含まれる:

これらのアーキテクチャはどれも細部は異なるけれども、とてもよく似ている。これらはいずれも同じ目的を持っている。関心の分離だ。これらはいずれも、ソフトウェアをレイヤーに分けることによって、関心の分離を達成する。どれも、最低ひとつは、ビジネスルールのためのレイヤーと、インターフェイスのためのレイヤーがある。

これらのアーキテクチャは、いずれも、次のようなシステムを生み出す:

  1. フレームワーク独立。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。
  2. テスト可能。ビジネスルールは、UI、データベース、ウェブサーバー、その他外部の要素なしにテストできる。
  3. UI独立。UIは、容易に変更できる。システムの残りの部分を変更する必要はない。たとえば、ウェブUIは、ビジネスルールの変更なしに、コンソールUIと置き換えられる。
  4. データベース独立。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない。
  5. 外部機能独立。実際のところ、ビジネスルールは、単に外側についてなにも知らない。

記事冒頭の図は、これらのアーキテクチャを単一の概念に無理なく統合する試みである。

依存ルール

これらの同心円は、ソフトウェアの異なる領域を表している。一般に、内側にいくほど、ソフトウェアは高レベルになる。外側の円はメカニズムで、内側の円は方針だ。

このアーキテクチャを機能させる重要なルールが、依存ルールだ。このルールにおいては、ソースコードは、内側に向かってのみ依存することができる。内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。

同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。

エンティティー

エンティティーは、大規模プロジェクトレベルのビジネスルールをカプセル化する。エンティティは、メソッドを持ったオブジェクトかもしれない、あるいは、データ構造と関数の集合かもしれない。エンティティが、大規模プロジェクト内で、たくさんの異なるアプリケーションから使われるのであれば、どちらでも問題ない。

大規模プロジェクトではなく、ひとつのアプリケーションを書いているだけであれば、エンティティは、そのアプリケーションのビジネスオブジェクトである。それらは、もっとも一般的で高レベルなルールをカプセル化する。それらは、外側のなにかが変わっても、変わらなさそうなものだ。たとえば、それらのオブジェクトは、ページナビゲーションの変更やセキュリティからの影響を受けないことが期待できる。アプリケーションの動作への変更が、エンティティーレイヤーに影響を与えるべきではない。

ユースケース

このレイヤーのソフトウェアは、アプリケーション固有のビジネスルールを含む。このレイヤーは、システムのユースケースすべてをカプセル化および実装する。これらのユースケースは、エンティティからの、あるいはエンティティーへのデータの流れを組み立てる。そして、エンティティ、プロジェクトレベルのビジネスルールを使って、ユースケースの目的を達成せよと指示する。

このレイヤーの変更は、エンティティーには影響を与えないことを期待する。このレイヤーが、データベース、UIあるいは、共通のフレームワークの変更から影響を受けないことも期待する。このレイヤーは、そういった関心からは隔離される。

しかしながら、アプリケーションの操作への変更は、ユースケースに、つまりは、このレイヤーのソフトウェアに影響することを期待する。ユースケースの詳細が変われば、このレイヤーのコードは、確実に影響を受ける。

インターフェイスアダプター

このレイヤーのソフトウェアは、アダプターの集合だ。これは、ユースケースとエンティティにもっとも便利な形式から、データベースやウェブのような外部の機能にもっとも便利な形式に、データを変換する。たとえば、このレイヤーは、GUIのMVCアーキテクチャを完全に内包するだろう。プレゼンター、ビュー、そしてコントローラーは、すべてここに属す。モデルは、コントローラーからユースケースに渡され、そして、ユースケースからプレゼンターやビューに戻される、単なるデータ構造である可能性が高い。

同じように、データはこのレイヤーで、エンティティーやユースケースにもっとも便利な形から、どんな永続化フレームワークが使われているにしろ、それにとってもっとも便利な形に変換される。例えば、データベースなど。この円よりも内側のコードは、データベースについてなにも知るべきではない。もしこのデータベースがSQLデータベースであるならば、どんなSQLであれ、このレイヤーに、もっと言うと、このレイヤーの中のデータベースに関連した部分に、制限されるべきだ。

また、このレイヤーには、その他すべてのアダプターもある。それらは、外部の形式(たとえば外部サービス)から、ユースケースとエンティティーで使われる内部形式にデータを変換するために必要なものだ。

フレームワークとドライバー

一番外側のレイヤーは、一般に、フレームワークやツールから構成される。データベースやウェブフレームワークなどだ。一般に、このレイヤーには、多くのコードは書かない。ただし、ひとつ内側の円と通信するつなぎのコードは、ここに含まれる。

このレイヤーには、詳細がなにもかも詰め込まれる。ウェブは、詳細だ。データベースは、詳細だ。これのものが悪影響を与えることのないように、外側に保っておく。

4つの円じゃないとダメなの?

いや、この円は、コンセプトを伝えるための方便だ。これらの4つ以外が欲しくなる可能性はある。ちょうど4つでなければいけないという決まりはない。しかしながら、依存ルールは、常に適用される。ソースコードの依存性は、常に内側に向かう。内側に移るにつれて、抽象化のレベルは上がる。一番外側の円は、低レベルで具体的な詳細だ。内側に移るにつれて、ソフトウェアは抽象的になっていき、高レベルの方針をカプセル化する。一番内側の円は、もっとも一般性がある。

境界をまたがる

右下の図は、どのように円の境界をまたがるのかの例だ。これは、コントローラーとプレゼンターが、次のレイヤーのユースケースと通信する様子を示している。制御の流れに注意して欲しい。コントローラーからはじまり、ユースケースを抜けて、プレゼンターで実行されることがわかる。ソースコードの依存性にも注意。いずれも、内側のユースケースを向いている。

われわれは、この明らかな矛盾を 依存関係逆転の原則(Dependency Inversion Principle) で解決することが多い。たとえば、Javaのような言語では、インターフェイスと継承関係を組み合わせて、ソースコードの依存性が、境界をまたがった右隣の点の制御の流れとは、逆になるようにするだろう。

たとえば、ユースケースがプレゼンターを呼び出す必要がある場合を考えてみよう。しかしながら、この呼び出しは、直接行われるべきではない。なぜなら、依存性ルール:外側の名前を、内側から言及することはできない、に違反するからだ。なので、ユースケースには、内側の円にあるインターフェイス(Use Case Output Portと書かれている)を呼ばせる。そして、円の外側のプレゼンターには、それを実装させる。

まったく同じテクニックが、アーキテクチャーの境界をまたがる、いたるところで使われる。動的な多体のアドバンテージを利用して、ソースコードの依存性が制御の流れの逆になるように作る。そうすれば、制御の流れがどこに入り込もうとも、依存性ルールを満たすことができる。

どんなデータが境界をまたがるの?

典型的には、境界をまたがるデータは、シンプルなデータ構造だ。基本的な構造体や、シンプルなデータ転送オブジェクト(Data Transfer object)を好みに応じて使うことができる。あるいは、データは、単純に関数の引数でも良い。または、それをハッシュマップにしても良いし、オブジェクトとして構築しても良い。重要なことは、隔離された、シンプルなデータ構造が、境界をまたがって渡されるということだ。われわれは、ズルをして、エンティティやデータベースの行を渡すべきではない。データ構造が、依存性ルールに抵触するような依存性を持つべきではない。

たとえば、多くのデータベースフレームワークは、クエリーに応答して便利なデータフォーマットを返す。これをRowStructure(行構造)と呼ぶとしよう。この行構造を境界をまたがって内側に渡すべきではない。それは、依存性ルールに違反する。なぜなら、内側の円に外側の円についてなにがしかを知ることを強制するからだ。

なので、境界をまたがってデータを渡すときには、常に、内側の円にとって扱いやすい形式になる。

結論

これらの簡単なルールに従うのは、難しいことではない。そして、頭痛がひどくなるのを防いでくれるだろう。ソフトウェアをレイヤーに分けることで、そして、依存性ルールに従うことで、本質的にテストしやすいシステムを作れるし、依存性ルールがもたらす恩恵ものきなみ受けられるだろう。システムの外部パーツ(データベースやウェブフレームワークなど)が古くなったら、それらの古臭い要素を、最小の取り組みで置き換えられる。