FreeStyleWiki

CleanArchitecture

[ソフトウェア開発,設計]

Clean Architecture

このページでは、ロバート・C・マーティンの著書、「Clean Architecture」の抜粋・このWikiの筆者の独断による要約を載せる。

  Clean ArchitectureのAmazonリンク

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

  • 所感
    • 構成と実務で読み取るべき部分について
      • ネットでよく見るクリーンアーキテクチャについての説明は第5章から
      • それまでのプログラミングパラダイムの部分は、テーマが大きいわりに語り切れているとはあまり思えない
      • 設計の原則は速攻で実用できるものではないので難しい(※ことわざが現実適用しにくいのと同じで、得た知識を経験で磨かないと使えない)
  • 用語の定義
    • コンポーネント:デプロイ単位(war, jarなど)
    • モジュール:クラスやソースコード

  第1部 イントロダクション

設計とアーキテクチャ

  • きれいな設計とコードを書かないと、いつしか「利益<改修コスト」になり事業が崩壊する
  • 設計とコードをきれいにする機会は後に訪れることはない、走りながらやっていくことが必要

2つの価値のお話

  第2部 構成要素から始めよ:プログラミングパラダイム

  • ここちょっと翻訳が悪いかも「直接的な制御の移行に規律を課す」より受け身で書いた方がよくて「(パラダイムによりプログラマーは)直接的にソフトウェアの操作に規律を課される」ぐらいがいいと思う

パラダイムの概要

構造化プログラミング

  • 構造化プログラミングは直接的な制御の移行に規律を課すものである
    • ここはすんなり受け入れられる。構造化することでプログラマーはソフトウェアの操作を直接制限される

オブジェクト指向プログラミング

  • オブジェクト指向プログラミングは間接的な制御の移行に規律を課すものである
    • 筆者の3段論法「OOPは多態性が本質→多態性はコンポーネントの独立性を可能にする→コンポーネントの独立性はシステムの作りを間接的に制御できる」
      • しからば、「OOPはシステムの作りを間接的に制御できる」
      • 先生ちょっとわかりにくくないですかね、まとめのフォーマットにこだわりすぎましたね
      • デザインパターンなどの設計は間接的にプログラマーに制限を加える、そしてデザインパターンはOOPのコンポーネントの独立性に支えられている

関数型プログラミング

  • 関数型プログラミングは代入に規律を課すものである
    • 変数が状態を持たないことで並行処理などに有効
    • 状態をもたないモジュールの概念は設計にも応用できる(→まさにドメイン駆動設計のValueObjectやドメインオブジェクトの考え)
    • 関数型プログラミングという概念はほかにもいろいろあるのではという感じはするが、システム設計者にとっては「パラダイム<ビジネスロジック」だしこれぐらいで終わらせていいのかもしれない(皮肉ではなく、パラダイムの研究で飯は食えない)

  第3部:設計の原則

  • SOLID原則:ロバート・C・マーティンが開発者コミュニティでまとめた開発の原則

SRP: 単一責任の原則

  • モジュールは単一のアクターに対して責務を負うべきである
    • ここでのアクターは、システムの外部に存在してシステムと相互作用する、特定の目的と役割を持つ存在で、人や組織、外部システムのことでいいと思う
    • 具体例:外部システムのAPIを2つまとめて扱うRepositoryを設計したり、異なるアクターが扱うようなテーブルを設計してはだめ

OCP: オープン・クローズドの原則

  • モジュールは拡張に対しては開いていて、修正に対しては閉じていなければならない
    • システムのうち変わりやすい部分を下位コンポーネント、変わりにくいものを上位コンポーネントと位置付ける。下位コンポーネントは上位に依存するように作る。
    • 具体例
      • これは書籍の例だと普通のMVCなWEBフレームワークだと何もしないでもそうなるので、例外が考えにくい
      • オープン・クローズドの原則の重要性について: いい事例が見つかった。例えば外部サービスを呼び出すときのクラスをインターフェースを使わず具象クラスだけで構築した場合、サービスクラスからAPIの実装クラスに依存が生じてしまう。これではAPIの実装に変更があるたびにサービスクラスを修正しなくてはならない。ここで、「サービスクラスとAPIの実装クラスでメソッドシグネチャを合わせとけばいいじゃん」という反論があるかもしれないが、メソッドを合わせるならばそれこそinterfaceを使えばいいのである。interfaceを作ることで依存がinterfaceに集まることになり、APIの実装クラスが修正されてもサービスクラスに影響がなくなる。実務的な感覚と、クラス図の抽象的な意味、そしてオープン・クローズドの原則の正しさがわかる。

LSP: リスコフの置換原則

  • これは継承の話に限定できそうだ
    • ある抽象クラスからサブクラスを作って継承する場合、サブクラスは、要求事項を(基底クラスよりも)増やすべきではなく、できることを(基底クラスよりも)減らすべきではない。
    • Railsで学ぶSOLID(3)リスコフの置換原則(翻訳)
      • 書籍にはREST APIでリスコフの置換原則に違反した例を書いていたが、REST APIのエンドポイントが異なっていればそもそもAPIを呼び出せないので、いい例ではないなと思った。Rubyのように派生クラスで基底クラスの挙動を変えられるような場合が問題だと思う。
  • ちょっと考えたが、これは継承をやすやすと使うのは難しいという話に落ち着く気がした
    • 継承が継承として使えるかどうか判断する材料としてWikipedia - is-aがある

ISP: インターフェース分離の原則

  • 必要としないコンポーネントやモジュール、フレームワーク、ライブラリへの依存は避けましょうという話

DIP: 依存関係逆転の原則

  • 具象クラス名を指定して呼び出すのはやめましょう、というのは具象クラスは変わりやすいものだから
  • 具象クラスよりinterfaceに依存させろというのはオープン・クローズドの原則でやった
  • それでも具体的なクラス名を指定しての呼び出しは避けられない→デザインパターンのファクトリ系の方法で名指しは防げる

  第4部:コンポーネントの原則

コンポーネント

  • ボブおじさんの昔話、コンポーネントがプラグイン的にロードできるようになるまで
  • 今はjarファイルやdll、.soファイルなど動的なライブラリの仕組みが進化している

コンポーネントの凝集性

  • REP:再利用・リリース等価の原則
    • コンポーネントに含まれるモジュールやクラスはテーマや目的が同じものでないといけない、そしてリリースはまとめて行えなくてはいけない
  • CCP:閉鎖性共通の原則
    • 一緒に変更される可能性のあるモジュールはまとめておけ、そうすればリリースの手間が減る
  • CRP:全再利用の原則
    • 密結合していないクラスを同じコンポーネントにまとめるべきではない

コンポーネントの結合

  • SDP:安定依存の原則
    • 不変性をもつモジュールに依存させるようにつくるべし、依存先が多いモジュールは不安定だと言える
  • SAP:安定度・抽象度等価の原則
    • 安定度の高いコンポーネントは抽象度も高くあるべき

  第5部:アーキテクチャ

アーキテクチャとは?

ソフトウェアアーキテクトとは何か?
それはプログラマである、プログラミングを続けて課題を経験していないと他のプログラマのために適切な仕事をすることはできない
ソフトウェアアーキテクチャの目的とは何か?
(1) 優れたアーキテクチャはシステムの理解・開発・保守・デプロイを容易にする (2) システムの更改のコストを最小限に抑え、プログラマの生産性を最大化する
ソフトウェアをソフトに保つための方法
できるだけ長く、多くの選択肢を残すべし(選択肢とは「詳細」のこと)
方針と詳細
「方針」とはビジネスのルールや手順、これの解決のために「詳細」を使う。詳細というのはDBやアプリケーションの構造、通信方法などを指す

独立性

  • アーキテクトが独立性を保って担保しなければいけない4つの事柄について解説されている
    • システムのユースケース
      • レイヤーとユースケース自体の違いでユースケースを分割していく
    • システムの運用
      • 負荷が高いモジュールであればサーバ自体を分割することが可能だろう、そういう意味ではマイクロサービスアーキテクチャは詳細の決定を後回しにできる
    • システムの開発
      • レイヤーとユースケースが分割されていれば開発はそれぞれ独自に行える
      • コード重複を恐れすぎるのはよくない、当初は重複的にみえることがあっても、後々異なるようにコードベースが進化するかもしれない
    • システムのデプロイ
      • レイヤーとユースケースが分割されていればデプロイ単位も分割されるので、開発がはかどるだろう

バウンダリー:境界線を引く

ソフトウェア要素を使用することに対する早すぎる決定は不幸を生む

  • 早すぎる決定による失敗例
    • P社はアプリケーションのサーバを3層構造で構築することを早めに決定したが、実はサーバを3つも使う必要性はなかった
    • W社はドメインオブジェクトを最初からすべて設計したが、実はドメインオブジェクトはそんなに必要なかった
  • 決定の遅延による成功例
    • 筆者はWikiエンジンの読み書き部分を最初から永続化させなかった
    • 永続化部分をファイルシステムへの書き込みで実装しインターフェース化しておくことで、のちにMySQLでの永続化にも対応できたし無駄な開発リソースを使わなかった
    • なんかFreeStyleWikiみたいな仕組みですね…

早すぎる決定を防ぐバウンダリーのやりかた

  • ビジネスルールとそうでないものの間に境界線を引く
    • データベースでさえ、それが本当にビジネスのルールなのか(ドメイン駆動的に言えばドメインなのか)見極めが必要だ
    • GUIは当然ビジネスルールと異なるものなので境界線を引く必要がある

やっぱボブ先生はビジネスロジック至上主義者やな

境界の解剖学

  • システムで境界線を引くときに取りうる方法について列挙
    • モノリス:システムがモノリシックでも内部のモジュールをちゃんと設計しておけば境界線を引くことはできる
    • デプロイコンポーネント:デプロイ単位を変えられること以外はモノリスと同じだ
    • スレッド:上記2つで使用可能
    • プロセス:双方のやり取りにはプロセス間通信が必要だろう
    • サービス:最も強い境界線であるが、やり取りにはネットワークが使われるためレイテンシーに気を付けなければいけない

方針とレベル

  • レベルとは入出力からの距離であると定義したとき、入出力から最も遠いところがレベルが高い
  • レベルが高いところに方針(ビジネスロジック)を書くべきである

ビジネスルール

この辺はドメイン駆動設計の話とかなりかぶってくる。

  • エンティティについてWiki主の補足、Difference between Entity and DTOの再掲
    • Entityという用語にもっとも関連のある解釈は、以下の3つになる:
      • エンタープライズJavaとJPAの文脈において:「データベースの中で保守される永続化データを表すオブジェクト」
      • ドメイン駆動開発の文脈において(Eric Evansの発言):「属性よりもその独自性によって主要に定義されるオブジェクト」
      • クリーンアーキテクチャの文脈において(Robert C. Martinの発言):「エンタープライズ横断的に重要なビジネスルールをカプセル化したオブジェクト」
  • 実用的には?
    • だから一般的にはDBのテーブルはエンティティでいいじゃんと思う。ただしそれがちゃんとドメインになっていること、単一責任の原則に従っていることを確認したほうがいい。
    • そしてボブ先生はエンティティに振る舞いを実装しているけど、これはリポジトリパターンでエンティティを操作している状況に近い(実装的にはエンティティ自体に振る舞いを付け加えるのはやめたほうがいいんじゃないかな?エンティティが振る舞いをもつと、CRUDのためのモジュールになってしまいドメインモデル貧血症を起こしそうだしバグりそう)

叫ぶアーキテクチャ

  • 設計されたアーキテクチャはユースケース自体の解決を叫んでなければならない
    • システムを見て「CMS」「動画販売」「マスタ管理」「補助金申請システム」とわからなければ…
    • 「Rails」「Struts2+Hibernate」「Node.js」とかに見えるとすれば失敗

クリーンアーキテクチャ

いよいよ話題の4重の円で示されるアーキテクチャの図について、リンク先がその元ネタです。

アーキテクチャの構成(構成のレベル順に)、カッコ内はwiki主の注釈

  • エンティティ
  • ユースケース(サービス)
  • コントローラ、プレゼンター、ゲートウェイ(リポジトリ)
  • デバイス、ウェブ、UI、外部インターフェース、DB
  • はしがき:クリーンアーキテクチャの依存ルール(Dependency Rule)を考える
    • Dependency Ruleはソースコードの依存関係が円の内側にしか向かわないことをルールとする
    • AがBに依存している、はUML的に「A→B」で表せる(コード的に言えばAがBを呼び出す、AはBを使うとも)。
    • 円を外側から見ていくとそれが依存関係になるはず
      • 「UI→プレゼンター→ユースケース→エンティティ」(完璧だ!)
      • 「DB→ゲートウェイ→ユースケース→エンティティ」(ゲートウェイがユースケースを使うパターンはあまりない気がするが、関係はあってる)
  • DB→ゲートウェイ→(ユースケース)→エンティティの関係をコードレベルに落とす
    • 以下のようなインターフェースをおけば、依存関係は図の通りになる UserGateway.java
      • ゲートウェイ→エンティティを表すコード片
package cleancoderscom.gateways;

import cleancoderscom.entities.User;

public interface UserGateway {
  User save(User user);

  User findUserByName(String username);
}
      • DB→ゲートウェイを表すコード片
package cleancoderscom.gateways;

import cleancoderscom.entities.User;

public class UserGatewayImpl implements UserGateway {
  User findUserByName(String username) {
    xxx.mapper.findXX(); // DB固有のコードがここに来る(※まあO/RマッパーがDBの差異も吸収しているのですが)
  }
}

プレゼンターとHumble Object

  • Humble Object: テストしにくいものを2つに分けて、テストしやすくする
  • ViewをViewModelとPresenterに分けて、Presenterに処理をお願いする
    • Presenter側でデータの成型を行う、ViewModelは出すだけ
  • UsecaseはHumbleではないのでDatabaseGateway(Repository)をHumbleにすることでテスト可能にする

部分的な境界

  • 境界線を引く方法
    • コンポーネントを別にする(jarやwarを別にする)
    • Strategyパターンを使って分離
    • Facadeパターンを使って分離

レイヤーと境界

  • ある程度予測を立ててアーキテクチャに境界線を引く練習

メインコンポーネント

  • SpringのWebConfigのようなすべての汚れ仕事をまかなうコンポーネントがあれば環境別にアプリケーションが立ち上げやすい

サービス:あらゆる存在

  • マイクロサービスアーキテクチャを構築して、デプロイ単位が分離されても必要な機能が分離できるわけではないこともある
  • 横断的関心事に対応するためにはサービス内部のコンポーネントをよく設計する必要がある

テスト境界

  • テストはアーキテクチャの円のもっとも外側にあり、最も詳細である、変わりやすいものだと覚悟する
  • テストをさほど変えずに、プロダクションをリファクタリングさせていくにはテスト用のAPIを準備することが必要

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

  • 割愛

  第6部:詳細

データベースは詳細

  • データを表形式で管理すること自体は、ビジネスロジックを解決するという目的からすれば些細なことである
  • SQLとか関係データベースの仕組みを目的としてはいけない

ウェブは詳細

  • GUIが目的でないことは明白だ

フレームワークは詳細

  • フレームワークだって目的ではないが、作るシステムがそれと結合してしまうことから取り外しが難しい
  • できるだけコアにフレームワークのコードを入れない、時間稼ぎをすることが必要

事例:動画販売サイト

  • 実際のWEBサイト構築例

書き残したこと

  • パッケージの分割方法いろいろ

アーキテクチャ考古学

  • 昔話、読み物