クリーンアーキテクチャ

The Clean Architecture

2022.07.14

Clean Architectureとは

Clean Architectureとはサーバーサイドで実装する際のアーキテクチャの指針を指します。オニオンアーキテクチャやヘキサゴナルアーキテクチャなど様々な設計思想が乱立していた頃、それらをまとめ上げるようにして提唱されたのがClean Architectureです。

CleanArchitecture

この図が有名ですが、これはクリーンアーキテクチャに基づいた1つの設計の例を表しています。上の図が表しているのは

  • フレームワーク、データベースが独立しており、交換可能であること。
  • 依存関係は矢印の向きに固定され、内側が外側に依存しないこと。
  • 逆の依存関係が必要な場合はDIP(依存関係の逆転)で乗り切ること の3つです。

依存とは?

矢印が表す"依存"とは何かを考えてみましょう。Clean Architectureはややこしい上に依存という言葉が至るところに出てくるので、もうすこし分かりやすい言葉に言い換えてみましょう。例えば、AがBに依存する、すなわちA -> Bという関係は「AがBの入出力を知っている」や「Bを変更するとAも変更が必要になる」(逆に言えばAが変更されてもBに影響はない)と考えるとより分かりやすいかもしれません。


Clean Architectureの依存関係

clean_architecture_circle

Frameworks & Drivers

一番外側の層はフレームワークやデータベースに依存する層です。この層に向けられた矢印がないことから、ここに依存している層がないことがわかります。これを言い換えると、この層を変更しても他の層に影響はない、つまりフレームワークやデータベースが交換可能となるわけです。

Interface Adapters

この層は外部のフレームワークが使いやすい形にデータを整えます。実はこの層がClean Architectureの難しいところなのですが、それはもう少し後で。。。

Application Business Rules

その名の通りビジネスルールが関わってくる層です。アプリケーション固有の処理がここに関わってきます。逆にこの層はどんなフレームワーク、データベースを使っているかは知りません。

Enterprise Business Rules

ここは分かりやすく、entityの層です。ここに変更が加わればプログラム全体に影響がありますが、他の層に変更があってもここには影響がありません。これは直感的にも正しいように思います。


依存関係の逆転

さて、Clean Architectureの目玉であるDIP(dependency inversion principle)について考えていきましょう。
ここまででもう実装のイメージが湧いている人はまずいないと思いますが、実際に作ってみると大きな問題にぶつかります。少し掘り下げてみましょう。 ControllerでGET methodを受け取り、データベースから該当するレコードを取り出して返すケースを考えます。GETを受け取ったcontrollerは外から2つ目の層です。この層がusecaseを呼び出します。controllerはusecaseの入出力(例えばidを渡せば対応するレコードが返ってくる)を知っているので当然controllerはusecaseにある関数を呼び出すことができます。

呼び出されたusecaseの関数はidを受け取ってdatabaseからレコードを、、、とするのですがデータベースは層の一番外側です。この層はデータベースやフレームワークについて何も知らないのです。これではidからレコードを取ってくることができません。これを解決するのが依存関係の逆転です。

DIP

上の図が依存関係の逆転を表します。つまりA->BでBがAを呼び出したくなった場合にどうすればよいのか、ということを表しています。赤色が内側の層、緑が外側です。
内側の層からすれば外側の層にある関数の入出力を知りたいわけです。ここで少し発想を転換してみましょう。


外側のことが分からないなら内側で決めてあげればいいじゃない。


もう一度上の図を見てください。赤色の層は緑の層のことを何も知りません。なのでとりあえず欲しい関数の引数と返り値の型を指定します。「よくわからないけどidを入れたら対応するレコードを返すfindOneという関数が欲しいなぁ」という具合です。これを受けて緑の層はそれに沿った関数を提供します。これが依存関係の逆転です。外に振り回されるのではなく内側でもう決めてるので外側はそれに従ってもらえばよいのです。
騙されたような感じがするかもしれませんが、外が中に依存するという向きは崩れていませんね。


Interface Adapterについて

先ほど先延ばしにした外から2番目の層についての話です。
フレームワークに依存するのは一番外側でした。この層は外の層が扱いやすいようにデータの変換を行う層でしたね。ですが、外側がSQLで書かれるのかORMを使うかによって扱いやすいデータの形というものは変わってきますし、それを外の層を見ずに、というのは無理があるように思います。確かに図にはDB, フレームワークとしか書かれていないのでDBを扱うORMライブラリのようなものはそこに含まれない、と考えることもできます。実際、db.create()みたいな処理が一番外側でしかできないのであればDIPが2層分必要になってしまいますが、Clean ArchitectureでDIPが発生するのは2番目と3番目の層の間のみ、ということになっています。以上のことから、DBの操作はこの層が行っても良いというのが自分の考えです。この点においてClean Architectureはそもそも破綻しているという記事やこの層がデータベースに依存しても良いとする記事もありましたので紹介しておきます。
データベースとORMライブラリは別物でMySQLを使うか、PostgreSQLを使うかは一番外側の層しか知らないけれど呼び出すのはこの層でもよいでしょ、ということなのかも知れません。


参考サイト