ゆるっと備忘録

見たものを忘れないように記録する不定期ブログ

クリーンアーキテクチャ本を読んだ

背景

Robert C. Martin 著作の「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んだ

ものすごく良い本だったなぁと思うと同時に、少しでも記憶に定着させるためにアウトプットしようと思って記事を書く

要するにこういうこと言いたいんだろうなという自分の解釈を書くだけなので、厳密に違ったとしても容認していただきたいです
読んだことがない方はこの機会にぜひ!

ここで記述する引用は全て「Clean Architecture 達人に学ぶソフトウェアの構造と設計 著 Robert C. Martin, 訳 角 征典 訳, 高木 正弘」からのものとします

本の結論

これから本に書いてあったことを簡単にまとめながら記すが、全体的に何が言いたかったねんというのをここでまとめておく

  • ビジネスロジックが上位、DBやUIが下位で、コードは常に上位へ依存する
  • 境界を作ることでコードの保守性が上がる

アーキテクチャとは

労力を少なくシステム開発できるようにすることを目指す、ソフトウェアの設計。以下、引用

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


アーキテクチャを常に意識してコードを書く方が良い。理由は下記の2つ

  • 短期的に早く実装するよりも、綺麗なコードを書いていく方が長期的には生産性が高い
  • あとでキレイにするという考えは、結局やらない(常に新しい開発が求められるから)


良いアーキテクチャにすることでソフトウェアの価値が上がる
ソフトウェアの価値は振る舞いと構造の2つ

  • 振る舞い: システムが要件に沿った動きになっているかが価値につながる
  • 構造: 柔軟にシステムを変更できるかが価値につながる

要件通りの動きをしても変更できなければ意味がないので、構造も重要だと言える

設計の原則

SOLID原則というものがある。
これは変更に強く、理解しやすく、多くのソフトウェアで適用できる設計原則

モジュールレベルの開発が対象となる
モジュール・コンポーネント・サービスといった単位が出てくるが、それぞれがどれくらいの範囲なのかはいまいちわからず...

S: 単一責任の原則(Single Responsibility)

モジュールの責任は1つにするべきという原則(1つの変更の影響範囲が広がらないように)

言い換えると、モジュールはたった一つのアクターに対して責務を負うべきという原則

  • モジュール: いくつかの関数やデータをまとめた凝集性のあるもの
  • アクター(ユーザーやステークホルダー

複数のアクターへ影響が出るようになっていると、コードを修正するときに影響範囲が広がってしまう
データを関数から切り離したり、Facadeパターンを利用したりして、コード変更時に複数のアクターへの影響を避ける

O: オープン・クローズドの原則(Open-Closed)

既存コードの変更よりも新しいコードの追加で振る舞いを変更できるようにするべきという原則(それによりバグの発生を減らす)

言い換えると、ソフトウェアは既存コードを変更せずに拡張できるようにするべきという原則
拡張のたびに既存コードを変更する必要があると、拡張のコストが大きくなってしまう
この原則を守るために、システムをコンポーネントに分割して依存関係を階層構造にし、インターフェースを利用することで上位レベルは下位レベルの変更を受けないようにする

L: リスコフの置換原則(Liskov Substitution)

交換可能なパーツを使った場合、交換可能となるように従うこと(クラスの継承で、小クラスは親クラスの振る舞いをできるようにしなければならないというイメージ)

親クラスから派生されたものは、親・兄弟ともに置換できるようにするべきである
置換可能になっていないとそれぞれの型に依存することになり、処理が複雑化してしまう

I: インターフェース分離の原則(Interface Segregation)

使っていないものへの依存は回避すべきという原則

クラスを利用する時はインターフェースを通じて、必要な動作のみを利用するみたいなイメージ
そうすることによって利用していない部分の変更の影響を受けず、その部分を変更したことによる再デプロイも避けることができる

D: 依存性逆転の原則(Dependency Inversion)

上位レベルは下位レベルの詳細の実装コードに依存するべきではないという原則
また、詳細は方針に依存すべき(Controllerがinteractorに依存するのではなく、interactorがinputportに依存するみたいなイメージ)

依存性逆転については本の中で頻出するので、最も重要な原則な気がした
参照先は抽象クラスやインターフェースに限定するべきで、具象なものには参照せず、継承も行うべきでない

コンポーネントの原則

コンポーネントとは、システムの一部としてデプロイできる最小の単位のこと

コンポーネントの凝集性に関する原則

どのクラスをどのコンポーネントに含めるべきかという凝集性に関する原則は3つある

  • 再利用・リリース等価の原則(REP)
    再利用の単位とリリースの単位は等価であるべき。コンポーネントを形成するクラスやモジュールは凝集性あるものであるべきで、まとめてリリース可能であるべき
  • 閉鎖性共通の原則(CCP)
    同じ理由・同じタイミングで変更されるクラスはコンポーネントにまとめる。逆の場合は別にする。単一責任の原則のコンポーネント
  • 全再利用の原則(CRP
    利用しないクラスを持つコンポーネントに依存するべきではない。コンポーネントにあるクラスは密結合であるべき。インターフェース分離の原則のコンポーネント

コンポーネントの結合に関する原則

  • 非循環依存関係の原則(ADP)
    循環依存があるとビルドやテストの対象コンポーネントが広くなってしまうため、省く必要がある
    インターフェースを作成するか、依存される箇所を別のコンポーネントとして作成することで循環依存は解消できる コンポーネントトップダウンから設計できればいいが、初めからそれを行うのは不可能なため、柔軟に対応していく方がいい
  • 安定依存の原則(ADP)
    安定とは依存するより依存されることの方が多い状態のこと。つまり変更がしづらい状態のこと
    変動を想定したコンポーネントは、安定したコンポーネントから依存されてはいけない(変動するたびに安定したコンポーネントに影響があるから)
    コンポーネントに安定を求める必要はなく、安定していないものから安定しているものへと依存している状態であれば良い
  • 安定度・抽象度等価の原則(SAP)
    抽象度とは、全てのクラスの中で抽象クラスとインターフェースはどれくらいあるかを表す度合い
    抽象度が高く安定性もあるもの、または抽象度が低く安定性がないものは望ましい。安定度だけが高いと拡張が難しく、抽象度だけが高いと無駄な抽象クラスが存在する可能性がある。安定度と抽象度のバランスを考えることが重要

アーキテクチャ

ここからが本題
アーキテクチャによってシステムのライフサイクルをサポートする
優れたアーキテクチャであれば、システムを容易に理解・開発・保守・デプロイできる

システムを開発するにあたって、方針と詳細を切り離された方がいい
例えば、印刷をしたいときにどのプリンタを利用するかという詳細を決めるべきではない。特定のプリンタに依存すると変更が効かなくなる
初めに方針だけを先に考えて、詳細はあとで決めることで、情報が揃った状態でどのようなサーバーやDBを使うかなどを決めることができる

アーキテクチャが懸念すべきこと

  • ユースケース: システムの意図がわかるように振る舞いを明確にするのが重要
  • 運用: 特定の技術に依存せず、変化に強い状態が好ましい。コンポーネントの適切な分割が大事
  • 開発: 各チームがお互いに干渉しないように開発できる状態が良い(コンウェイの法則)
  • デプロイ: 構築後即時デプロイできる状態が好ましい。コンポーネントの適切な分割が大事

まとめると、コンポーネントを適切に分割し、様々な選択肢を残す状態にしておくのが良い

様々な選択肢を残すためには切り離しを行う必要がある

  • レイヤーの切り離し: UI・アプリケーション特有のビジネスルール・アプリケーションに依存しないビジネスルール・データベースなど
  • ユースケースの切り離し: 注文の追加・注文の削除など

切り離しはソースコードレベル・デプロイ(バイナリ)レベル・サービス(実行単位)レベルの3つのレベルがあり、柔軟にそれぞれのレベルに変えられるようにしていくと良い

境界線を引く

マンパワーを奪うのは早すぎる決定との結合である
早すぎる決定とは、ビジネス要件が定まらないうちにDBやウェブサーバーを決めること

ビジネス要件と技術に境界線を引くことでこの決定を延期できる
どの技術を使うかを後で決めて、特定の技術に対する考え事を減らすこともできる

境界線は重要なものと重要ではないものに引く。例えばGUIはビジネスルールとって重要ではない。DBはGUIにとって重要ではないなど
ビジネスルールはデータベースと境界線を引き、データを取得・保存できるインターフェースがあるということだけ知っとけばいい(こうすることでさまざまなDBを利用できる)
また、IOはシステムとは無関係なので、GUIからシステムを構築するのは悪手だとも言える

境界線は変更の軸があるところに引く。つまり変更の理由や頻度が異なるところに引くと良い

境界にはレベルがあり、モノリス内のソースレベル、デプロイレベル、サービスレベルの3つがある サービスレベルの場合は、サービス間で通信が行われるためレイテンシーを気にする必要がある

方針とレベル

下位レベルから上位レベルへの依存となる状態が好ましい
入力・出力から離れているものほどレベルが高いと言える
入力・出力はインターフェースを使って、上位レベルから切り離すべき

ビジネスルール

ビジネスルールとは、コンピュータで実装されていなくてもビジネスマネーを生み出したり節約できたりするもの(自動化しなくてもいいものとも言える)
例えば銀行がローンに利子をつけるときは、コンピュータでなくてもそろばんでもいい
これを最重要ビジネスルールと呼び、そのために必要なデータ(金利・支払いスケジュールなど)は最重要データと呼ぶ
ビジネスルールはシステムの中で最も独立しているべきである

エンティティ
システム内部にあるオブジェクトで、最重要ビジネスデータを操作する最重要ビジネスルールをいくつか含んだもの ビジネスにとって不可欠な概念をまとめて、システムとは独立させる。ビジネスそのものという感じ

ユースケース
アプリケーション固有のビジネスルール。自動化した部分とも言えそう
新規ローン作成を例にした場合、連絡先情報がシステムへと入力 -> 与信スコアが生成 -> 与信スコアが基準値を超えない限り支払い見積もりの画面に進まないという流れががユースケース
データどのように入出力されるかは無関係
ユースケースよりエンティティの方が上位になる

叫んでいるアーキテクチャは良い状態

アーキテクチャによってそのシステムが何を表しているのかわかると良い
そのために、ユースケースを中心とするようにするべき。どの技術を使うとかは詳細なのでユースケースとは切り離されるべき

建物の設計を例に出すと、設計図だけで図書館の設計とわかるようになっていると良い状態
ソフトウェアも同じだと言える

クリーンアーキテクチャ

この章は最も重要だと思う
翻訳もネットにあるので載せておく

クリーンアーキテクチャ(The Clean Architecture翻訳) | blog.tai2.net

さまざまなアーキテクチャがあるが、それらはどれも関心ごとの分離をして、レイヤー分割をしている

様々な良いとされるアーキテクチャには下記の特徴がある

  • フレームワーク非依存: ライブラリやフレームワークに依存せず、それらをツールとして利用
  • テスト可能: DBやUIといったものがなくてもビジネスルールをテストできる
  • UI非依存: UIはシステムの他の部分を変更することなく簡単に変更できる
  • データベース非依存: DBにビジネスルールは依存していないので簡単にDBを変更できる
  • 外部エージェント非依存: ビジネスルールは外界のインターフェースを知らない

依存性のルール エンティティ <- ユースケース <- インターフェイスアダプター <- フレームワークとドライバー
という順番でエンティティに向かって依存の方向が向く
内側は外側に変更があったとしても何も影響がない

また、プレゼンターの仕事はアウトプット用のデータをView用の形式に変換することである

プレゼンターとHumble Object

Humble Object パターンとはテストをしやすい部分としにくい部分を分離するデザインパターン
これを利用することでテスト容易性が向上する

例えば、GUIはテストしづらいが、GUIに渡すデータをフォーマットするPresenterはテストしやすい

Humble Object パターンを利用することは、アーキテクチャの境界の特定と保護を行うのに役立つ

部分的な境界

アーキテクチャに完全な境界を作るのは難しい。特にシステムが初期の場合
対策として、部分的な境界を作って対処する(YAGNIに反することにはなる)

方法

レイヤーと境界

UI・ビジネスルール・データベースの3つで境界を引ければ簡単だが、実際はそんな簡単なものではない
UIでもGUIやコンソールがあるし、英語や日本語表示もある

部分的に境界を実装するべきか無視するか推測する必要があるし、1回限りの決定ではなく常に見張って、後で境界を実装する必要もある

メインコンポーネント

Main コンポーネントは他のコンポーネントを作成・調整・監督するコンポーネント
最下位レベルに位置し、初期状態や構成の設定・外部リソースの収集をして、上位レベルに制御を渡す
クリーンアーキテクチャの最も外側に位置する

サービス:あらゆる存在

サービスとはアーキテクチャではない
振る舞いを分離しただけのもの
プロセスやプラットフォームを超えた関数呼び出しにすぎない

サービスはデータやリソースを共有しているし、大規模なシステム開発に利用されている
しかし、データ・リソースの共有や、大規模なシステム開発ができるということに関して特別メリットがあるわけではない


サービスを作ることがアーキテクチャになるのではない

例えば、タクシー配車アプリで、急に子猫配送機能をつけるとなった場合、全ての動作に影響を与える横断的変更が起こる
それを阻止するためにも、サービスは内部コンポーネントアーキテクチャを設計し、依存性のルールに沿うことで派生クラスを生み出しやすいようにする

テスト境界

テストはアーキテクチャに関与している
ソフトウェア設計の第一のルールは変化しやすいものに依存しない。なのでテストもGUIのような変化しやすいものに依存しないべきである

テストはテスト対象コードに依存し、テスト自体が非常に詳細で具体的なのでアーキテクチャの最も外側になる
テストがコードに構造的結合がされていると、コードを変更するたびにテストも変更しなければいけないので良くない

クリーン組み込みアーキテクチャ

ファームウェア(ハードウェアとやりとりするコード)とソフトウェアは別である

ファームウェアはハードウェアに依存しするもの
しかし、ハードウェアの進化は速いため、ソフトウェアのファームウェアに対する依存箇所が多いとシステム全体がファームウェアになる
ファームウェアではなくソフトウェアの部分を多くする必要がある

ファームウェアに限らず、コードはシステムの動作・高速化をさせるだけでなく、理解しやすい形にするべき

組み込み開発は一般的ではないUIや制限されたIO・メモリなど特別なことが多いが、開発自体は特別ではない
ハードウェア・ファームウェア・ソフトウェアの境界を定めて開発するといい
ソフトウェアとファームウェアの境界にはハードウェア抽象化レイヤー(HAL)を設置し、ソフトウェアがハードウェアの詳細を知らなくても動くようにする

マイクロコントローラーを使用するときは、低レベル機能プロセッサ抽象化レイヤー(PAL)でフームウェアから分離する
OSが存在する場合もあるが、その場合はソフトウェアをOS抽象化レイヤー(OSAL)でOSから分離する
レイヤー分けすることによって、対象のハードウェアやOS等がなくてもテストを行えるようになる

詳細

データベースは詳細

どこからどのような形式でデータを取得するかを上位は知る必要はない
データモデルはアーキテクチャ的に重要だが、データを移動させる技術は重要ではない

Webは詳細

WebはGUIであり、IOデバイスの一種と考えられる
Webに対しても適切に抽象化を行い、ユースケースから分離させると良い

フレームワークは詳細

フレームワークと結合すると、仕様変更の影響を受けたり、変更したい時に邪魔になったりする
フレームワークを使う場合はコンポーネントを別にして、プラグインとして利用するようにする

書き残したこと

コードを分割する方法はいくつかある

個人用メモ

MVPとMVCの違い

  • MVP: presenterはviewに値を渡すだけでなくメソッドも使う。viewもpresenterを利用
  • MVC: controllerはviewから呼ばれて値を返すだけ

ビジネスロジックとは

プレゼンテーション層・ビジネスロジック層・データアクセス層にアプリケーションを分けた場合、プレゼンテーションでもアプリケーションでもないもの

データアクセスとは、その名の通りファイルやDBにアクセスするもの
プレゼンテーションとは、 プログラムとユーザーとのやり取りを担当するもの。CLIGUIなどUI・APIごとに処理が変わる

「コンピュータとじゃんけんをして、その結果をどこかに保存する」というアプリケーションがある場合、じゃんけんの勝敗判定と一連の流れがビジネスロジック
参考: https://qiita.com/os1ma/items/25725edfe3c2af93d735