結合度
モジュールやコンポーネント間の結合の度合いは、
強度(Strength)
距離(Distance)
変動性(Volatility)
の3つの切り口による評価がある。これらは直交した評価軸ではない。
強度
昔から出てくる分類
内容結合(Content Coupling): 別のモジュールの内部実装を参照する
共通結合(Common Coupling): グローバル変数を共有する
外部結合(External Coupling): 標準化されたインタフェースをもつグローバルな状態を共有する
制御結合(Control Coupling): 制御のためのフラグなどを渡して相手モジュールの挙動を変える
スタンプ結合(Stamp Coupling): データの塊(オブジェクトやレコード)を共有する
データ結合(Data Coupling): プリミティブなデータを受け渡す
距離
距離で結合を評価すると、
メソッド間の結びつき
クラス間の結びつき
コンポーネント間の結びつき
サービス間の結びつき
システム間の結びつき
などが存在する。下にいくほど距離が遠く、変更時の調整が大変になる
変動性
モジュール/コンポーネントが一緒に変更される理由で分類したもの。
Operational(運用上の結合度): コンシューマはプロバイダなしで実行できない。
Development(開発上の結合度): プロデューサやコンシューマの変更には調整が必要だ。
Semantic(意味的結合度): コンセプトが共有されているので、一緒に変更しなければならない。
後述
Functional(機能結合度): 責務が共有されているので一緒に変更しなければならない。
例えば、ログ出力が共通していて、ログフォーマットを変更しなきゃいけないときには、両方のコードを修正する必要がある。
Incidental(偶発的結合度): 特に理由はないけど、一緒に変更しなければならない。
例えば、サービスAがあるファイルに書き込み、サービスBがそのファイルを読み込むようになっていた。どちらもハードコードされていて、サービスAがパスを変更したらサービスBがエラーになった。それで初めて2つのサービスの関連性に気付いた。というようなケース。
例1: メールシステムがSMTPでメールを送る
Operational: 強い。SMTPは同期的である。
Development: 弱い。SMTPは枯れた標準技術のため、変わらない。
Semantic: とても強い。SMTPはエンティティ、属性、許容される値が定義されている。
Functional: とても弱い。送信者とMTAは異なるビジネスロジックを持っていてい、共通してるのはネットワーク接続を使うという点のみ。
例2: RDBMSを直接参照するパターン
Operational: とても強い。サーバの可用性に依存する。トポロジーとフェイルオーバー戦略に注意しなければならない。
Development: とても強い。スキーマ、サーバのバージョン、プロトコルのバージョンに依存する。
Semantic: とても強い。テーブル、カラム、ジョインは両者が知ってなくてはならない。
Functional: 弱い。共通するコードは無い。
例3: RESTful API
Operational: 強い。RDBMSほどではないが、サーバの可用性にやはり依存する。
Development: 強い。データフォーマットの変更からは切り離される。エンコードの方式が標準化されるので、結合度はRDBMSよりも減る。
Semantic: とても強い。RESTのリソースと送信するデータのエンティティは整合性が取れていなければならない
Functional: 弱い。異なる言語、技術、デザインパターンが使える
例4: メッセージング
メッセージングブローカーを介して、データを受け渡す。
Operational: とても弱い。相手先が死んでいても継続できる。
Development: 弱い。スキーマ変更から切り離される。
Semantic: 強い。が、これまでほどではない。ブローカでマッピングするようにできる。
Functional: 中くらい。すべてのコンポーネントは同じメッセージ技術を共有しなければならない。より
意味的結合
意味的に結合したサービスは、あるエンティティを共有し、それがコアコンセプトになる。
例えばEコマースにおけるSKUを考える。
SKUは、価格、コスト、棚番号、商品説明、有効期限などの属性をもつ。
SKUに「プライスポイント」という概念を新たに導入する。仕入原価が少しずつ異なる商品でも、一律の価格を設定するもので、値付け作業の負荷を下げると同時に、消費者の購入時の比較検討負荷を下げる目論見がある。
SKUにプライスポイント属性を追加すると、SKUを受け取るすべてのシステムでプライスポイントが設定されているものはそちらを参照するように変更しなければいけなくなる。
これが意味的結合である。
https://gyazo.com/cc8d79532b3c5568dc1636f6eada93e8
これを解消するためには、SKUをコアコンセプトとせず、SKU自体を分解して各システムが必要な属性だけをとり扱うことである。
下流のシステムを全体の概念に結びつけるべからず。
Nygardは特に、この意味的結合を弱めるべし、を強調している。
ソフトウェアにおける直交性
関心の分離
モジュールやコンポーネント、それ自体が高い凝集性をもつ
モジュールやコンポーネント間は、結合度を低くする。
モジュール間の機能的な重なりは小さくする。
情報や判断はモジュール、コンポーネントの中に隠す
辛み = 強度 * 変動性 * 距離
いずれか2変数が大きくても、残り1変数を限りなく小さくできるのであれば、結合に伴う辛みは減る。
「集約」は 結合強度が高く変動性も高いが、同じモジュールに操作を文字通り集約するので結合距離は小さい。
0 ≒ High(強度) * High(変動性) * Low(距離=メソッド間結合)
「境界づけられたコンテキスト」は結合強度が高く変動性も高いが、関連のあるコンポーネントをまとめる境界を作るので、結合距離を小さくする
「サポートサブドメイン」は枯れたソフトウェア領域なので変動性が低い
0 ≒ High(強度) * Low(変動性) * High(距離=システム間結合)
「腐敗防止層」
0 ≒ Low(強度) * High(変動性) * High(距離=サービス間結合)