ワーサー王列伝

主にプログラミング?

Clean Architectureのお気持ち

最近は設計に悩むことが多いので、ヒントを得るために巷で話題のクリーンアーキテクチャについて調べて私なりにまとめてみました。

TL;DR

どの外部に対しても依存性をちゃんと守れば、コードは冗長になるけど、テストとかメンテナンスがしやすくなるね。

ドメイン駆動開発

クリーンアーキテクチャは、ドメイン駆動開発というソフトウェアの開発手法を実践する上での考え方の一つであるため、ここでは、クリーンアーキテクチャの解説に先だってドメイン駆動開発の簡単な解説を行う。

ドメイン駆動開発とは?

ドメインと呼ばれる「制作しているプログラムが適応される領域において特に重要な場面やルールの複雑さ」に焦点を当ててプログラム開発を行うことをドメイン駆動開発(DDD)という。

モデリング

ドメイン駆動開発では、目的を達成するためのフレームワークやツールではなく、目的となる領域の複雑さに注目し、それをうまく表現する抽象的なモデルを考え、ソフトウェアとして実装することを最優先に考える。

そのために、プログラムが使われる領域に詳しい人と、プログラムの開発者が、同じ言葉を使って必要なモデルの探求を行うこともドメイン駆動開発の一部とされる。

ドメイン駆動開発のメリット

ドメイン駆動開発を行うことで、ドメインの複雑さをプログラムに内包させることが可能になり、プログラムを用いるユーザーや業界から見てより価値のあるソフトウェアを開発することが可能になる。また、独自のユースケースや複雑な内部処理と、使用するフレームワークやツールを自然に切り離すことができ、その結果メンテナンスしやすいプログラムを書くことができることもメリットの一つとなる。

ドメイン駆動開発の具体的なアプローチは[6]などを参照すると分かりやすい。

ドメイン駆動開発のアーキテクチャ

実際にドメイン駆動開発に適したプログラムの全体像を表現するために、いくつかのプログラムの構造が提案されている。それらはアーキテクチャと呼ばれ、レイヤードアーキテクチャ、ヘキサゴナルアーキテクチャ、オニオンアーキテクチャ、クリーンアーキテクチャなどいくつかの種類がある。

これらのアーキテクチャの考え方を用いることで、ドメイン駆動開発のメリットの一つであるメンテナンスの容易さを獲得することが可能となる。

クリーンアーキテクチャ概要

クリーンアーキテクチャの主な思想は以下の図にまとまっており、以降の解説はこの図をもとにして行う

f:id:tomioka2012:20190701105539j:plain [2]

このように、クリーンアーキテクチャは同心円状に分割された複数のレイヤから構成される。レイヤの分割は、解決すべき問題を分割し、それぞれのレイヤで考えるべきことを明確にする。

クリーンアーキテクチャのメリット

クリーンアーキテクチャを用いることで、以下の特徴をもつソフトウェアを開発することが可能になる。

  • フレームワーク独立

  • テスト可能

    • UIやデータベースなどの外部要素がなくても、メインとなるロジックのテストが可能となる。
  • UI独立

    • 内部の実装を変更せずにUIを変更することが可能となる。

依存性のルール

図のそれぞれのレイヤは、ソフトウェアの異なる領域を表しており、外側の円がメカニズムを、内側の円が方針を記述している。

これらのレイヤには、決められた依存性がある。原則として、内側のレイヤは外側のレイヤに依存してはならず、ソースコードは内側にのみ依存することが出来る。

具体的には、外側のレイヤで宣言された名前を内側のレイヤで使用してはいけない。これは、変数だけでなく、関数、クラスなどプログラム内の名前の付けられるすべてのものに適応される。

各レイヤについて

Entities(Enterprise Business Rules)

図の黄色い部分。

外部のプログラムに依存しない、プロジェクト内で不変なルールを記述し、カプセル化を行う。

メソッドを持つオブジェクトのような形で実装され、プロジェクト(もしくは同じドメイン)内で使い回すことが可能。

Use Cases(Application Business Rules)

図の赤い部分。

制作するソフトウェアやアプリケーションに固有の振る舞いやルールを記述し、カプセル化を行う。

UIやフレームワークからは独立していながら、Entitiesには依存し、Entitiesへのデータの入力やEntitiesからのデータの出力の流れを組み立てる。また、その流れを通るデータの構造を定義する。

立場としては、Entitiesを利用して要件を満たす振る舞いを記述する役割を持つ。

Controllers(Interface Adapters)

図の緑の部分。

外部のUIやフレームワークで使われているデータの形式と、Use Cases以内で使われているデータ形式を繋ぎ、相互に変換する

Use Cases以内で使われているデータ形式は、外部のUIやフレームワークに依存せず、Use CaseやEntitiesで用いるために最も都合の良いものとなる。

MVCやMVPの文脈で述べられるPresenter、View、Controllerは全てこのレイヤに内包される。このとき、ModelにあたるのはUse Casesと受け渡しを行うデータ構造にあたる。

External Interfaces(Frameworks & Drivers)

図の青の部分。

一般にフレームワークやツールから構成され、詳細な処理を記述する。UIやデータベースなどもここに内包される。

また、一部内側のレイヤと通信するための処理が含まれる。

レイヤの境界について

レイヤごとの役割や、依存性のルールを守るために、レイヤの境界の実装は重要なものと考えられる。

図の右下部分には、境界をまたぐControllersとUse Cases間の処理の流れが図視されている。

f:id:tomioka2012:20190701105531j:plain

境界をまたがる処理の実装

図では、Controllersに含まれるControllerとPresenterがUse Casesと通信する様子が描かれている。

Use Case Output PortとUse Case Input Portはインターフェースであり、それぞれPresenterとUse Case Interactorで実装されている。また、Use Case InteratorはUse Case Outputに、ControllerはUse Case Input Portにそれぞれ依存している。

このとき、依存や実装の矢印が内側のレイヤ(Use Cases)から外側のレイヤ(Controllers)には向いていない。このことから、図の関係は依存性のルールを守っているといえる。

依存関係逆転の原則

この依存関係(特にUse Case InteratorとPresenterの関係)は依存関係逆転の原則によって解決されている。

Use Case Interatorが出力したいデータをPresenterに渡すためには、通常Presenterへの参照を持つ必要がある。しかし、それは依存性のルールを破ることに他ならない。

そこで、Use Case Interatorの出力先をインターフェースとし、Use Cases内部に記述する。これがUse Case Outputにあたる。Use Case InteratorはUse Case Outputに依存するため、依存性のルールは守られ、PresenterがUse Case Outputを実装することで、望んだ挙動を得ることが出来る。

このような手法が境界をまたがる部分で一般に用いられることとなる。

境界を通るデータ

境界をまたがって受け渡されるデータ構造は、通常はシンプルなデータ構造である。そしてそれは、外部の実装の影響を受けるものであってはならない。

データの構造は、内側のレイヤで定義され、常に内側のレイヤにとって使いやすいものであることが望ましい。

まとめ

プログラムを取り囲むUIやフレームワークなどのすべての実装に対して、漏れなく依存性を考慮し、複数の段階でカプセル化を行うことで、テストがしやすく、外部から独立した可搬性のある、一貫したルールに基づいたソフトウェアが作成できる。

具体的な実装は[7]が分かりやすく、以上に目を通した上で見てみると得るものが多いかと思われる。

参照