コンテンツにスキップ

クリーンアーキテクチャ

クリーンアーキテクチャ(The Clean Architecture)とは、Robert C. Martin が主張している、きれいなアーキテクチャとはこういうものだ、という目安である。

Clean Coder Blog

図の意味

黄色の層(Enterprise Business Rules)

ユースケースが扱うオブジェクトであり、エンティティという名前がついている。エンティティは値を持ったオブジェクトであり、さらにオブジェクトは自身を操作するメソッドを持っていたり、制約条件を持っている。

DDD でいうドメイン知識(ルール/制約)が含まれるだろう。DDD でいう値オブジェクトも多分含まれるだろう。

赤色の層(Application Business Rules)

ユースケースを実装する層(ビジネスロジック層)。フレームワークを意識しないという点で抽象的なコードになっている。

エンティティのリストからエンティティを検索するとか、エンティティ単体では実現できないコードが入っている。

エンティティの持つメソッドを呼び出してビジネスロジックを記述する。

緑色の層(Interface Adapters)

橋渡し役。抽象的なユースケースにとって便利な形式に変換したり、ユースケースの実行結果をフレームワークが扱う形式に変換したりする層だと思う。

青色の層(Frameworks & Drivers)

多分 MySQL とか、Express.js とか、React とかのフレームワークが当てはまる。

①②③ について

図中に書けてないが Controller(ユーザー入力を受け取る人) が直接 UseCase の実装を参照しておらず、インターフェースを経由しているのがポイントらしい。Presenter(表示する人)も同様。

矢印の向きに着目する。緑色の層は赤色の層に依存している。Controller は UseCase のインターフェースに依存している。Presenter は UseCase のインターフェースを実装している。

同心円について

  • 同心円の内側のものほど抽象的になる。つまりフレームワークやハードウェアに依存するコードはなくなる。
  • 同心円の外側のものほど具体的になる。また開発対象から遠くなる。
  • 同心円内の矢印は依存性。外から中に向かっている。依存関係逆転の原則、つまりインターフェースでやりとりさせることで解決できる。
  • 同心円内の矢印は処理の流れを指すものではない。例えば Gateways から DB アクセスは当然あり得る。
  • 同心円は 4 つで分けられているが、4 つが正解と述べているわけではない。
  • 外側の層は内側の層に依存するのがクリーンアーキテクチャの肝だが、緑色の層(Interface Adapters)については外界とユースケースの橋渡し役であり、現実には外界に依存する必要があるという点で矛盾しているという意見がネットにはある。

クリーンアーキテクチャが述べていることは以下 3 点だと解釈している。

  • フレームワークに依存しない、テスト可能で、UI に依存せず、外界にも依存しないのが美しいアーキテクチャである。
  • 上を守るために、ハードウェアや枠組みに依存するコードとそうでないコードを層として分けるべき。
  • 上を守るために、どの層の部品もインターフェースを作っておいて、交換可能な部品にするべき。

依存方向=処理の流れではないことに注意する

出典不明の画像があるがわかりやすい。

サーバーサイド未経験の大学生が 4 日で Golang×CleanArchitecture の API を構築した話 #アーキテクチャ - Qiita

ユーザが UI を操作したとき、応答が UI に返ってくるまでの処理の流れを示したものだと思われる。

依存方向は常に内側だが、処理は外側を行き来してもアーキテクチャ的に問題ないようだ。

クリーンアーキテクチャというアーキテクチャは存在するのか

クリーンアーキテクチャなんてものはない(クリーンアーキテクチャーの読み方) - プログラマのはしくれダイアリー

美しいアーキテクチャというのはこんな感じだ、というのが上記の図であって、これがクリーンアーキテクチャというアーキテクチャだという提唱ではないという声。

クリーンアーキテクチャの Usecase はなぜ Controller へ値を返すのではなく Output Port として Presenter を呼び出すのか

クリーンアーキテクチャの Usecase はなぜ Controller へ値を返すのではなく Output Port として Presenter を呼び出すのか - Runner in the High

クリーンアーキテクチャの右下の図 │ nrslib

最近の Web フレームワーク(Express.js とか NestJS とか Flask とか FastAPI とか)では以下のように結果を Controller に return する事が多い。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class UseCaseInteractor implements IUseCaseInputPort {
  constructor(private useCaseOutputPort: IUseCaseOutputPort) {}
  sayHello() {
    return "Hi";
  }
}
class Controller {
  constructor(private useCaseInputPort: IUseCaseInputPort) {}
  sayHello() {
    console.log(useCaseInputPort.sayHello());
  }
}

しかし、クリーンアーキテクチャにおける同心円の図と、図の右下の処理フローにしたがって実装すると以下のような感じになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Presenter implements IUseCaseOutputPort {
  sayHello() {
    console.log("Hi");
  }
}
class UseCaseInteractor implements IUseCaseInputPort {
  constructor(private useCaseOutputPort: IUseCaseOutputPort) {}
  sayHello() {
    useCaseOutputPort.sayHello();
  }
}
class Controller {
  constructor(private useCaseInputPort: IUseCaseInputPort) {}
  sayHello() {
    useCaseInputPort.sayHello();
  }
}

要するにクリーンアーキテクチャにおいては Controller へ入力したとき、UseCase は Controller に結果を戻すのではなく、Presenter に結果を渡すという形で出力する。この点でクリーンアーキテクチャは伝統的な階層型アーキテクチャとは異なる。

Web フレームワークの思想にそぐわない場合、無理して導入することはないという意見がネットでは見られる。自分もそれでいいと思う。

Humble Object パターン

テストしやすいコードとテストしにくいコードは分けておくこと。

❌️悪い例
1
2
3
4
5
function func() {
  const values = ["aaa", "bbb", "ccc"];
  const i = Math.floor(Math.random() * values.length);
  return values[i];
}
✅️良い例
1
2
3
4
5
6
7
8
function funcA(min: number, max: number) {
  return = Math.floor(Math.random() * (max - min)) + min;
}

function funcB() {
  const values = ["aaa", "bbb", "ccc"]
  return values[funcA(0, values.length)];
}