Open37

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

mao12312mao12312

第1章 設計とアーキテクチャ

設計とアーキテクチャの違いは

設計とアーキテクチャという似た用語があり、アーキテクチャの方が上位概念として捉えられがちだが、本書では 特に違いはない と紹介している。
どちらも下位レベルの詳細と上位の構造のは全体設計の一部として重要なものである。

アーキテクチャ・設計の目的は?

本書ではアーキテクチャ・設計の目的は下記のように記されている。

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

実際の企業で起こった崩壊

ケースとして設計がうまくいっていない開発・リリースを例として設計の大切さを記している。
リリースバーションが1→8になるにあたって、コードの増加は漸近線に近づいているがコストは40倍になっている。
これは機能の開発ではなく、既存の不具合の対応をおこなっているからである。

原因

原因はコードをクリーンにすることを後回しにしてしまったからである。
後回しにしたところで、コードをクリーンにする時間はなく、機能を市場に出すことが優先され、負債が溜まっていく。
この質の低いコードが出来上がってしまう背景は、 「質を犠牲にスピードを得る」 という考えによるところが大きいと考える。

解決方法

では、どのようにすれば生産性を落とさずスケールさせることができるのか?
本書では下記のように述べている。

速く進む唯一の方法は、うまく進むことである。
つまりコードの品質に目を向け、後回しにせず実装を進めることである。

今度は「スピードを犠牲に質を得る」話のように思えるが実際は違う。
本書ではテスト駆動開発(TDD)を使用した開発とTDDを使わない開発で作業完了時間の実験をしたところ、TDDを使用した方が早く完成した。

短期的にも長期的にも、 崩壊したコードを書くほうがクリーンなコードを書くよりも常に遅い。
ということになる。

t_wadaさんも同様に述べている。

・短期的には得られる
・中期的には逆効果になる
・長期的には致命傷になる
引用:https://speakerdeck.com/twada/quality-and-speed-2020-autumn-edition

まとめ

・開発/運用における生産の低下は「質を犠牲にスピードを得る」という考えが原因。
・長期的に生産性を維持・向上させるならば、クリーンなアーキテクチャと設計をアプリケーションに落とし込む必要がある。
・そのためには優れたアーキテクチャと設計を知らないといけない。

yaGi_04yaGi_04

第2章 2つの価値のお話

2つの価値とは?

本書では、「振る舞い」(機能)と「構造」(アーキテクチャ)の2つの価値をソフトウェア開発者が責任を負う価値と定義されている。

振る舞い(機能)

振る舞いとは、ソフトウェアによりステークホルダーにお金を生み出したり、節約させることであり、プログラマーはその振る舞いをソフトウェアに与えるために雇用されている。

  • プログラマーはソフトウェアに振る舞いを与えるために雇用されている
  • そのため、ステークホルダーの機能仕様や要件文章を作成するための支援もする
  • もし、ステークホルダーの要件を満たしていない場合、デバッガでバグを修正する

だが、それだけが仕事ではない

構造(アーキテクチャ)

ステークホルダーの要件が変更になれば、柔軟にそれに対応する必要があり、その変更が簡単な故ソフトウェアである。

  • ソフトウェア
    • ソフト → 変更が簡単な
    • ウェア → プロダクト

形状にとらわれないアーキテクチャにしたほうが実用的である。

また、

変更の難易度は、変更の形状ではなく変更のスコープに比例する

と記述されているが、あまりピンと来なかったので参考になったURLを添付しておく。
参考: 変更の難易度は、変更の形状ではなく変更のスコープに比例させる

機能 or アーキテクチャ

先述されている通り、ソフトウェア開発者には、ステークホルダーに2つの価値を提供し、維持する責任がある。
しかし、実際にはビジネスサイドの要求や、ステークホルダーの課題解決をするために片方にフォーカスされがち(振る舞い)である。
また、アーキテクチャの重要性をビジネスサイドが評価できないジレンマもある。

ソフトウェア開発チームには、機能の緊急性よりもアーキテクチャの重要性を強く主張する責任が求められる。

まとめ

  • アーキテクチャの重要性を主張するのもソフトウェア開発者の仕事
  • 振る舞いを継続的に提供していくためには、構造の重要性をステークホルダーに理解してもらう必要があり、常にステークホルダーとコミュニケーションをとる必要があると考える

常に闘争である

優れたソフトウェア開発チームは、真正面から闘争に立ち向かう。
ステークホルダーたちと対等に、ひるむことなく口論する。
ソフトウェア開発者もステークホルダーであることは忘れてはいけない

mao12312mao12312

第3章 パラダイムの概要

パラダイム

パラダイムとはプログラミングの方法。
プログラミングの考え方や捉え方の意味合いもある。

本書では3つのパラダイムを紹介していた。

構造化プログラミング

  • 最初に導入 されたパラダイム
  • 制限がないジャンプ(goto文の使用)をifやwhileなどの構文に置き換えた
    • goto構文は単に指定した場所に飛ぶので、なぜそこに飛ぶかなどの意図が不明確で伝わりづらく、スパゲティコードの原因にもなる。

本書では構造化プログラミングを下記のように記している。

構造化プログラミングは、直接的な制御の移行に規律を課すものである。

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

  • 関数呼び出しのスタックフレームをヒープに移動できること
  • 関数から戻ってきたあとでも関数で宣言したローカル変数が存在し続けられること
  • 上記を発見し、ポリフォーフィズム(呼び出した関数が呼び出し元のオブジェクトに適した振る舞いをすること)の発見につながった

本書ではオブジェクト指向プログラミングを下記のように記している。

オブジェクト指向プログラミングは、間接的な制御の移行に規律を課すものである。

関数型プログラミング

  • 関数型プログラミングの基礎となっているのラムダ計算の基本的な概念は不変性である
  • このことから関数型言語には代入分がない(変数の値を変更する手段が用意された言語もある)

本書では関数型プログラミングを下記のように記している。

関数型プログラミングは、代入に規律を課すものである。

これらのパラダイムは何をすべきかより何をするべきではないかを示している。
またアーキテクチャの大きな関心ごと「コンポーネントの分離」「データ管理」「機能」に対応している。

mao12312mao12312

第4章 構造化プログラミング

  • 1950,60年代はプログラミングはハードなモノであり、複雑なプログラムを管理するためには人間が支援しないといけない上に多々失敗してしまう。
  • 上記を解決するために数学の証明を適応しようと考えた。
  • ifやdo/whileを使用することによって、証明可能な単位に 再帰的に分割できる可能性が出てきた。
  • 構造化プログラミングではあらゆるプログラムは「順次」「選択」「反復」の 3つの構造で構築できるとしている。
  • こうして大きなモジュールからコンポーネント、そして証明可能な機能へ分割することができた
  • 分割した機能が正しいことを証明するのは科学的な証明の反証可能性を使用した
  • 構造化プログラミングではモジュールを機能的に分割することが最適としている。

最小の機能から最大のコンポーネントまで、あらゆるレベルにおいて、ソフトウェアは科学
のように、反証可能性によって動かされている。ソフトウェアアーキテクトは、簡単に反証で
きる(テスト可能な)モジュール、コンポーネント、サービスを定義しようとする。そのため
に、さらに上位のレベルにおいて、構造化プログラミングのような制限を課している。

yaGi_04yaGi_04

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

オブジェクト指向プログラミング(OO言語)でよく特徴として取り上げられている「カプセル化」「継承」「ポリモーフィズム」についての考察がこの章でされている

カプセル化

  • 「プライベートなデータメンバー」と「クラスのパブリックなメンバー関数」に線引きを作ること
  • C言語(非OO言語)では「プライベートなデータメンバー」と「クラスのパブリックなメンバー関数」を完璧に分けて実装できた
  • C++(OO言語)では言語の性質上クラスのメンバー変数をヘッダーファイルに宣言している。そのため、クライアントサイド
    からメンバーの実装が見えてしまっており、完璧なカプセル化が壊れてしまっている
  • また、JavaやC#ではヘッダーと実装の分離が禁止され、さらにカプセル化の弱体化が起こっている

継承

  • C言語でもプログラマーが手作業で実装していた

継承はOO言語以前も使われていたが、さほど便利でもなく多重継承なども実現は困難であった

  • 結論としては、OO言語は継承は便利に使えるようになっているが、非OO言語でも同様の実装は可能である

ポリモーフィズム

  • OO言語はポリモーフィズムを安全かつ便利に提供してくれた
  • 非OO言語でも関数へのポインタを利用して実装できたが、関数ポインタが危険であった
    • ポインタ初期化時にポインタを経由して関数を呼び出す規約があるため
  • 関数をデバイスなどの依存関係から解放し、IOの変更があっても動く関数にすることができる
    • プラグインアーキテクチャーの考案

依存関係の逆転

  • ポリモーフィズム以前は、ソースコードの依存関係は、制御の流れに従っている
    • 制御の流れはシステムの振る舞いによって決まり、ソースコードの依存関係は制御の流れによって決まる
  • IFを実装することで制御の流れを逆転させ依存関係を逆転させることができた
    • 依存関係の方向を絶対的に制御できるようになった
  • それにより、データベースやUIをビジネスルールに依存させることができるようになり、ビジネスルールからUI、DBへ依存することがなくなる
    依存関係逆転の法則の参考

OOとは「ポリモーフィズムを使用することで、システムにあるすべてのソースコードの依存関係を絶対的に制御する能力」

mao12312mao12312

6章 関数型プログラミング

25を二乗する計算プログラム
Java

public class Squint {
    public static void main(String args[]) {
      for (int i=0; i<25; i++)
        System.out.println(i*i);
} }

Clojure(関数型言語)

  (println (take 25 (map (fn [x] (* x x)) (range))))
  • Javaではルー プの制御変数 i は可変変数が存在するが、Clojureには存在しない。

  • 関数型言語の変数は変化しない
    ↑アーキテクチャの観点からすると上記の概念は重要
    理由:競合状態、デッドロック状態、並行更新の問題の原因が、すべて可変変数にある

  • 更可能な変数を並行更新や競合状態から保護するために、トランザクショナルメモリを使用する。

イベントソーシング
データの現在の状態だけを格納する考え方。CRUDのCRのみで状態を管理するイメージ。
イベントだけなので可変変数は必要としないが、現在の状態が欲しい際は過去のイベントを全て参照する必要がある。

mao12312mao12312

第3部 設計の原則

SOLID原則

関数やデータ構造をどのようにクラスに組み込むのか、そしてクラスの相互接続をどのようにするのかの指針となる。

SOLID原則の目的は下記の性質を持つ中間レベルのソフトウェア構造を作成すること

・ 変更に強いこと
・理解しやすいこと
・コンポーネントの基盤として、多くのソフトウェアシステムで利用できること

「中間レベル」というのはモジュールやコンポーネントを指している。

SOLID原則の種類と概要

・単一責任の原則(SRP:SingleResponsibilityPrinciple)
個々のモジュールを変更する理由がたったひ とつだけになるように、ソフトウェアシステムの構造がそれを使う組織の社会的構造に大 きな影響を受けるようにする。

・オープン・クローズドの原則(OCP:Open-ClosedPrinciple)
ソフトウェアを変更しやすくするために、既存 のコードの変更よりも新しいコードの追加によって、システムの振る舞いを変更できるよ うに設計すべきである。

・リスコフの置換原則(LSP:LiskovSubstitutionPrinciple)
交換可能な パーツを使ってソフトウェアシステムを構築するなら、個々のパーツが交換可能となるような契約に従わなければいけないということ。

・インターフェイス分離の原則(ISP:InterfaceSegregationPrinciple)
ソフトウェアを設計する際には、使っていないものへの依存を回避すべきだという原則。

・依存関係逆転の原則(DIP:DependencyInversionPrinciple)
上位レベルの方針の実装コードは、下位レベルの詳細の実装コードに依存すべきではなく、 逆に詳細側が方針に依存すべきであるという原則。

詳細については次章以降に記載されている。

yaGi_04yaGi_04

第7章 SRP: 単一責任の原則

どのモジュールでもたった一つのことを行うべき → クリーンアーキテクチャー的には間違え

  • このような原則は存在するが、、
    • 巨大な関数をリファクタリングする際に小さな関数に切り分けるときに使う
    • これを用いるのは最下位レベル

単一責任はそのモジュールを使用するユーザーやステークホルダーごとに作成する

モジュールを変更する理由はひとつだけである

モジュールを使用する、ユーザーやステークホルダーが変更する理由になる

モジュールはたったひとりのユーザーやステークホルダーに対して責務を負うべきである

たったひとりのユーザーやステークホルダー → ユーザーをグルーピングして"アクター"と呼ぶ

それぞれのモジュールはアクターに対しての責任を負うように作るべきである

  • モジュールを使用するのが、複数アクターが混在する場合、ひとつのアクターに対しての変更が別のアクターに対しての予期しない変更になってしまい、バグになる可能性がある

解決策

  • 関数を別クラスに移動する方法で解決できる
  • Facadeパターンを使用する
yaGi_04yaGi_04

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

ソフトウェアの振る舞いは、既存の成果物を変更せずに拡張できるようにすべき

各レベルの概念にもとづいた保護の層ができている

  • Presenterの変更にControllerやInteractorは影響を受けない

情報の隠蔽の役割

  • Controllerの変更がInteractorの変更に影響されないようにするとともに、Intoractorの変更がControllerへ与える影響も保護したい

図が多くて文での説明が難しいので参考リンク張りますmm
オープン・クローズドの原則の重要性について

mao12312mao12312

第9章 LSP:リスコフの置換原則

概要

本書では下記のように記載されている。

ここで望まれるのは、次に述べるような置換可能な性質である: S 型のオブジェクト o1 の各々に、 対応する T 型のオブジェクト o2 が 1 つ存在し、T を使って定義されたプログラム P に対して o2 の代わりに o1 を使っても P の振る舞いが変わらない場合、S は T の派生型であると言える。

上記を簡潔にすると、
派生型は基底型と置換可能でないといけない。
本書の図9-1参照

正方形・長方形問題

図9-2参照
quare(正方形)は、Rectangle(長方形)の派生型ではない。
長方形は高さと幅をそれぞれ指定するが、正方形は高さ・幅同時に設定するので派生型として扱うと適切に処理されない。
対策として、図9-2のUserの中に正方形の場合(if分)の条件分岐を追加してするなどあるが、オープン・クローズドの原則に違反してしまう。

リスコフの置換の原則違反の例

本書p96を参照
複数のタクシー会社を横断して、自分に最適なタクシーを探すこと ができるシステムを例としている。
下記一部コードを記載した。

class Taxi {
    // 省略
    url = () =>  purplecab.com/driver/Bob/pickupAddress/24 Maple St./pickupTime/153/destination/ORD
}
class Acme extends Taxi {
    // 省略
    url = () =>  purplecab.com/driver/Bob/pickupAddress/24 Maple St./pickupTime/153/destination/ORD
}

const getDestination = (url) => {
    return url.substr(url.indexOf('destination/') + 1)
}

基底型となるTaxi型があり、その派生であるAcme型(タクシー会社)がある。
Acmeのプログラマはdestination というフィールド名を dest と省略してしまったので、上記のAcmeクラスのgetDestinationは成り立たなくなる。

解決するシンプルな方法は、Acme会社であれば...というif文を追加することである。
しかし、対応するタクシー会社が増えるにつれてif文を追加することになるので、システム的にバグが発生しやすい状況になる。
本書では「URIをキーとする設定データ ベースを使い、何らかの配車コマンド作成モジュールを用意する」と記載されている。(p97参照)

上記のように置換可能になっていないせいで複雑な仕組みを追加することになる。

yaGi_04yaGi_04

第10章 ISP: インターファイス分離の原則

  1. 複数ユーザーがOPSクラスを使っている。(プログラムはJavaで書かれているとする。)
  2. ここでは、User1がops1を使い、User2がops2を使い、User3がops3を使っていたとする。
  3. ここで、ops3に変更があった場合それを使っていないUser1やUser2も再コンパイル、再デプロイの必要が出てくる。
  4. 解決策として各操作に対してインターフェイスを作成することで、OPSの変更があっても影響しない箇所の再コンパイル、再デプロイが不要になる。

インターフェイス分離の原則(ISP)とアーキテクチャとの関係

  1. システムSをフレームワークFを使い実装したいとする
  2. フレームワークFはデータベースDに依存して実装されている
  3. この場合データベースDに変更があった場合システムSにまで影響を及ぼす。

第13章で改めて掘り下げられる

mao12312mao12312

第11章 DIP: 依存関係逆転の原則

ソースコードの依存関係が(具象ではなく)抽象だけを参照しているもの。
それが、最も柔 軟なシステムである。これが「依存関係逆転の原則(DIP)」の伝えようとしていることである。

要するに「具体に依存せずに、抽象に依存せよ」ということ。
上位のレイヤーが下位のレイヤーの実装に依存しがちになるが、本来は上位レイヤーがドメインの本質を持っているので、影響を受けるべきではない。
ので、実装に依存するのではなく上位のレイヤーが宣言した抽象に依存するようにすることで、上位レイヤーへの依存させないようにする。

Abstract Factory

インスタンスの生成を専門に行うクラスを用意することで、生成されるオブジェクトに間違いがないようにすること。
https://www.techscore.com/tech/DesignPattern/AbstractFactory

具体的な実装は下記リンクを参照
https://zenn.dev/naas/articles/c743a3d046fa78

yaGi_04yaGi_04

第12章 コンポーネント

コンポーネントとは、デプロイの単位
元々プログラムをメモリ上のどの場所にどのように配置するのかは、プログラマの役割であった。

リローケタビリティ

コンパイラが出力するバイナリコードに手を入れて、スマートローダでメモリに再配置できるようにしたもの。

リンカ

ロードとリンクそれぞれ別のフェーズに分けることになり、遅い部分(リンクの処理)をリンカと呼ばれる別アプリケーションに切り出した
リンカは出力されたリンク済みのリロケータブル形式であり、ローダによる再配置も非常に高速にできた

プログラムは、コンパイルとリンクに使える時間を使い切るまで肥大化する

→マーフィーの法則

ムーアの法則により解決された

yaGi_04yaGi_04

第13章 コンポーネントの凝集性

再利用・リリース等価の原則(REP)

再利用の単位とリリースは等価になる。

  • コンポーネントのデプロイにはリリース番号が必要
    • 互換性、変更を確認するため
  • このため、コンポーネントは凝集性を求められる
    • 一貫したテーマや目的があり、それを共有するモジュールを集める必要がある
  • まとめてリリースが可能でなければいけない

閉鎖性共通の原則(CCP)

同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること。変更の理由やタイミングが異なるクラスは、別のコンポーネントに分けること。

  • 単一責任の原則(SRP)をコンポーネント向けに言い換えたもの
    • 「コンポーネントを変更する理由は複数あるべきではない」
  • CCPでは同じタイミングで変更されることが多いクラスは一つにまとめておく
    • デプロイ作業を減らすため

全再利用の原則(CRP)

コンポーネントのユーザーに対して、実際には使わないものへの依存を強要してはいけない。

  • 一緒に用いられることが多いクラスやモジュールは同じコンポーネントにまとめる
    • ひとつのクラスを利用することは少なく、ほかのクラスと組み合わせて再利用可能な抽象として用いることが多い。
  • 同じコンポーネントにまとめるべきではないコンポーネントも示す
    • 使う側のコンポーネントは、使われる側のコンポーネントの一つのクラスを使いたい
    • だからといって依存関係は弱くなることはない
    • 使われる側のコンポーネントが変更されるたびに、使う側のコンポーネントにも変更が必要になる(変更が不要でも、再コンパイルやデプロイが必要になってくる)
    • コンポーネントに依存するのであれば、コンポーネントに含まれるすべてのクラスに依存するようにしておきたい
    • 逆に依存しないコンポーネントは別のコンポーネントにしておきたい

コンポーネントの凝集性のテンション図

REP, CCPは包含関係、CRPは相反する原則になっている
この三つのバランス関係をうまくとることが、アーキテクチャの腕の見せ所

  • REP,CRPを守ることにだけ力を入れているアーキテクトは、些細な変更が大量のコンポーネントに影響を及ぼす
  • CCP, REPに注目すると、リリースの回数が無駄に増えてしまう

優れたアーキテクトは、この三角形のなかで開発チームの現在の懸念事項に見合った落としどころを見つける。
時がたてばチームの懸念事項が変わるということも心得ている。

mao12312mao12312

第14章 コンポーネントの結合

非循環依存関係の原則

「二日酔い症候群」:自分が変更したソースコードを他の誰かの変更によってうまく動かない現象
上記の解決策が「週次ビルド」と「非循環依存関係の原則」である。

週次ビルド

週の4日は変更を気にせずに開発を進め、5日目(金曜日)に統合作業を行う。
メリットは最初の4日は変更を気にせずに開発を進められること、デメリットはプロジェクトが大きくなると1日だけでは統合が終わらず、前日またその前日と統合作業がずれ込んでしまう可能性があるということ。

循環依存の除去

もう一つの解決策が、開発環境をリリース可能なコンポーネントに分割することである。(図14-1参照)
担当するコンポーネントの動作が確認できればチームが使用するディレクトリに移動しする。そのバージョンのコンポーネントを使用するかは開発チームに委ねられる。

この方法を成り立たせるためには循環依存があってはいけない。(図14-1参照)
循環依存とはコンポーネントの矢印を辿っても元のコンポーネントに戻ることがない状態のこと。(有向非循環グラ)

コンポーネントの依存グラフにおける循環依存の影響

要件が追加され新たにクラスが追加されたとする。(図14-2参照)
図の例だと、循環依存になりリリースするのが難しくなる。
図の例で考えると

  • Databaseから見たら「Interactors」「Entities」「Authorizer」は一つのコンポーネントのように見える。
  • Entitiesをテストしようとすると「Interactors」「Authorizer」も統合してビルとしないといけない。

循環依存の解消

コンポーネントの循環依存を排除して、有向非循環グラフに戻すこと が可能。

  1. 図 14-3 の場合は、User が必要とするメソッドを持つインターフェイスを作成。(図14-3参照)
  2. Entities と Authorizer の両方が依存する新しいコンポーネントを作成。(図14-4参照)

「変化」するもの

コンポーネントの構造は要件によって変化するモノである。
変更を伴うことも視野に入れて、循環依存が入らないように注意しないといけない。

トップダウンの設計

コンポーネント図はトップダウンで設計するのは不可能

システム設定の際にも検討はするか、その後も変更が生まれる
コンポーネント図はビルド可能性や保守性を確認するために作成するモノで、アプリケーションの機能について記載することはほどんどない。(依存関係の把握のために設計初期に作成することもある)

安定依存の原則(SDP)

安定度の高い方向に依存すること。

安定度高⇨安定度低は✖︎といこうこと
閉鎖性共通の原則(CCP)を満たすよう設計すれば、特定の変更以外に影響を受けないコンポーネントを作ることができる。
このようなコンポーネントは自身の変更がしづらくなる為、変更しづらいコンポーネントから依存されてはいけない。

安定度

少ない労力で状態を変更できるかどうか。
少ない労力で変更できる⇨安定度低
少ない労力で変更できない⇨安定度高
ソフトウェアを変更しづらくするには、多数のソフトウェアコンポーネントから依存されるようにすればいい。(図14-5,6参照)
安定したコンポーネント = 複数のコンポーネントから依存されている
上記の状態はコンポーネントを変更しない理由が依存されているコンポーネント分存在することになる。
このような外部要因で変更が必要になることはない。この ようなコンポーネントを独立コンポーネントと呼ぶ

依存するコンポーネントが一つもない場合は、変更しやすい安定度が低いコンポーネントとなる。
このようなコンポーネントを従属コンポーネントと呼ぶ。

安定度の指標

・ファン・イン:依存入力数。この指標は、コンポーネント内のクラスに依存している外部 のコンポーネントの数を表す。
・ファン・アウト:依存出力数。この指標は、コンポーネント内にある、外部のコンポーネ ントに依存しているクラスの数を表す。
・I(Instability):不安定さ。I = ファン・アウト÷ (ファン・イン + ファン・アウト)。こ の指標は、ゼロ以上 1 以下の値になる。I = 0 が最も安定しているコンポーネントを表し、 I = 1 が最も不安定なコンポーネントを表す。

I の値が 1になるのは、そのコンポーネントに依存するコンポーネントがひとつもなく、そのコンポーネントが他のコンポーネントに依存し、他のコンポーネントに対して何も責務を負わず、ただ依存しているだけである。

I の値がゼロになるのは、このコンポーネントはほかのコンポーネントに対する責務を負い、ほかのコンポーネントからは独立している状態。これは、最も安定した状態である。

すべてのコンポーネントに高い安定度を求める必要はない

全てのコンポーネントが安定している状態が理想かというとそんなことはない。
システムには手を加えづらくなるし、状況によって変更もできない。
コンポーネント構造を設計する場合は安定をの高いコンポーネント、安定度の低いコンポーネントどちらも存在する前提で行いたい。
3 つのコンポーネントからなるシステムの理想的な構成については図14-8を参照
安定度の低いコンポーネント⇨安定度の高いの順で上から記載すると安定依存の原則(SDP)違反を見つけやすい。

安定度高⇨安定度低で依存させてしまった場合は、依存関係逆転の原則(DIP)を使用して解消することができる。(図14-10)

安定度・抽象度等価の原則(SAP)

上位レベルの方針をどこに適用するか

システムの中には頻繁に変更すべきでないソフトウェアがある。
上記を踏まえると、ビジネスやアーキテクチャに関する決定は仕様変更になってほしくないので、安定度の高いコンポーネントに配置する。
しかし、安定度の高いコンポーネントに配置すると、方針を表現しているソースコードを変更しづらくなる
その結果、柔軟性に欠けるアーキテクチャになってしまう。
この解決方法として、クラスを作るときには既存コードを変更せずに拡張できるようにしておくオープン・クローズドの原則(OCP)があげられる。

この原則を満たすクラスが抽象クラスである。

安定度・抽象度等価の原則(SAP)の導入

「安定度の高いコンポーネントは抽象度も高くあるべき」で「安定度の低いコンポーネントは具体的なものであるべき」ということ。
つまり、コンポーネントの安定度を高くしようと思えば、拡張できるようにインターフェイスと抽象クラスで構成すること。
抽象クラスとは(ちょっと脱線)

抽象クラスとは、抽象メソッドを1つ以上持つクラスです。
抽象クラスは、自身だけでは意味をもたず、サブクラスに継承されることで初めて機能します
抽象クラスを継承したサブクラスは、抽象クラスにある抽象メソッドのオーバーライド必須
https://qiita.com/aiko_han/items/e8ddce85188970fd77da
https://qiita.com/yoshinori_hisakawa/items/cc094bef1caa011cb739

上記で安定度の高いコンポーネントが安定度の低いコンポーネントに依存していたのを解決する方法が依存関係逆転の原則であった。
紹介したことを踏まえると安定依存の原則(SDP)では、「安定度が高くなる方向に依存すべき」で安定度・抽象度等価の原則(SAP)では、「安定度が抽象度と連動するべき」というこの二つの組み合わせが、
コンポーネント版の依存関係逆転の原則を表現している。

抽象度の計測

●Nc:コンポーネント内のクラスの総数。
● Na:コンポーネント内の抽象クラスとインターフェイスの総数。
● A:抽象度。A = Na ÷ Nc

A は 0 から 1 までの値をとり、0であれば抽象クラスとインターフェイスが含まれないことを表し、1であれば抽象クラスやインターフェイスしか含まれないことを表す。

主系列

安定度(I)と抽象度(A)の関連の定義について。(図 14-12)
安定度:0が高、1が低
抽象度:0が低、1が高
(図を見ながら説明)

主系列からの距離

コンポーネントの位置が主系列からどれだけ離れているかを表す指標を作成し、計測してみる。

D(Distance)3:距離。D = |A+I-1|。これは 0 以上 1 以下の値となる。値が 0 の場合、そのコンポーネントはまさに主系列にあることを意味する。値が 1 の場合、主系列から最もかけ離れた状態を表す。

yaGi_04yaGi_04

第15章 アーキテクチャとは?

ソフトウェアアークテクチャとはプログラマである
プログラミングをしながらチームの生産性を最大化させる形状をもたらす
アーキテクチャの目的は、ソフトウェアシステムの開発・デプロイ・運用・保守を用意にすること

それらを容易にするための戦略は、できるだけ長い期間、できるだけ多く選択肢を残すことである。

アーキテクチャの主な目的は、システムのライフサイクルをサポートすることである。
優れたアーキテクチャはシステムを容易に理解・開発・保守・デプロイでき、最終的な目的は、システムのライフタイムコストを最小限に抑えて、プログラマの生産性を最大にすること。

開発

チームが異なればアーキテクチャの決定も異なる

  • 開発者が5人
    • コンポーネントやインターフェイスが明確に定義されていないモノリシックなシステムでも開発ができる
  • 開発者が7人が5チーム
    • 信頼性が高い安定したインターフェイスでコンポーネントを分割しなければ、作業をうまく進めることが難しい
    • コンポーネント分割が1チームずつの分割である場合、最適な分割ではなく、開発スケジュールに追われてそのようになっているかもしれない

デプロイ

システムを** 単一のアクション **で簡単にデプロイできるようにすること
開発初期はデプロイ戦略を考慮することはほとんどない。そのため、システムの開発は容易になるかもしれないが、デプロイは非常に難しいものとなってしまう。
早い段階からアーキテクトがデプロイの問題について考えていれば、何らかの対策ができていただろう。

運用

運用の問題の多くは、ソフトウェアアーキテクチャを変更しなくても、ハードウェアを追加すれば解決できる。
しかし、アーキテクチャがしっかりしていると、見ただけで運用方法が明らかになる。

保守

保守は最もコストがかかるもの

  • 洞窟探検
    • 既存のソフトウェアから、新しい機能の追加や欠陥の修正において、最適な場所や戦略を見つけるコスト

アーキテクトを考えればこれらのコストは大幅に低下する。
意図せずに壊してしまうリスクを大幅に軽減できる。

yaGi_04yaGi_04

第16章 独立性

優れたアーキテクトは以下のことをサポートする

  • システムのユースケース
  • システムの運用
  • システムの開発
  • システムのデプロイ

ユースケース

アーキテクチャはシステムの意図をサポートしている
システムの意図はアーキテクトの最大の関心事であり、最優先事項
アーキテクチャがシステムの振る舞いに大きな影響を与えることはない
アーキテクチャが選択肢として考えられることはほとんどない

  • サービスの振る舞い > アーキテクチャ

アーキテクチャレベルでシステムの意図がわかるように、振る舞いを明らかにすること
システムのユースケースは、システムの構造のなかにハッキリと見えるようになる

運用

アーキテクチャはシステムの運用において、本質的な役割を果たす
決定は選択肢として残しておく

  • モノリシックに書かれたシステムがその構造に依存している場合
    • 必要に応じて、複数プロセス、複数スレッド、マイクロサービスなどへと簡単にアップグレードすることはできない。
  • コンポーネントを適切に分割し、コンポーネントの通信方法として特定の技術を想定していないアーキテクチャ
    • システムの運用ニーズの変化に合わせて、スレッド、プロセス、サービスなどへと比較的簡単に移行することができる。

開発

チームや利害関係の多い組織では、開発中にチームがお互いに干渉しないように、それぞれのチームの行動を独立させるアーキテクチャをシステムに持たせる必要がある。
[コンウェイの法則]

デプロイ

目指すべきは「即時デプロイ」
これもシステムをコンポーネントに適切に分離・分割することで実現可能
このなかには、システム全体をまとめ、各コンポーネントが適切に開始・統合・管理されるようにする主要なコンポーネントも含まれる

選択肢を残しておく

実際にこのバランスを取ることは難しい
問題となるのは、すべてのユースケースを把握できないこと、運用上の制約、チーム構造、デプロイ要件がわからないことである
システムのライフサイクルに応じて、それらは必然的に変化していく
できるだけ長い期間、できるだけ多く選択肢を残すことが可能になる

レイヤーの切り離し

すべてのユースケースを把握することはできない。だが、システムの基本的な意図はわかっている

  • ユーザーインターフェイスを変更する理由は、ビジネスルールを変更する理由とは関係がない
    • ユースケースの UI 部分とビジネスルールの部分を分離したい
      • そうすれば、ユースケースは明確にしたままで、それぞれの部分を独立して変更できるようになる。

システムは切り離された水平レイヤー(たとえば、UI、アプリケーション特有のビジネスルール、アプリケーションに依存しないビジネスルール、データベースなど)で分割されている

## ユースケースの切り離し
ユースケースはシステムを分割する自然な方法である
ユースケースは、システムの水平レイヤーを薄く垂直にスライスしたものである

  • 注文追加のユースケースの UI と注文削除のユースケースの UI を分離する
    • ビジネスルール、データベースについても同様である
    • ユースケースの分割をシステムの高さにそろえておく

変更する理由の違いでシステムの要素を切り離しておくと、古いユースケースに影響を与えずに新しいユースケースを追加できる
また、ユースケースから使用する UI やデータベースをグループ化しておけば、新しいユースケースを追加しても古いユースケースに影響を及ぼすことがない

独立した開発が可能

コンポーネントが明確に切り離されていれば、チーム同士の干渉は緩和される

独立デプロイ可能性

レイヤーやユースケースが切り離されていれば、デプロイの柔軟性も高まる

重複

  • 本物の重複
    • あるインスタンスに変更があれば、そのインスタンスのすべての複製にも同じ変更を反映しなければいけない
  • 偽物の重複
    • 明らかに重複していたコードが(変更頻度や理由が違うために)異なる進化を遂げ、数年後には両者がまるで違ったものになる

切り離し方法

ユースケースの切り離しは、運用にも適用可能である。ただし、運用で活用するには、適切な方式が必要となる。
切り離し方式も、そうした選択肢のひとつだ。
レイヤーやユースケースを切り離す方法はいくつもある

  • ソースコード
  • デプロイレベル
  • サービスレベル

プロジェクトの初期段階では判断が難しい
サービスが成長するにつれ、いずれデプロイ可能な単位やサービスとして切り離すことになるだろう。
いざというときのために、サービスを作れそうなところまで切り離すというものである。

システムの切り離し方式は時間とともに変化する可能性があるということだ。
そして、優秀なアーキテクトであれば、そうした変化を予見して、適切に進めていくのである。

yaGi_04yaGi_04

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

ソフトウェアアーキテクチャとは、境界線を引く技芸である

アーキテクトの目的は、求められるシステムを構築・維持するために必要な人材を最小限に抑えることである
人のパワーを奪うものは、早すぎる決定との結合である。
→ ビジネス要件が固まらないまま、フレームワークやDBなどのシステム要件の決定を下すこと

境界線はどこに引くのか?

  • 境界線は「重要なもの」と「重要ではないもの」の間に引く
    • GUI はビジネスルールにとって重要ではないので、その間に境界線を引く。
    • データベースは GUI にとって重要ではないので、その間に境界線を引く。
    • データベースはビジネスルールにとって重要ではないので、その間に境界線を引く。
  • こうすることで、ビジネスルールはDBやGUIの影響を受けない

入出力はどうするのか?

  • IO は無関係
    • インターフェイスの背後には、それを動かすモデルがある
    • モデルはビジネスルール
    • ここでは、GUIがビジネスルールを気にかけていることになるが、ビジネスルールがGUIを気かけることはない
    • GUIは他の種類のインターフェイスに置き換え可能である

プラグインアーキテクチャ

ほかのモジュールから影響されないモジュールが必要になる

  • ウェブページのフォーマットやデータベースのスキーマを変更したときに、ビジネスルールが壊れるようなことはあってほしくない。

システムをプラグインアーキテクチャにしておくと、変更の影響を伝播させないファイアウォールを構築できる。
境界線は変更の軸があるところに引く。

  • それぞれのコンポーネントは変更の頻度や理由が違う

単一責任の原則(SRP)はどこに境界線を引けばいいかを教えてくれる。

mao12312mao12312

第18章 境界の解剖学

システムのアーキテクチャは、ソフトウェアコンポーネントとそれらを分離する境界によって定義される

本書ではこの境界をいくつか紹介している

境界を越える

実行時に境界を越えるものがあるとすれば

  • 境界の向こう側の関数の呼び出し
  • データの受け渡し

になる。
適切に境界を越えるにはソースコードをの依存関係を管理する必要がある。
理由はモジュールの変更時に再コンパイル、デプロイする必要があるからである。
変更に対してファイアウォールを管理・構築することが、境界の意味するところである

恐怖のモノリス

アーキテクチャの境界は、物理的に表現されているわけではない
プロセッサーとアドレス空間の中で競うとデータを区分しているだけ。単一の実行ファイルに過ぎない(モノリス)
単一のファイルだからといって境界が存在していないとか、意味がないというわけではない。
アーキテクチャは内部の依存性を管理するために、何らかの動的ポリモーフィズムに依存しているからである。
ポリモーフィズムに相当するものがなければ、アーキテクトは切り離しを実現するために、関数へのポインタを使用するといった危険な方法に頼ることになる。
なぜ危険なのか?
オブジェクト指向言語他と、オブジェクトとして安全にモノの受け渡しができるが、ポインタで渡す場合は気をつけていないと用意されているメモリの範囲をはみ出すこともあり、動作がおかしくなるから

可能な限りシンプルに境界を越えるには、下位レベルのクライアントから上位レベルのサービスに対して、関数呼び出しをすることである。

(図18-1参照)

上位レベルのクライアントが下位レベルのサービスを呼び出す必要がある場合、動的ポリモー フィズムを使用して、制御の流れの依存性を逆転させる

(図18-2参照)

こうしておくことで、チーム単位での開発・テスト・デプロイの作業に役に立つ

デプロイコンポーネント

アーキテクチャの境界の最も単純な物理的表現は、動的リンクライブラリである。

→動的リンクとは、

プログラムの実行に必要なライブラリやモジュールなどを、実行時にリンク(連結)して起動すること。https://e-words.jp/w/動的リンク.html

実行時にリンクされるので、コンポーネントはバイナリやデプロイ可能な形式でデリバリーされる。
これは「デプロイレベルの切り離し方式」である。

スレッド

スレッドはアーキテク チャの境界やデプロイ単位というよりも、実行のスケジュールや順序を整理する方法である。

実行単位。
スレッドはひとつのコンポーネントに含めることもできるし、複数のコンポーネントに分散させることもできる。

ローカルプロセル

  • 強力な物理的なアーキテクチャの境界
  • 通常はコマンドラインや同等のシステムコールで作成される
  • 同じプロセッサまたはマルチコアの同じプロセッサセットで実行されるがアドレス空間は別々
  • ローカルプロセス間の通信は、ソケットか、メールボックスやメッセージキューなどのOSが適用しているもの
  • ローカルプロセスは「上位コンポーネント」の一種と考える

サービス

  • 最も強い境界
  • サービスとは、一般的にコマンドラインや同等のシステム コールで開始されるプロセスのこと
  • 物理的な場所に依存せず、同じプロセッサやコアに存在することもあればそうでないこともある。
mao12312mao12312

第19章 方針とレベル

ソフトウェアシステムは方針を示したものである。
その方針はさらに詳細な方針へと分割される。(ビジネススケールの算出方法やレポートのフォーマット方法など)
同じ理由や時期に変更する方針は、同じレベルの同じコンポーネントにまとめておき、異なる理由や時期に変更する方針は、異なるレベルの異なるコンポーネントに分けておく。

レベル

「レベル」の厳密な定義は「入力と出力からの距離」である。
方針がシステムの入力と出力から離れていれば、それだけレベルは高くなる

(図19-1参照)

データフローとソースコードの依存性は、必ずしも同じ方向を指しているとは限らない
ソースコードの依存性はデータフローから切り離し、レベルと結び付けるべきである。

mao12312mao12312

第20章 ビジネスルール

ビジネスルールとは、ビジネスマネーを生み出したり節約したりするルールや手続きのことだ。

例:銀行の場合はローンに利子をつけることで、利益を得るビジネスルールとなっている。
これはコンピュータで計算しようと、手で計算しようと関係ない。
このようなビジネスにとって欠かせないものであり、自動化に関わらず存在するルールを最重要ビジネスルールと呼ぶ。

この最重要ビジネスルールにはいくつかのデータが必要になる。
ローンの場合は貸付金残高や金利、支払いスケジュールなどである。
こうしたデータのことを最重要ビジネスデータと呼ぶ。

エンティティ

エンティティとは

コンピュータシステムの内部にあるオブジェクトであり、最重要ビジネスデータを操作する最重要ビジネスルールをいくつか含んだものである。

エンティティオブジェクトには以下のものが含まれる

  • 最重要ビジネスデータ
  • 最重要ビジネスデータへの簡単なアクセス手段
    参考:図20-1

このようなクラスを作成するときはビジネスにとって不可欠な概念のソフトウェアをまとめ、他のシステムとは切り離すようにする。
データベース、ユーザーインターフェイス、サードパーティ製のフレームワークなどの影響を考えなくても良いようにする。
「クラス」とあるがオブジェクト指向言語である必要はなく、最重要ビジネスデータと最重要ビジネスルールを同じソフトウェアモジュールにまとめるだけで良い。

ユースケース

ビジネスルールはエンティティほど純粋なものばかりではなく、自動化されたシステムを定義・制限することによって利益をあげるビジネスルールもある。こうしたルールは自動化されたシステムとして意味があるので、手動の環境では使用できない。
参考:p191新規ローンの箇所

上記の例で出てきたものをユースケースと呼ぶ

ユースケースとは、自動化されたシステムを使用する方法を記述したものである

他の表現

ユースケースとは、利用者があるシステムを用いて特定の目的を達するまでの、双方の間のやり取りを明確に定義したもの。利用者は機器を操作する人間以外にも外部の他のシステムなどを想定する場合もある。
https://e-words.jp/w/ユースケース.html

ユースケースはアプリケーション固有のビジネスルールが記載されている。

ユースケースには、エンティティの最重要ビジネスルールをいつ・どのように呼び出すかを規定したルールが含まれている。ユースケースはエンティティのダンスを制御しているのである。

例にもあったようにユースケースは入力データ、出力データ、エンティテへの参照といったデータを持っている。
エンティティが上位概念でユースケースが下位概念なので、エンティティはユースケースのことを知らない(依存関係逆転の原則)
エンティティは複数のアプリケーションで使用できるように一般化されているので、システムの入力と出力から遠く離れている。したがって、ユースケースはエンティティに依存し、エンティティはユースケースに依存していないのである。

リクエストとレスポンスのモデル

ユースケースは入力を期待し、出力を生成するのは役割なのでユースケースクラスのコードが HTMLやSQL のことを知る必要はない。
依存性がないことは重要でリクエスト、レスポンスが依存しているとそれらに依存するユースケースも間接的に結び付けられて依存してしまうからである。

ユースケースのデータ構造にエンティティオブジェクトへの参照を含めたいと思うかもしれないが、2 つのオブジェクトの目的・変化の理由はまったく違うのでこのような実装にしてはいけない。
このような実装をしてしまうと閉鎖性共通の原則(CCP)と単一責任の原則(SRP)に違反し、コードに多くのトランプデータや条件分岐が発生してしまうからである。

まとめ

ビジネスルールはソフトウェアが存在する理由である。
他の何者の影響を受けるべきではない。したがって最も独立していて、再利用可能なコードでなければいけない。

yaGi_04yaGi_04

第21章: 叫ぶアーキテクチャ

家族用の一戸建ての計画ならば、まずは玄関があり、玄関ホールがあり、そこからリビングルームやダイニングルームに続いているだろう。ダイニングルームの近くには、少し離れてキッチンがあるだろう。キッチンの隣にはダイネットエリア(食事スペース)があり、その隣にはファミリールームもあるだろう。計画を見れば、家族用の一戸建てであることがわかるはずだ。アーキテクチャが「戸建て」と叫んでいるのである。

  • アーキテクチャはフレームワークに関するものではない(そうあるべきではない)
    • アーキテクチャはフレームワークから提供されるものではない
    • フレームワークは使用するツールであり、アーキテクチャが従うものではない

アーキテクチャがフレームワークにもとづいているのなら、そのアーキテクチャはユースケースにもとづくことはできない。

  • 優れたアーキテクチャはユースケースを中心にしている

    • フレームワーク、ツール、環境に依存することなく、ユースケースをサポートする構造を問題なく説明できる
  • フレームワークは非常に強力で、非常に便利なものである。

    • ユースケースを重視したアーキテクチャをどのように維持するかを考える必要がある

システムアーキテクチャがユースケースをサポートするものであり、フレームワークから少し距離を置いたものになっていれば、フレームワークを使うことなく、すべてのユースケースのユニットテストを実行できるはずだ

アーキテクチャは、システムで使用しているフレームワークではなく、システムそのものについての情報を伝える必要がある

mao12312mao12312

第22章 クリーンアーキテクチャ

ヘクサゴナルアーキテクチャ、DCI アーキテクチャ、BCEなどのアーキテクチャは違いがあるものの目的は「関心事の分離
基本的にはビジネスルールのレイヤーと、ユーザーやシステムとのインターフェイスとなるレイヤーに分かれている。

こういったアーキテクチャーは以下の特性を持つシステムを生み出す。

● フレームワーク非依存:アーキテクチャは、機能満載のソフトウェアのライブラリに依存していない。これにより、システムをフレームワークの制約で縛るのではなく、フレームワークをツールとして使用できる。
● テスト可能:ビジネスルールは、UI、データベース、ウェブサーバー、その他の外部要素がなくてもテストできる。
● UI非依存:UIは、システムのほかの部分を変更することなく、簡単に変更できる。たとえば、ビジネスルールを変更することなく、ウェブ UI をコンソール UI に置き換えることができる。
● データベース非依存:Oracle や SQL Server を Mongo、BigTable、CouchDB などに置き換えることができる。ビジネスルールはデータベースに束縛されていない。
● 外部エージェント非依存:ビジネスルールは、外界のインターフェイスについて何も知らない。

これらをまとめたのが図22-1。

依存性のルール

円の中央に近づくほどソフトウェアのレベルが上がっていく。円の外側は仕組み。内側は方針。
注意なのがレイヤーをこの4つに分割することではない。

エンティティ

企業全体の最重要ビジネスルールをカプセル化したもの。
オブジェクトでも、データ構造と関数でも構わない。
外部で何か変化が起きても、エンティティが影響を受けることはほとんどない。

ユースケース

アプリケーション固有のビジネスルール
エンティティに入出力するデータの流れを調整し、ユースケースの目標を達成できるように、エンティティに最重要ビジネスルールを使用できるようにする。
このレイヤーがエンティティに影響を与えることはないし、データベース、UI、共通のフレームワークなどの外部の変更の影響を受けることもない。
ただ、アプリケーションの操作の変更がユースケースに影響を与えることはあるかもしれない。

インターフェイスアダプター

インターフェイスアダプターのレイヤーのソフトウェアは、ユースケースやエンティティに便利なフォーマットから、データベースやウェブなどの外部エージェントに便利なフォーマットにデータを変換するアダプターである。

プレゼンター、ビュー、コントローラーなどがこのレイヤーに属する。
モデルは、コントローラーからユースケースに渡され、ユースケースからプレゼンターとビューに戻されるデータ構造にであり、このレイヤーはエンティティやユースケースに便利な形式から、データベースに便利な形式にデータを変換するのが役割。
円の内側はDBなど外部については知らない。
このレイヤーではエンティティやユースケースが使用できるように外部の形式から変換する用のアダプターも含まれる

フレームワークとドライバ

図の縁の外側はフレームワークやツールで構成されている。
通常このレイヤーはコードを書かない、書くとしてもグルーコードぐらいである。
フレームワークやドライバのレイヤーには、詳細が詰まっているので、外側に配置し影響を受けにくくしている。

境界線を越える

基本的にレイヤーの外側から内側へ依存するようになっている。
レイヤーの境界を越える中で内側から外側へ依存するような場合(図22-1)は依存性逆転の法則を使用して解消する。境界線を越えたところでソースコードの依存関係が制御の流れと逆転するようにする。

境界線を越えるデータ

境界線を越えるデータは、単純なデータ構造で構成されるべき。
例えばデータベースフレームワークで便利な形式(行構造)などで円の内側にデータを渡す際には内側がその構造(円の外側)について知らないといけなくなる為、基本的には円の内側に適したデータ構造で渡されるべき。

典型的なシナリオ

図22-1を見ながら説明

まとめ

こうしたルールを使用すれば、保守しやすいシステムを構築できるだろうと筆者は伝えている。
システムの外部パーツを別のものを使用したくなった際は最小限の労力で置き換えができるだろう。
個人的な感想としては、このレイヤーに分割する目的としては依存性のルール決めとそれによる責任の明確化をしているのだと思った。

yaGi_04yaGi_04

第23章: プレゼンターとHumble Object

Humble Objectパターン

テストしやすいモジュールとテストしにくいモジュールに分割するパターン

プレゼンターとビュー

  • view
    • GUI部分(Humble Object)
  • presenter
    • viewにobjectを渡すための処理部分

データベースゲートウェイ

ユースケースインタラクターとデータベースの間にあり、アプリケーションがデータベースに対して実行する作成・読み取り・更新・削除のメソッドを含んだポリモーフィックインターフェイス

  • ゲートウェイ
    • データベースのレイヤーにあるクラスで実装する(Humble Object)
  • インタラクター
    • アプリケーション固有のビジネスルールをカプセル化しているだけ(Humbleではない)

テストしやすい部分としにくい部分を分割することで、アーキテクチャの境界が定義される
このパターンを使用すると、システム全体のテスト容易性が大幅に向上する

yaGi_04yaGi_04

第24章: 部分的な境界

本格的なアーキテクチャの境界はコストが高い
だが、今後必要になりそうな境界を作っておきたい状況がある(YAGNIの考えに違反する)
この章ではそうした状況で使える戦略の紹介がされている

最後のステップを省略する

後で必要そうな境界は最初に定義しておき、デプロイする際に二つを同じコンポーネントに含めてデプロイする
このさい、相互インターフェースも、入出力のデータ構造も、すべて設定しておく

  • デメリット
    • 時間がたつとコンポーネントの分離が弱くなり、依存性の境界線が間違った方向にこえていく可能性がある

Strategyパターンを使用する

依存関係の逆転を行い。片方だけインターフェースを作成し将来の分割に備えておく

  • デメリット
    • インターフェースが片方しか作成されていないため、依存関係の逆転が壊れる可能性がある

Facadeパターン

境界はFacadeクラスに定義しておき、ClientはFacadeからserviceを呼び出す

  • デメリット
    • Clientはすべてのサービスクラスに推移的に依存していることに注意
    • Serviceクラスのいずれかのソースコードを変更すると、Client も再コンパイルが必要になる
    • 簡単に裏ルートを作れる

アーキテクチャの境界を部分的に実装していくにはメリット、デメリットそれぞれあるので、適宜状況に応じた適切な選択をする必要がある

mao12312mao12312

第25章 レイヤーと境界

システムは「UI」「ビジネスルール」「データベース」という 3 つのコンポーネントで構成されている。
ゲームを例として考えると、UIはユーザからのメッセージを処理。ビジネスルール(ゲームルール)は何らかの永続的なデータ構造にゲームの状態などを保存する。

だがこれだけなのだろうか?
(Hunt the Wumpus p215を参照)

  • UIコンポーネントとゲームルールを切り離す
    • メリット:多言語対応ができる
  • DBコンポーネントとゲームルールを切り離す
    • メリット:ゲームルールがDBの詳細を知らなくていい。

ここにクリーンアーキテクチャの適応を考える。

上記のアーキテクチャーの境界を見つけることはシンプルなので簡単だが、他にないだろうか?
先ほどは「言語」をUIの変更の軸として扱ったが、テキストの通信方法も変更したくなるかもしれない。(シェルウィンドウ、テキストメッセージ、チャットアプリケーション)
つまり境界は変更の軸によって定義される (図25-3)

このようなBoundary インターフェイスがあることで、違うものがある決まった振る舞いもしくは入出力を持つことで、同じように扱えるようになっている。

流れを横切る

図 25-4は左側がユーザーとの通信、右側はデータの永続化と流れを分けることができる。
ゲームルールはそれら全てを制御している。

これかの情報の流れは増えることもある。
例えばオンラインプレイを実現するために「ネットワーク」が必要になる。(図25-5)

流れを分割する

今までは「ゲームルール」を最上位とする例で考えてきたが、ゲームルールコンポーネントを考えてみる。
ゲームルールの一部には

  • Move Management:洞窟がどのように接続されているか、どのオブジェクトがどの洞窟にあるかプレーヤーが洞窟を移動する方法
  • Player Management :プレーヤーの状態や、イベントのコストとメリットを把握する方針。

PlayerManagementとMove Managementを分ける理由

PlayerManagementとMove Managementを分離するようにマイクロサービスを追加する(図 25-7)
MoveManagementはプレーヤーのコンピュータで処理されるが、PlayerManagementはサーバーで処理される。
こうすることでこのアーキテクチャの境界はPlayerManagementとMove Managementにあることがわかるだろう。

まとめ

例のHunt the Wumpusはシンプルなプログラムゆえアーキテクチャの境界を作る意味は薄いかもしれないが、この章の目的はアーキテクチャの境界があらゆるところに存在することを示している。
PlayerManagementとMove Managementのように、マイクロサービスとして扱うなら上記のような分け方が必要である。
このようにどこまでを境界として扱うかはコストを評価して完全に実装する必要があるのか、部分的に実装すべきなのか、無視したほうがいいのかを判断しなければならない。そして最初の設計時のみ考慮するのではなく、システムの進化に伴い常に見張る必要がある。そうしなければ1章のような崩壊につながってしまうだろう。

yaGi_04yaGi_04

第26章 メインコンポーネント

Mainコンポーネントは究極的な詳細コンポーネント(クリーンアーキテクチャの概念では最下位レベル)
OS以外はこのコンポーネントに依存しない
このコンポーネントの仕事はシステムの抽象的な部分に制御を渡すこと(依存関係はDIを使用して注入する)
mian関数で処理は定義せず、すべて上位コンポーネントの処理を使用する

Mainはアプリのプラグインとして考える

初期状態や構成を設定して、外部リソースを集め、アプリケーションの上位レベルの方針に制御を渡すプラグインである。

mao12312mao12312

第27章 サービス:あらゆる存在

サービス指向「アーキテクチャ」とマイクロサービス「アーキテクチャ」についての解説。
サービス思考アーキテクチャとマイクロサービスアーキテクチャの違い
https://www.talend.com/jp/resources/microservices-vs-soa/

サービスアーキテクチャ?

サービスを使用することは、その性質上、アーキテクチャである

これは間違いで、システムのアーキテクチャは、上位レベルの方針と下位レベルの詳細を分離し、依存性のルールに従う境界によって定義される。
アプリケーションの振る舞いだけを分離しただけでは、関数呼び出しにしかならない。
プロセスやプラットフォームで機能を分割する大きな利点サービスはサービスであり、アーキテクチャを定義するものではないといこうことだ。
システムに存在するそのほかの多くの関数は、振る舞いを分離(インターフェイス)しているだけであり、アーキテクチャにおいて重要なものではなく、サービスもプロセスやプラットフォームの境界を越える関数呼び出しにすぎない。

サービスのメリット?

システムをサービスに分ける大きなメリットは、サービス同士が強く分離されていることだと言われる

複数のサービスは複数のプロセスで実行されるためお互いの変数にアクセスすることができない。
それによってサービスのインターフェイスが明確になる。
だが正しくない箇所もある。
例えば、サービス間で渡されるデータレコードに新しいフィールドが追加される例を考えてみる。
プロセッサ内やネットワーク上にある共有リソースについては分離されていない。
新しいフィールドを扱うすべてのサービスを変更することになってしまう。
サービスはデータレコードと強く結び付いているということであり、結果として、間接的ではあるが相互に結び付いていることになってしまう。

誤った開発とデプロイの独立

もう一つのメリットは
サービスに分けることができれば専属のチームがサービスを所有・運用することができ、開発とデプロイの独立性は、スケーラブルであると考えられることである。

プロイが可能なサービスが、数十、数百、数千あればエンタープライズ向けのシステムが構築でき運用も楽になると考えられているが、先ほどのデータや振る舞いの結びつきがある限り開発・デプロイ・運用には調整が必要になってくる。

子猫の問題

以前にも出たタクシー配車システムを例に考えてみる。
システムを以下のようになサービスに分解してみた。
TaxiUI サービス :モバイルデバイスを使用してタクシーを発注する顧客を扱う
TaxiFinder サービス :さまざまな TaxiSupplier(タクシー業者)を 調べ、ユーザーの条件に合ったタクシーを決定とデータレコードに保管
TaxiSelector サービス :ユーザーの指定した料金、時間、豪華さなどの条件を受け取り、候補のなかから適切なタクシーを選択
TaxiDispatcher サービス:選択されてたタクシーの発注

ある日、子猫宅配サービスを開始する計画があることが発表された。
以下要件

  • 市内にいくつかの子猫収集地点を設置して、宅配の注文が入ったら、近くにいるタクシーが収集地点から子猫を選び、注文された住所に子猫を届けるのである。
  • 賛同してくれる業者もいればそうでない業者もいる。
  • 乗客のなかにも猫アレルギーの人はいるだろうから、子猫の宅配に使ったタクシーは、その後 3 日間は猫アレルギーの顧客を乗せないようにしたい。

この機能を実装するために、どれだけのサービスを変更 しなければいけないだろうか?
答えは全てである。(サービスはすべて結合されており、独立して開発・デプロイ・保守することはできない)
あらゆるソフトウェアシステムは、サービス指向で あろうとなかろうと、この問題に直面する。
今回のような機能分割しているサービスは、すべての動作に影響を与える新機能の追加に対して非常に弱い。

救世主のオブジェクト

図 27-2を参照
Riders:乗車に固有のロジック
Kittens:子猫の新機能

参考:Factoryとは
https://gimo.jp/glossary/details/factory_method.html

コンポーネントベースのサービス

コンポーネントベースのだからできたのではないので、サービスでも実現可能。
SOLID の原則を使用してサービスを設計し、コンポーネントの構造を与えておけば、サービスに含まれる既存のコンポーネントを変更することなく、新しいコンポーネントを追加
図 27-3 参照(内部コンポーネントの設計があるため新機能を派生クラスとして追加可能)

横断的関心事

我々が学んだのは、アーキテクチャの境界はサービスとサービスの中間に位置するわけではないということだ。アーキテクチャの境界は、サービスを横断することで、コンポーネントに分割しているのである。
横断的関心事に対応するために、サービスは依存性のルールに従った内部コンポーネントのアーキテクチャと一緒に設計する必要がある(図 27-4)。サービスがシステムのアーキテクチャの境界を定義しているのではなく、サービス内部のコンポーネントがそれを定義しているので ある。

yaGi_04yaGi_04

第28章 テスト境界

テストはシステムの一部

システムコンポーネントとしてのテスト

クリーンアーキテクチャーの概念にしたがうと、テストは依存性のルールに従う必要がある

  • テストは具体的な実装でテストするコードに対して依存するため、アーキテクチャの円の最も外側にある
    • システムに含まれるものはテストに依存していない (常にテストの方が依存している)
  • テストも独立してデプロイ可能なコンポーネントにする必要がある

テスト容易性のための設計

  • テストを極端に分離してしまうと、システムの外側にあるものととらえてしまう
    • これは破滅的な見方になり、結果不安定なテストになる
  • システムと強く結合したテストは、システムに合わせて変化する必要がある
    • システムコンポーネントに対する小さな変更であっても、結合した多くのシステムが壊れたり、変更が必要になったりする
    • 共通のシステムコンポーネントを変更すると、何百や何千というテストが壊れる可能性がある(脆弱なテストの問題)

解決策は、「変化しやすいものに依存しない」

テストAPI

この API には、テストからセキュリティ制約を取り除き、コストの高いリソース(データベースなど)をバイパスし、システムをテスト可能な状態にする「スーパーパワー」が必要である
テスト API の目的はアプリケーションの構造からテストの構造を切り離
すことが目的である

構造的結合

構造的結合は、最も強く、最も油断できない、テストの結合の形態
すべてのクラスのテストクラスと、すべてのメソッドのテストメソッドを含んだテストスイートでアプリケーションの構造と深く結び付いている
テスト API の役割は、アプリケーションの構造をテストから隠すこと
このような進化の分離が必要なのは、時間が経つにつれて、テストは具体的かつ個別化する傾向があるから

mao12312mao12312

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

ソフトウェアは消耗しないが、ファームウェアやハードウェアは時代遅れになる。その結果、ソフト ウェアの変更が必要になる。

参考:The Growing Importance of Sustaining Software for the DoD(国防総省のソフトウェアを維持する重要性の高まり)

ハードウェアは時代と共に進化しているがそれと同時にソフトウェアに機能が追加され複雑性も増している。
筆者は上記の言葉に下記を付け加えたいそう。

ソフトウェアは消耗しないが、管理できていないファームウェアやハードウェアの依存関係により、
ソフトウェアが内部から破壊される可能性がある。

ファームウェアは下記のように定義されていることが多いが、筆者は「何に依存しているのか、ハードウェアの進化に合わせてどれだけ変化しにくいか」で決まるらしい。(ハードウェアとやり取りするコード)

●ファームウェアは、ROM、EPROM、フラッシュメモリなどの不揮発性メモリデバイスに 保存されたものである。(https://en.wikipedia.org/wiki/Firmware)
● ファームウェアは、ハードウェアデバイスにプログラムされたソフトウェアプログラムま たは命令セットである。(https://techterms.com/definition/firmware)
● ファームウェアは、ハードウェアに組み込まれたソフトウェアである。(https://www.life wire.com/what-is-firmware-2625881)
● (ファームウェアは)読み取り専用メモリ(ROM)に書き込まれたソフトウェア(あるい はプログラムやデータ)である。(http://www.webopedia.com/TERM/F/firmware.html)

ハードウェアは進化しているのでそれに合わせて組み込みコードを構造化する必要がある。
また、筆者はファームウェアエンジニアが書くべきファームウェアが多すぎると言っている。
コードにSQLを埋め込んだり、組み込みエンジニアではないエンジニアもラットフォームの依存性を広げたりするたびに、実質的にファームウェアを書いている。

ハードウェアと同居することでハードウェアや技術に完全に依存し、ファームウェアになってしまう。
ハードウェアに依存するファームウェアではなく、寿命の長いソフトウェアにする必要があると筆者は行っている。

適性テスト

なぜ組込みソフトウェアはファームウェアになるのだろうか?
そもそもの考え方に原因がありそう。

ソフトウェアを構築する 3 つの活動(Kent Beck )

  1. まずは、動作させる。動作しなければ、仕事にならない。
  2. それから、正しくする。あなたやほかの人たちが理解できるようにコードをリファクタリ
    ングして、ニーズの変化や理解の向上のためにコードを進化させていく。
  3. それから、高速化する。「必要とされる」パフォーマンスのためにコードをリファクタリ
    ングする。

組込みシステムソフトウェアの多くは「動作させる」ことを第一に、「高速化する」という目標ことも考えて書かれていると筆者は言っている。(組み込みだけではなく、多くのアプリケーションでもおこっている)
アプリを動作させることを、私はプログラマの適性テストと呼んでいるが、アプリを動作させることだけに関心を持つプログラマは長期的には不利益になっている。

実際に適正テストを通過したコードをみている(p.244)

マイクロプロセッサのアーキテクチャのことを把握しており、特定のハードウェアに結びついた拡張されたCの構造体を使用している。
別のハードウェア環境に移植する可能性がある限り、このソフトウェアは長寿ではなくなる。
アプリケーションは確かに動作している。エンジニアは適性テストを通過しているが、このアプリケーションは「クリーン組込みアーキテクチャ」になっているとは言えない。

ターゲットハードウェアのボトルネック

組込み開発者は、その他の開発者が扱わない多くの懸念事項がある。
制限されたメモリ空間、リアルタイムの制約と期限、制限された IOなどなど...
だが組み込み開発にも本書の原則は組込みシステムにも適用できる。
組込みの特別な問題にターゲットハードウェアのボトルネックがある。
クリーンアーキテクチャやプラクティスを適応しないと特定のターゲットでしか問題が発生する。

クリーン組込みアーキテクチャはテスト可能な組込みアーキテクチャ

ターゲットハードウェアのボトルネックを解消する為の方法
レイヤー
レイヤーにはいくつかあるが「ソフトウェア」「ファームウェア」「ハードウェア」の3層に分かれており「ハードウェア」が一番下の階層
ハードウェアとそれ以外との分離がされているが、ハードの変化にソフトウェアも影響を受けることが適性テストを通過するとき発生する。理由はすべてのコードからハードウェアの知識の汚染を取り除くものが存在しないからである。
ソフトウェアとファームウェアを混ぜるのはアンチパターンで、コードは変更しにく意図しない結果につながることが多い。

ハードウェアは詳細
ソフトウェアとファームウェアの境界線は、コードとハードウェアの境界線と違い、うまく定義できるものではない。
ソフトウェアとファームウェアの境界は、ハードウェア抽象化レイヤー(HAL)と呼ぶ
HAL はその上に置かれたソフトウェアのために存在する。
たとえば、ファームウェアはバイトやバイトの配列をフラッシュメモリに格納することができる。
一方、アプリケーションは名前と値のペアを何らかのどこかに保存してよき取る必要がある。
この場合にソフトウェアはどこに保存されているか(フラッシュメモリなのかなど)を知る必要はない。
HAL はアプリケーションが必要とするサービスであると考えられる、ソフトウェアから隠しておくべき詳細である。

ハードウェアの詳細はHALのユーザーに明らかにしない

クリーン組込みアーキテクチャのソフトウェアは、ターゲットハードウェアをオフにしたテストが可能

プロセッサは詳細
組込みアプリケーションが特殊なツールチェーンを使用している場合、ヘッダーファイルが提供されていることがある。
このプロセッサのレジスタ、IO ポートなど他のプロセッサー機能に直接アクセスするような機能がある。
簡単にアクセスできるようになって便利だが、別のプロセッサ用にコンパイルできないし、同じプロセッサでも別コンパイラで動かない可能性がある。
ヘッダーファイルの参考例p250

こうした場合オフターゲットでコードをテストしようとすると、整数が間違ったサイズになってしまう。
また、別プロセッサに移植する際にヘッダーファイルを知るファイルを制限していないため移植が困難になる。

この解決方法はstdint.h(stdint.hはC言語の標準ヘッダ)に書き込むことである。
組込みソフトウェアやファームウェアから stdint.h を使用すると、コードがクリーンになりポータビリティを確保できる。
同様にマイクロコン トローラーの周辺機器にアクセスできる C言語の拡張を使用している場合は、デバイスアクセスレジスタを限られた場所から使用して、完全にファームウェアに閉じ込めておくなどしてレジスタを知っているのはファームウェ アだけの状況をつくる。

OSは詳細
リアルタイム OS(RTOS)や組込み版の LinuxやWindowsを使用する場合を考える。
純粋な組込みシステムでは、HALだけでコードを動作環境から切り離すことができるが、OSを経由して動作環境のサービスにアクセスする。
OSを直接使用すると開発会社の影響を直接受けたりして、OSの変更に合わせて多くのコードを修正する必要がある。
クリーン組込みアーキテクチャでは、「OS抽象化レイヤー(OSAL)」でソフトウェアとOSを分離する。このレイヤは関数の名前を変更、関数をまとめてラップなどすることがある。ほかのOSに移植する場合は新たにOSALを作成するだけで良い。

まとめ

組込みソフトウェアでもアプリケーションのソフトウェアと同じような手法を使って依存性の排除を行うことができる。ファームウェアが多いプロダクトは長期的に安定しなくなる。
クリーン組込みアーキテクチャは、プロダクトの長期的な健康のためである。

yaGi_04yaGi_04

第30章 データベースは詳細

データベースはエンティティではない

データベースはあくまでデータへのアクセスをするためのソフトウェアに過ぎない

アーキテクチャーで重要なのは、データ構造

どのデータベースを使用してようが、関係ない
例えばRDBSを使用したとして、データをDBの行やテーブルオブジェクトとして受け渡せる。しかし、アーキテクチャ的にはユースケースや、ビジネスルール、UIがRDBSに縛られてしまう

データベースはディスクとRAMとの間でデータを移動する仕組みにすぎない
アーキテクチャ的には磁気ディスクにどのような形式でデータが保存されているか気にするべきではない

パフォーマンスはどうなの?

データストアのパフォーマンスは下位レベルでの関心ごと
下位レベルのデータアクセスの仕組みを使うことになるため
上位概念のビジネスルールとは切り離して考えられる

yaGi_04yaGi_04

第31章 ウェブは詳細

振り子の問題

  • すべての計算パワーを中央サーバーにまとめるか、逆にすべての計算パワーを端末に分散させるかの間を行ったり来たりしている

アーキテクチャ観点から考えると、振り子の問題は短期的でありビジネスルールからは切り離しておきたい
ウェブはGUIであり、ビジネスロジックの中心ではなく詳細にあたる(ウェブは入出力デバイスの一種であるとう考え方)

マーケティングの奴らはいつも、ビジネスルールと UI を一体化したがるのだから

yaGi_04yaGi_04

第32章 フレームワークは詳細

フレームワークはあなたの課題を解決するためではなく、彼ら(フレームワークの製作者)の課題を解決するためにフレームワークをつくっている

  • 彼らの課題と、あなたの課題が重なる部分が多い場合フレームワークは便利なものになる

フレームワークはあなたのシステムのコアに結合しようとしてくる(フレームワークの製作者はあなたのプロダクトの課題を解決しようとしているわけではない)
ビジネスオブジェクトに組み込むと、ずっとフレームワークの制限を受けることになる

  • プロダクトが成長してくるとフレームワークには手に負えない機能がでる
  • フレームワークのバージョンアップで使用していた便利な機能が廃止されたり、仕様変更されてしまったり
  • 優れたフレームワークを見つけたときに乗り換えたくなる可能性

解決策はフレームワークをプラグインとして使用すること(プラグインとして使用する)

  • フレームワークの中でもc++のSTLやjavaの標準ライブラリはほぼ間違いなく使用することになる
mao12312mao12312

第33章 事例:動画販売サイト

プロダクト

アクター
法人向け:ストリーミング視聴限定のライセンス販売
個人向け:ダウンロードとストリーミングの2種類の販売
動画作成者:動画や練習問題などの添付資料の提供
管理者:新しい動画シリーズを追加したり、動画シリーズに動画を追加したり、各種ライセンス価格を設定

上記アクターがシステムにおける変更の主な要因になる。
図 33-1 のユースケースを見ると「視聴者としてカタログを閲覧する」と「購入者としてカタログを閲覧する」があり、抽象化すると「カタログを閲覧する」とし、どちらも継承できる。(本書では抽象ユースケースと言っている)

コンポーネントアーキテクチャ

アクターとユースケースを考えることでコンポーネントアーキテクチャを検討できるようになった。(図 33-2参照)
図中の二重線は、アーキテクチャの境界を表している。
ここで重要なことは、個別にデリバリー可能なようにもできるし、もう少しな大きな単位でデリバリーできる選択肢を残していることが重要。

依存性管理

図 33-2 の制御の流れは、右から左へ進むようになっている。
しかし、全てが右から左になっているわけではない。
上位レベルの方針を含むコンポーネントに向かうように、ほとんどが左から右になっている。
使用の関係(通常の矢印)は制御の流れと同じ向きなのに対して、継承の関係(白抜きの矢印)は制御の流れと逆向きであることもわかる(オープンクローズドの原則)

yaGi_04yaGi_04

第34章 書き残したこと

レイヤーによるパッケージング

技術的な役割にもとづいて分割する、水平方向のレイヤーアーキテクチャ

  • ウェブ用のコードレイヤー
  • ビジネス用のレイヤー
  • 永続化のレイヤー

厳格なレイヤードアーキテクチャでは、隣接する下位レイヤーだけ依存すべきだとされている

とりあえず動くものを複雑になりすぎず、手早くつくることに優れた方法
ソフトウェアの規模が大きくなり複雑化すると、コードを 3つの巨大なバケツに分けるだけでは手に負えなくなってくる
ビジネスドメインに関して何も叫ばない

機能によるパッケージング

垂直方向のレイヤーアーキテクチャ

  • 関連する機能
  • ドメインの概念、(ドメイン駆動設計の用語で言うなら)
  • 集約ルート

にもとづいて分割する
先ほどの水平方向のレイヤーアーキテクチャとクラスやインターフェースは同じだが、3つのパッケージに分けるのではなく、1つのパッケージにまとめる

トップレベルのコードの構成がビジネスドメインについて叫ぶようになった
あちこちのパッケージに散らばっているのではなく、1 つのパッケージにまとまっているから修正すべきコードを把握しやすい

ポートとアダプター

ビジネス(ドメイン)に関するコードを技術的な実装(フレームワークやデータベース)から切り離して独立させるため、「内側(ドメイン)」と「外側(インフラストラクチャ)」に分ける構成
大原則は「外側が内側に依存するようにせよ。その逆は許さない」

コンポーネントによるパッケージング

レイヤードアーキテクチャの目的は、同じ種類の機能を持つコードを切り離すこと
依存性の矢印はすべて下向きではあるが、ユースケースによっては Controller がService を飛び越えRepositoryを操作するじっそうになってしまう場合がある
このような構成は緩いレイヤードアーキテクチャと呼ぶことが多い。

コンポーネントによるパッケージングは粒度の粗いコンポーネントに関連するすべての責務をひとつの パッケージにまとめることを目指すというものだ
「ビジネスロジック」と永続化コードをひとつにまとめて「コンポーネント」にする手法だ

もしドメインに関するコードを書きたいのなら、ドメインのComponent だけを見ればいい

悪魔は実装の詳細に宿る

Java のような言語で public アクセス修飾子を気軽に使いすぎることで、プログラミング言語が用意してくれているカプセル化に仕組みを活用できなくなっている
具象実装クラスを直接インスタンス化するような、アーキテクチャスタイルに違反したコードを防ぐ手段が一切なくなってしまう

組織化かカプセル化か

もし Java アプリケーションですべての型を public にしたら、パッケージは単なる組織化(フォルダのようなグループ化)の手段でしかなくなり、もはやカプセル化はできなくなる
(図34-7, 34-8で説明)
アーキテクチャの原則を守らせるのはコンパイラ任せにすることをお勧めする。メンバーの自制心に頼ったり、コンパイル後にツールでチェックしたりするより、そのほうがずっといい。

そのほかの分割方法

コードをそれぞれ別のソースコードツリーに分割する方法
理想を言えば、アプリケーションのコンポーネントごとに、別々のソースコードツリーを持つようにしておきたい
しかし、あくまでも理想的な解決策であって、あまり現実的ではない

  • パフォーマンスが問題になるし
  • 複雑度も増すし
  • 保守も面倒

まとめ

いくらうまい設計をしても、その実装方法の複雑さを考慮しなければ、あっという間に設計が崩れてしまう
チームの規模やメンバーのスキルやソリューションの複雑さ、そして時間と予算の制約などを考慮しよう