「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んだ感想
1.概要
クリーンアーキテクチャを採用した開発(Golang)に携わる際に、継ぎ接ぎの知識で、見よう見まねの実装をしていたところがあったため、クリーンアーキテクチャに対する理解を深めるために、この本を読みました。
本記事ではその感想や、個人的に大切だと思ったところなどをまとめてみました。
2.ソフトウェア設計の目的とは
ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保守するために必要な人材を最小限に抑えることである。
- これを実現するために、優れたクリーンなアーキテクチャと設計がどのようなものかを把握する必要がある
- 早く進む唯一の方法は、うまく進むことである
3.SOLID 原則
ここでは、SOLID 原則がアーキテクチャ的にどのような意味を持つのかについてまとめています。
SOLID 原則の詳細については扱わないので、SOLID 原則になじみのない人は以下などを参照してください。
SRP:単一責任の原則
-
よくある間違い:
どのモジュールもたったひとつのことだけを行うべき ×
モジュールはたったひとつのアクターに対して責務を負うべきである 〇
(アクター:変更を望む人たちをひとまとめにしたグループ) -
別々のアクターのコードをひとつにまとめてしまって、とあるアクターのための変更が、別のアクターに影響を及ぼすのが良くない
OCP:オープン・クローズドの原則
- コンポーネントを階層構造にまとめて分割した際に、上位レベルのコンポーネント(ビジネスルールを含むなど、中心となる関心ごとを処理しているコンポーネント)はその下位レベルのコンポーネントの変更に依存するべきでない
- 最上位レベルのコンポーネントはオープン・クローズドの原則を最も満たしている状態になる
- クリーンアーキテクチャでよく出てくる同心円の図で表現されている依存関係のルール
LSP:リスコフの置換原則
- インターフェイスを駆使して、リスコフの置換原則を満たすアーキテクチャを設計する
ISP:インターフェイス分離の法則
- 必要としない機能を抱えたものに依存することは、アーキテクチャレベルにおいても有害である
DIP:依存関係逆転の原則
- 上位(抽象)モジュールは、下位(具象)モジュールに依存してはならない
- 処理の流れ上、上位モジュールが下位モジュールに依存してしまうとき、インターフェイスを導入することにより、依存関係を逆転することができる
ソースコードレベルのみならず、アーキテクチャにおいても、SOLID 原則は大切
4.コンポーネントの凝集性
コンポーネントの原則
SOLID 原則がレンガを組み合わせて壁や部屋を作る方法を伝える原則だとするならば、コンポーネントの原則は部屋を組み合わせて建物を作る方法を伝える原則である。
再利用・リリース等価の原則(REP)
再利用の単位とリリースの単位は等価になる。
REP はコンポーネントの再利用を円滑に行うための考え方を示す
- コンポーネントにはリリース番号が付与されている必要がある
- リリース時に変更内容が適切に通知・文書化されている必要がある
- コンポーネントを構成するクラスやモジュールは凝集性が高く、一貫したテーマや目的を共有している必要がある
- コンポーネントを構成する要素は、まとめてリリースされる必要がある
閉鎖性共通の原則(CCP)
同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること。変更の理由やタイミングが異なるクラスは、別のコンポーネントに分けること。
- SOLID 原則の(SRP)単一責任の原則をコンポーネント向けに言い換えたもの
- 変更の種類が似ているクラスをひとつのコンポーネントにまとめることで、変更箇所を最小限のコンポーネントに絞れるという点で、オープン・クローズドの原則(OCP)とも関連している(完全に変更に対して閉じているわけではないが)
全再利用の原則(CRP)
コンポーネントのユーザーに対して、実際には使わないものへの依存を強要してはいけない。
- SOLID 原則でいうインターフェイス分離の原則(ISP)
- どちらも「不要なものには依存しない」ということを伝えている
コンポーネント凝集性のテンション図
- これらの原則には相反するところがある
- REP と CCP はコンポーネントを大きくする方向に働くが、CRP は小さくする方向に働くため
- 3つの原則のバランスを取るのが大事
- バランスの良し悪しは、その時の開発チームのフェーズによって変わってくる
- 開発初期段階は開発のしやすさを重視したいため、右寄りになるが、後段階では再利用性を重視して、左よりになるなど(開発時の利便性と再利用性のトレードオフを考慮する)
辺にある記述は、反対側の頂点にある原則を無視したときにかかるコストを表している
5.コンポーネントの結合
コンポーネントを結合する際に考慮するべき3原則をまとめる
非循環依存関係の原則(ADP)
コンポーネントの依存グラフに循環依存があってはいけない。
- 依存関係逆転の原則を適用するなどして解消する
安定依存の原則(SDP)
安定度の高い方向に依存すること。
- 安定度の高いとは、変更しづらいことを指す
- 変動を想定したコンポーネント(安定度の低いコンポーネント)は、変更しづらいコンポーネント(安定度の高いコンポーネント)から依存されると変更が難しくなってしまうので良くない
安定度・抽象度等価の原則(SAP)
コンポーネントの抽象度は、その安定度と同程度でなければならない
- 安定度の高いコンポーネントは抽象度も高くあるべきで、安定度の高さが拡張の妨げになってはいけない
- 一方で安定度が低いことによってその内部の具体的なコードが変更しやすくなるため、安定度の低いコンポーネントはより具体的であるべき
6.アーキテクチャ
- 優れたアーキテクトは、方針と詳細を慎重に区別して、決して依存することがないように、両者を切り離す
- 優れたアーキテクトは、詳細の決定をできるだけ延期・留保できるように、方針をデザインする
7.ビジネスルール
エンティティ
- 最重要ビジネスルール、ビジネスデータを含んだオブジェクト
ユースケース
- アプリケーション固有のビジネスルールを記述する
(ユーザーから提供された入力、ユーザーに戻す出力、出力を生成する処理ステップなどを規定する) - ユーザーとエンティティのインタラクションを支配する
ビジネスルールはシステムのなかで、最も独立していて、最も再利用可能なコードでなければいけない
8.クリーンアーキテクチャ
世の中にはさまざまなシステムアーキテクチャがあるが、細部に多少の違いはあれど、いずれも「関心事の分離」という同じ目的を持っている。
ソフトウェアをレイヤーに分割することで、この分離を実現している。
そして、これらのアーキテクチャは、以下の特性を持つシステムを生み出す
- フレームワーク非依存
- テスト可能
- UI 非依存
- データベース非依存
- 外部エージェント非依存
これらを単一の事項可能なアイデアに統合したもの ↓(よく見るやつ)
ソースコードの依存性は、内側(上位レベルの方針)だけに向かっていなければいけない。
ソフトウェアをレイヤーに分割して、依存性のルールを守る。
依存関係逆転の原則を使って、制御の流れがどのような方向であっても、依存性のルールに違反しないようにする。
9.レイヤーと境界
- 単純なシステムであれば、UI・ビジネスルール・データベースという 3 つのコンポーネントで構築できるが、ほとんどのシステムはこれよりも多くなる。
- アーキテクチャの境界はあらゆるところに存在するため、アーキテクトはアーキテクチャの境界がどこにあるのか、完全に実装する必要があるのか、部分的に実装すべきなのか、無視したほうがいいのかを判断する必要がある。
テスト境界
- テストもアーキテクチャの一部である
- テストは、非常に詳細で具体できであるため、アーキテクチャの円のもっとも外側にあると考えることができる
- システムの一部として設計されていないテストは、脆弱で保守が難しくなる傾向がある
- テスト API などを用いて、アプリケーションの構造をテストから隠すことで、プロダクションコードの変更が多数のテストを破壊する事態を防ぐことができる
10.まとめ・感想
- SOLID 原則の話にしろコンポーネントの話にしろ、良いアーキテクチャを実現するための考え方は根本的に共通してたり、似ている部分が多いと思った
- ユースケースの実装方針を言語化できてなかったところが解消された
(「ユースケースはエンティティのダンスを制御している」← この表現好き) - クリーンアーキテクチャの同心円図の背景にある考えを学ぶことができた
Discussion