今の現場で導入されているクリーンアーキテクチャについて、今更ですがまとめてみました。
クリーンアーキテクチャとは
上記画像で表現されている以下ルールを持ったアーキテクチャとされています。
- ソフトウェアをレイヤーに分割することで関心事を分離する
- ソースコードは円の内側(抽象)の方向に依存する
- 制御の流れと依存関係を逆転させて依存の方向を制御する
1・2は円のルール、3は右下の図のルールです
それぞれのルールについて詳しくみていきます。
①ソフトウェアをレイヤーに分割することで関心事を分離する
前提
レイヤーとは
レイヤーとは、下から上へ積み重ねて全体を構成している階層構造の内の1つ1つの層を指します。
例えば、コンピュータのハードウェア上でOSが動き、OS上にアプリケーションソフトが動いている場合、以下のような階層構造・レイヤーになります。
関心事の分離とは
関心事の分離とは、プログラムの目的や役割ごとにクラスや関数などのアプリケーションの構成要素を分離することです。
例えば、労務クラスの中に労務に関する機能を複数持たせるよりも、以下のように目的に応じて分離されている方が理解しやすく変更もしやすくなります。
- 給与計算クラス
- 年末調整クラス
- 入社手続きクラス
なぜレイヤーに分割して関心事を分離するのか
変更をそのレイヤーに限定させることができるためです。
例として、UI・ビジネスロジック・DBのレイヤーで構成されているカレンダーアプリがあったとします。
このカレンダーアプリで表記を「M月d日」から「M/d」といったUIを変更したいケースがありました。
レイヤーに分割されていないと、1つのクラスやファイルの中からUIに関する処理を見つけるのに時間が掛かります。
さらに、変更したとしてもビジネスロジックやDBに関する処理に影響がないか確認する必要もあります。
しかしレイヤーに分割されている場合、変更する箇所は関心事が表示であるUIレイヤーだとすぐに特定できます。
また必要なレイヤーだけ変更すればいいため、変更による影響を局所的にすることができます。
※クリーンアーキテクチャの円は4つのレイヤーになっていますが、必ずしもこの4つにする必要はありません。
②ソースコードは円の内側(抽象)の方向に依存する
前提
抽象・具体とは
コードレベルの話では抽象はインターフェース、具体はインターフェースを実装したクラスが思い浮かびます。
しかし、アーキテクチャにおける意味は下記です。
-
抽象
ビジネスルール -
具体
デバイス、UI、 DBなど
例えば、メールのビジネスルールは「指定した相手に文章を送る」という抽象的なものです。
一方でデバイス、UI、DBなどはビジネスルールで定めたことを実現するための具体的なものを指します。
依存するとは
ソフトウェア分野の「依存する」とは、プログラムのビルドや実行のために別のプログラムが必要であることです。
例えば、以下はManager
クラスの中でEmployee
クラスをnewしているため、Employee
クラスがないとManager
クラスにコンパイルエラーが発生します。そのため、Manager
クラスはEmployee
クラスに依存しています。
class Manager
{
private Employee employee = new Employee();
}
class Employee
{
}
なぜ抽象方向に依存するのか
抽象度が高い方に依存することで、システムを安定させて変更しやすくすることができるためです。
依存関係による安定度と柔軟性
先述した依存関係には安定度と柔軟性の観点があります。
先程も例に出したEmployee
クラスとManager
クラスで考えてみます。
そのため、Employee
クラスの方がManager
クラスよりも安定度が高くなります。
しかし、Employee
クラスを変更すると依存する側に影響が発生してしまうため、柔軟性は低いといえます。
- 依存する側の変更
依存しているManager
クラスを変更しても、依存されるEmployee
クラスには影響が発生しません。
そのため、Manager
クラスの方が変更がしやすいので、Employee
クラスよりも柔軟性が高くなります。
しかし、Manager
クラスは依存される側の影響を受けるため、安定度は低いといえます。
まとめると下記のようになります。
依存する側 | 依存される側 | |
---|---|---|
安定度 | 低い | 高い |
柔軟性 | 高い | 低い |
つまり、依存する側は柔軟性が高いため変更されやすいものにして、依存される側は安定度が高いため変更されにくいものに設計する方がいいといえます。
抽象は安定度を高く、具体は柔軟性を高くする
先程、抽象はビジネスルール、具体はそれを実現するデバイス・UI・DBと書きました。
再度メールを例として考えてみます。
抽象のビジネスルールは変更されにくいものです。
メールアプリのUIが変更されたとして、「指定した相手に文章を送る」というビジネスルールを変更する必要はありません。
反対に具体は変更されやすいものです。
メールアプリのUIを変更したとしても、「指定した相手に文章を送る」というビジネスルールに影響を与えるべきではありません。
そのため、変更されにくい抽象は依存される側、変更されやすい具体は依存する側になるように設計します。
つまり、ソースコードは抽象方向に依存することで、システムを安定させて変更しやすくすることができます。
③制御の流れと依存関係を逆転させて依存の方向を制御する
前提
制御の流れとは
制御の流れとは、プログラムの実行される順番です。
例えば、以下のコードはHoge
クラス→Fuga
クラスの順番に実行されています。
class Hoge
{
public void Func()
{
var fuga = new Fuga();
fuga.Func();
}
}
class Fuga
{
public void Func()
{
Console.WriteLine("Fuga Func");
}
}
依存関係を逆転するとは
依存関係を逆転するとは、インターフェースを使用することで、制御の流れに対して依存の向きを逆にすることです。
上記の依存関係・制御の流れ
先程のHoge
クラスとFuga
クラスの依存関係と制御の流れは以下になります。
このときの制御の流れと依存関係は同じ向きになっています。
インターフェースを使用した際の制御の流れ・依存関係
Employee
クラスとManger
クラスの間にインターフェースを使用することで、制御の流れと依存関係を逆転させることができます。
class Hoge
{
private IFuga fuga;
public Hoge(IFuga fuga)
{
this.fuga = fuga;
}
public void Func()
{
this.fuga.Func();
}
}
interface IFuga
{
public void Func();
}
class Fuga : IFuga
{
public void Func()
{
Console.WriteLine("Fuga Func");
}
}
インターフェイスを使用したことで、Fuga
クラスに依存していたHoge
クラスはIFuga
インターフェイスに依存するようになりました。
またFuga
クラスがIFuga
インターフェイスに依存するようになり、ここで依存関係と制御の流れは逆転します。
インターフェイスを使用しても、制御の流れに変化はありません。(プログラム実行時にHoge
クラスはインターフェースではなくFuga
クラスを呼び出しているため)
なぜ制御の流れと依存関係を逆転するのか
②のルールである、依存の向きを円の内側(抽象)に向けるためです。
例として、クリーンアーキテクチャの画像にある右下の図を確認してみます。
制御の流れはFlow of control
の矢印の向きです。
依存関係は、Controller
・Presenter
・Use Case Intefactor
がinterfaceに依存しています。
オレンジ枠で囲っている箇所が制御の流れと依存関係が逆転している箇所です。
もしインターフェースが無い状態でUse Case Interactor
がPresenter
を呼び出そうとした場合、Use Case Interactor
はPresenter
に依存する必要があります。
しかし、その場合は円の内側(Application Business Rules
)が外側(Interface Adapters
)に依存してしまうため、円の内側に依存するというルールに反してしまいます。
そこで、Use Case Output Port
インターフェースを使用することで、制御の流れと依存関係の向きを逆転させ、依存の向きを円の内側にすることができます。
つまり、依存関係を逆転して依存の方向を制御することで、②のルールである、依存の向きを円の内側(抽象)に向けることができます。
まとめ
クリーンアーキテクチャは以下ルールを持つアーキテクチャ。
-
ソフトウェアをレイヤーに分割することで関心事を分離する
レイヤーに分割することで関心事を分離することで、変更の影響をそのレイヤーに限定させるため。 -
ソースコードは円の内側(抽象)の方向に依存する
抽象方向に依存させることで、抽象的なビジネスルールを安定させ、具体的なUIやDBを変更しやすくするため。 -
制御の流れと依存関係を逆転させて依存の方向を制御する
依存関係を逆転させることで、依存の方向を抽象に向けるため。(依存の向きを抽象にする理由は2のルール)