背景およびまえがき
サービスアーキテクチャの思想を学んでいく中で、DDD本で初歩を学んでいた改めてコンテキスト境界といったものに深い理解を得たいと感じた。それが結果的にどのような観点でまとめたいのか?というこのコンポーネントのまとめ方原則の理解を促進してくれたのだ。
そこで初めてクリーンアーキテクチャ本を読んだ際には「???」だったこのコンポーネントの凝集性原則3つをそれぞれの特性を交えながら考えていきたいと思う。
概要
クラスの凝集度、つまりカプセル化の考え方と一緒で1段階マクロなまとめ方がある。
どんなにクラスという単位でカプセル化を頑張っていたとしても、
このマクロレベルからのまとめ方がしょ~もない設計をしていては、アーキテクチャの保守性は決して改善はされない。
よく書籍では
・再利用・リリース等価原則
・全再利用原則
・閉鎖性共通原則
の順番で記載されているが、私の記事ではあえてこの順番を入れ替えて記述していくことにする。
その理由は、読んでいただければよーく理解できるであろう。
前提
まずは0からアーキテクチャを考えているということを想定して考えてみよう。
そして最初から将来のメンテコストを抑えられるために、アーキテクチャの保守性に初期投資を許されている状況下にあるとする。
さらに、ここでは【保守性】と【再利用性】は明確に異なる理由として扱うことにする。
一昔前では、保守性の副特性として 再利用性があったようだが、
マイクロサービスアーキテクチャの思想、コンテキスト境界に関する記述を見ている限り、
これが副特性として扱われるのは、違和感しかないためである。
なのでここでは保守性はまだ対象に入る変更の頻度などが不確実であり、その頻度なども読めない状況下でのメンテナンスコストを抑えたいという特性とする。
そして、再利用性はメンテナンスを繰り返していく中である程度時間が経過するごとに成熟した状態で、
もうめったに変更も入ることがない非常に安定したものを他のプロジェクトなどでもリサイクルすることで、車輪再発明するという無駄なコストを抑えたいという特性とする。
両者に共通するのは、アーキテクトがステークホルダーであることだ。
このアーキテクトの立場の心情を想像しながら考えてみると考察がしやすいであろう。
その人がどの観点でのラクさを取りたいのか? を意識して以下の原則と向き合うとわかりやすいと個人的には思う。
閉鎖性共通原則
この原則の考え方は、アジャイルソフトウェア開発奥義で書かれてるものだと以下の通りだ。
--引用--
コンポーネントに含まれるクラスは、すべて同じ種類の変更に対して閉じているべきである。
その変更はそのコンポーネント内のすべてのクラスに影響を及ぼすが、他のコンポーネントには影響しない。
--引用終わり--
これはクラス設計でいうところの、単一責務に相当している。
そのコンポーネント内のクラス群は互いに密になっているということだ。
なぜこの原則から目指すのか?
0から何かを作成している上記の前提状況下では、まずはこの原則を満たすことを目指そう。
他2つの再利用性原則はここではまだ考えない。
なぜなら再利用性を最初から追及していた場合にこんなリスクが想定される。
下図のように再利用したい【対象コンポーネント】を他の3つのプロジェクトで使われてしまってるとしよう。

もしも対象コンポーネントに対して、外部からの要求が入った場合、容易には変更できなくなる。
他の3つのプロジェクトに対して影響を与えることは避けられないし、変更加えて各プロジェクトで再テストしたりなど
そのコストは予測ができないが、高くつくことは容易に想像がつく。
かといって、古いverのままそのコンポーネントを使うのか? それこそ使われなくなっていってしまう。
以上のようなリスクを避けるために最初から効率性を求めずに、
まずはどんな変更頻度でどのような変更が入るのか読めないから、変更しやすい理由という観点でのまとめ方であるこの原則をまずは目指すわけである。
単一責務と開閉原則の復習
そのクラスが担うことはたった1つである。ゆえにそのクラスが変更される理由も1つである。
なぜなら1つの変更は、密にまとまった1つのカプセルを中心に考えられるように設計されてると
変更メンテ時にまずカプセルの特定がしやすい。(SRPの特徴)
特定が容易なうえで、そのカプセルに変更という外部からの衝撃を加えた際に、
どのように結合した他のカプセルに影響が波紋するのか?を分析しやすいこと。(OCPの特徴)
そしてそれらの変更にかかるコストが安いこと。
これの考え方のコンポーネント版が、この閉鎖性共通原則である。
これはリンク先の単一責務をみてほしいのだが、わたしは以下のようにこの閉鎖性共通原則を捉えている。
・利用者姓
・利用者名
など密接に関係しあった属性の集合に対して【たった1つ】の抽象的名前として「利用者の氏名」という名前を付けられる状態がデータの凝集という観点での単一責務の1つの指標であると考えている。
この属性と 属性の集合からなるクラスの関係性を クラスとコンポーネントの関係性に照らしあわすのだ。
・氏名
・性別
・生年月日
・電話番号
という値オブジェクト的立ち位置のちっさいクラスと それらを属性に持つ主要概念のクラス
それらをまとめたグループに【たった1つ】の名前を付けることができるのか?(図を参照)
もしも複数の名前を付けられるような状態では、そのコンポーネントには複数の責務が混在していることになる。
ループバックチェックで唯一の責務かチェックする
コンポーネントに複数の責務が混在することを予防するためにもわたしはループバックチェックをお勧めしている。
どういうことかというと、上図のように関連が強いクラスを密集させたらそこに対してどんな名前を付けたくなるか?を複数人でチェックするのだ。
この時人によって付ける名前の意図がバラバラであったら、それは危険信号だ。
唯一1つにだれもが名前を付けられるような状態が、そのコンポーネントが1つの責務になってる定性的指標だ。
次にコンポーネント名前から中身にどんなクラスがあるかなっていうチェックもする。
これによってコンポーネントの担う責務が何なのか?という意図がより明確な名前の洗練にもなるし、
無関係な要素が混在してしまうことで、コンポーネントの担う責務が複数になってしまうことを防ぐのだ。
関係性が密なものはまとめる
別の例で考えてみよう。
UMLのモデリングでは関係性が密な結合部分実線で、ある特定の処理の引数などで使われるような弱い結合は点線で表すことになっている。
関係性が密というのは、属性として相手をもつのか? で決まってくる。
たとえば下図のようなコンポジット(集約の特殊版)関係や集約などである。
直感的に考えてもこの強い結合部分を下図に示すように分けるって発想を最初からやるってことはしないだろう。
注文予約に関する変更は当然、注文予約クラスだって注文予約明細クラスだって同じタイミングで変更されるって考えるのが妥当であるからだ。

だって、もしも明細に変更が起きるようなことがあればそれはダイレクトに注文予約の方にまで影響が波紋する。
最悪、注文予約を参照する他のコンポーネントにまで影響を及ぼしかねない。
よって、なにか特殊な理由がない限り、開発初期の方で全閉鎖性原則を守りたい時には、
実線の密結合なところは1つのコンポーネントいまとめることをまずは目指した方がいい。
テストの観点
これは常々イベントでも言っていることだが、一度引いたコンテキスト境界は絶対的に正しいと思ってはいけない。
詳しくはコンテキスト境界の記事で改めて書くが、
1つのコンポーネントにまとめたら常時、変更が入った際の様子をモニタリングできるようにしておくことが大事である。
もしかしたら下図のように、変更時に修正が入らなくてはいけないクラス群と、そうでないクラス群とが発生しうるからだ。(緑が同時に修正入った領域で、その際に修正されなかった部分が赤の領域)
変更入る時のモニタリング結果によって、
上図のように同じ変更理由にも関わらず、なぜか何度も修正はいる部分とそうでない部分とが起こった場合、
赤い部分を別のコンポーネントに分けるという方法を検討した方がいい。
そうすることによって、本来変えてはいけない赤い部分を間違って修正されてしなうリスクを抑えることができる。