🧐

ちょっとだけわかるクリーンアーキテクチャ

2024/05/19に公開

ちょっとだけわかるクリーンアーキテクチャ

なんとなくクリーンアーキテクチャの内容を知ってはいたが、しっかりと学んだことがなかったので自分なりの解釈とともにまとめてみた。

  • この記事でわかること
    • クリーンアーキテクチャの目的
    • クリーンアーキテクチャの概要
    • 例の図に書いていること
  • この記事でわからないこと
    • 具体的な実装方法

アーキテクチャとは?

アーキテクチャとは構造・構成のことで、ソフトウェア全体、あるいは一部の構造・構成のこと。
(家を建築する場合、家の形、外観、空間や部屋のレイアウトなど)

ソフトウェアアーキテクチャには例として以下のようなものがある

  • レイヤードアーキテクチャ
  • ヘキサゴナルアーキテクチャ
  • オニオンアーキテクチャ
  • サーバーレスアーキテクチャ
  • サービス指向アーキテクチャ
  • マイクロサービスアーキテクチャ

アーキテクチャの目的

ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保守するためのに必要な人材を最小限に抑えることである。

これを実現するために、機能を簡単に開発・変更・拡張できるアーキテクチャを構築する必要がある。

つまり、ソフトウェアが「ソフト」であるべき理由である。

先述のアーキテクチャも、おおよそはこの目的のために考えられたアーキテクチャであろう(おそらく)

この「ソフトさ」を追求したアーキテクチャのひとつがクリーンアーキテクチャである。

YAGNIに原則は皆さんの知るところではあると思いますが、アーキテクトについてはこの視点についても注意する必要があるでしょう。

YAGNIの原則

You Ain't Gonna Need It

どうせ使わない(必要な時になるまで実装しないほうがいい)、という原則

アーキテクチャの選定

クリーンアーキテクチャはどのようなソフトウェアにおいても最適なアーキテクチャである、という考えは間違っている。

ソフトウェアが成長するにつれて最適なアーキテクチャは常に変化するものである。

クリーンアーキテクチャ

では、クリーンアーキテクチャとはどのようなアーキテクチャなのか?

クリーンアーキテクチャのポイントは以下です。

  • レイヤー構成(円の階層構造)
  • 依存の方向性(矢印の方向)

簡単のため、PHP(Laravel)を使用する場合を考え、馴染み深いものだけを残してみましょう。

Presenters, Gatewaysを消して、Services, Models, Viewを配置しました。

EntitiesはModels, Servicesと厳密には違うと思いますが、おおよそ同じと捉えて問題ないでしょう。

それぞれより詳しく見ていきましょう。

レイヤー構成

レイヤー構成は内部から外部に向かって以下のようになっています。

内部に行くほど抽象度・方針のレベルが高くなり、外部に行くほど具体的な詳細のレベルが高くなる。

具体例として銀行ローンのビジネスで確認していきます。

Enterprise Business Rules

最重要ビジネスルール、方針。最重要ビジネスデータを扱う。最も変更されにくいもの。

ソフトウェアの中心となる概念。

Entities(Models,Services)
: 密接に結びついた最重要ビジネスルールと最重要ビジネスデータをオブジェクトとしてまとめたもの。

銀行のローンビジネスの場合、以下のようなModelが考えられる

  • principle: 元本
  • interest: 利率
  • period: 期間
  • makePayment(): 支払い
  • applyInterest(): 利息適用
  • chargeLateFee(): 遅延料金適用

UseCasesの変更によって、Modelsが影響を受けることはない。

Application Business Rules

アプリケーション固有のビジネスルール。

UseCases
: Modelの最重要ビジネスルールをいつ・どのように呼び出すかを規定したルール。Modelの制御。Modelに依存する。

銀行ローンビジネスの場合、以下のようなUseCaseが考えられる。

新規ローンのために連絡先情報を収集する

入力:氏名、辞書、生年月日、運転免許証番号など
出力:入力と同じもの + 与信スコア

通常コース:
1. 氏名を受け取り、検証する。
2. 住所、生年月日、運転免許証番号などを検証する。
3. 与信スコアを取得する。
4. 与信スコアが500未満なら「否認」にする。
5. それ以外なら「顧客」を作成して「ローンの支払見積もり」を起動する。

DB, UI, フレームワークなどの変更によって、UseCasesが影響を受けることはない。

Interface Adapters

UseCases, ModelsのフォーマットからDB,Webなどに便利なフォーマットに変換するアダプター。

Controllersなど

Frameworks & Drivers

コードをあまり書かないレイヤーであり、詳細。
変更される可能性が最も高く、最も外部に配置される。

View, Web, UI, Devices, DB, External Interfacesなど

依存の方向性

図に示される依存の方向性は以下のようになっている。

これは、あるレイヤーが変更される場合の他のレイヤーへの影響を表現するものである。

Laravelで開発したことがある人なら、システムがどのような流れで処理されていくかを簡単に想像できるでしょう。

例えば、Viewsによる見え方を変更しても、Controllersを変更する必要性はありません。

Controllersでデータのフォーマットを変更した場合、Viewsで表示する画面を変更する必要があるかもしれませんが、UseCasesが変わることはありません。

UseCasesで使用ケースが変更される場合、Controllersから渡されるデータ構造が変更されますが、Modelsが提供する最重要のビジネスルールは変わりません。

Modelsで最重要のビジネスルールが変わる場合、それを取り扱うUseCasesにも変更が必要でしょう。

以上のように、それぞれのレイヤーが矢印の向きに依存しており、依存先に変更が発生した場合は依存元にも変更が必要になります。
逆に、依存元に変更が発生しても、その依存先には何も影響を与えません。

この依存関係の場合、依存先が依存元よりも変更されやすいものだったらどうでしょうか?

極端な例で言えば、ServicesがUseCasesよりも変更されやすいものだったとしたら、UseCases本来の変更目的以上にServicesの変更が影響してくるのです。

つまり、依存関係の方向性として重要なのは、より変更されにくいものは依存先に、より変更されにくいものは依存元にするようにするということです。

DBの問題

以上の依存関係ではDBを扱っていませんでした。ので、Repositoryとして追加してみましょう。

基本的にServicesからDBに接続して必要なデータを取得するので以下のようになるでしょう。

本当にこれでいいでしょうか?

Services(Models, Entities)は最重要のビジネスルールで、最も重要なものだったはずです。

これがDBに依存しているということはDBが変更される事によって(どんなDBを使うか、例えばOracleやMySQLやNoSQLのどれを使うかによって)、最重要のビジネスルールが変更されてしまうことになります。

もちろんそんなことがあってはいけません。特にDBに関しては変更される可能性が高いものなので、そのたびにビジネスロジックが変わってしまっては大変なことになります。

そのため、最初のClean Architectureの図では一番外側に配置されています。矢印の向きも、ServicesからDBではなく、DBからServicesになっています。

ではこの依存関係の向きを変えるためにはどうしたらいいでしょうか?

ここで重要になってくるのがSOLID原則の依存関係逆転の原則(DIP)です。

SOLID原則

SOLID原則は以下の5つの原則から成り立っています。

  • SRP: Single Responsibility Principle
    • 単一責任の原則
  • OCP: Open-Closed Principle
    • オープンクローズドの原則
  • LSP: Liskov Substitution Principle
    • リスコフの置換原則
  • ISP: Interface Segregation Principle
    • インターフェイス分離の原則
  • DIP: Dependency Inversion Principle
    • 依存関係逆転の原則

依存関係逆転の原則

依存関係逆転の原則は上位レベルの方針は、下位レベルの詳細の実装コードに依存すべきでなく、詳細が方針に依存すべきである、という原則です。

結論から言うと、IF(Interface)をかませることで依存関係を逆転させることができます。

Interfaceとは、異なるレイヤー同士を接続する境界で、入出力の内容を定義するものです。

実際のコードで言うと、関数の引数・戻り値を設定するものです。

interface RepositoryInterface {
  public function foo(string $bar): string
}

実際の実装をする場合はimplementsなどで実際のロジックを実装することができます

class Repository implements RepositoryInterface {
  public function foo(string $bar): string
  {
    console.log('baz');
  }
}

我々のパソコンにもUSBなどのインターフェイス(規格)が存在していますが、その先がマウスなのか、キーボードなのか、カメラやマイクなのかはあらかじめ認知しているものではありません。

そのため、インターフェイスをかませることで、

以下の依存関係を逆転させたい場合、

Interfaceをかませると以下のようになります。

Servicesから参照するのはあくまでRepositoryInterfaceで、これらをまとめてServicesComponentとして境界をわかりやすくしています。

そしてこのRepositoryInterfaceをRepositoryとして実装します。(白抜きの矢印はInterfaceの実装)

これによって依存関係を逆転することができました。

依存関係逆転の法則は、アーキテクチャのあらゆる構成の依存関係に対して、Interfaceをりようすることで自在に依存の方向性をコントロールするという強力なものです。

アーキテクチャへの適用

以上の内容をもとに、依存関係逆転の法則を適用すると以下のようになります。

これで依存方向の整理ができました。もうDBを変更する際にServicesの最重要なビジネスルールを変更する必要はありません。

これまでの説明はPHPの説明用に簡略化したものでした。

これをもと汎用的なアーキテクチャとして表現したものが、書籍でも扱われている以下になります。

これは何も新しい概念が出てきたわけではなく、例の図をクラス図として置き換えたものです。

InputBoundary、OutputBoundaryについてはそれぞれController, PresenterとのInterfaceになります。

InputData, OutputDataはデータ構造を示しています。それぞれ、ControllerからUseCasesへ扱いやすい形にしたもの、UseCasesからPresenterへ扱いやすい形にしたものです。Request, Responseのようなイメージです。

境界線部分の矢印の向きは変わらず内部へ向かっているのも見て取れるでしょう。

結局クリーンアーキテクチャって?

結局クリーンアーキテクチャとは何でしょうか?私は以下のようなものと考えています。

  • 最重要ビジネスルール(方針)を中心としたアーキテクチャ
  • 依存関係は、常に最重要ビジネスルールが最上位レベルであり、下位レベルの詳細の変更によって上位レベルが影響を受けないようにしたアーキテクチャ
  • 各レイヤー、コンポーネントをそれぞれ別々に開発・テストができるアーキテクチャ
  • 下位レベルの詳細の決定を先延ばし、または変更を容易にすることができ、変更コストが低いアーキテクチャ

コードは生モノで、放っておくと徐々に腐っていきます。

機能追加や保守によりコードが変更されればサれるほど腐っていきます。

この腐敗速度をなるべく遅くし、コードの健康状態を維持するためにアーキテクチャが必要です。

ソフトウェアに必要なアーキテクチャの構成は、ソフトウェアの成長に従って変化します。

常にソフトウェアの状態を監視して適切なアーキテクチャを検討していくのが、ステークホルダーの未来を明るくすることでしょう。

Reference

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Discussion