【アーキテクチャパターン】POSA Vol.1のLayers(レイヤ)

Pattern-Oriented Software Architecture Vol.1に載っているアーキテクチャパターンの1つ,Layersをまとめる.

Layersアーキテクチャパターンは,アプリケーションを特定の抽象レベルに属するサブタスクのグループ群に分割するという構造化に有効である.

概念的に異なるものを分離して実装することの利点としてはざっくり下記がある.

  • チーム開発が容易になる
  • 増分的にコードを書いてテストできる
  • 個々のレイヤ単位で容易に交換できる
  • 新言語や新アルゴリズムなどの優れた技術を容易に取り込める

前提

分割が必要な大規模システム

課題

抽象度の高い要素は,それよりも抽象度の低い要素が提供する操作だけを使用する.

以下のフォースのバランスを取る必要がある

  • ソースコードを変更してもシステムには影響を及ぼすべきではない
  • インターフェースの変更があってはならない.
  • システムの部分要素は交換可能であるべきである.
  • 現在開発中のシステムのために設計された下位レベルの要素が,将来的に別システムの開発で利用される可能性がある.
  • 可読性と保守性を向上させるために,システム上の類似した責務をグループ化すべきである.しかし,各グループ内のコンポーネントは高い凝集性を持つこと.
  • コンポーネント粒度に「標準」が存在しない.
  • 複合コンポーネントに対して,さらに分割を行う必要がある.
  • コンポーネント境界をはさんでデータのやり取りが行われると,パフォーマンスの低下を招くことがある.
  • システムは,プログラマのチームによって実装されるものである.そのため各メンバの作業はきっちり線引きされていなくてはいけない.

解決策

システムを適当数のレイヤで構造化し,それを互いに積み重ねていけば良い.
もっとも中小レベルの低いレイヤをレイヤ1とする.
この最下位レベルのレイヤがシステムの基盤となる.

レイヤJの役割は上位のレイヤJ+1のリクエストを下位のレイヤJ-1に変換するだけか,さらに独自の機能をもつのか,ということなどを決定しなければならない.
この解決策の本質は,1つのレイヤにおいては,その構成要素であるコンポーネントが同一抽象レベルで作業を行うということである.

このように構造化されたシステムをレイヤシステムという.この構造をレイヤアーキテクチャと呼ぶ.

静的側面

f:id:yusuke_ujitoko:20161115221745p:plain

Layersパターンの構造上の特徴は,「レイヤJがレイヤJ-1のサービスだけを利用する」ということである. 2つのレイヤ間には,それ以外の依存関係はない. 各レイヤは,それよりも下位に属するレイヤすべてを遮蔽し,そのレイヤよりも下位のレイヤから直接アクセスできないようにする.

f:id:yusuke_ujitoko:20161115221951p:plain

個々のレイヤを詳しく調べると,複数コンポーネントから構成されることがわかる.

動的側面

シナリオ1

クライアントがレイヤNにリクエストを出す. レイヤNは,自分自身でそのリクエストを実行できないので,次のレイヤN-1を呼び出して,サブタスクを支援してもらう. ... 最終的にレイヤ1レベルのサービスが実施される. このリクエストへのレスポンスが,レイヤ1からレイヤNに到達するまで繰り返し伝達していく.

このような通信をトップダウン通信という. この通信方法の特徴は,下位レイヤは上位レイヤのリクエストを,複数のリクエストに分解してさらに下位のレイヤに引き渡すことである.

シナリオ2

ボトムアップ通信. 動作はレイア1から開始される. たとえばデバイスドライバが入力を検知したときに,ボトムアップ通信が行われる.

トップダウンの情報や制御フローが「リクエスト」と呼ばれるのに対して,ボトムアップの呼び出しは「通知」という用語で呼ばれる. 複数のボトムアップ通知は,上位レイヤで一つの通知にまとめられたり,1対1の関係を維持したりする.

シナリオ3

リクエストがすべてのレイヤを通過するのではなく,その一部のレイヤだけで処理されるというものである. 最上位レイヤNのリクエストがレイヤN-1に送られるのはレイヤN-1でそのリクエストを受け付けることができる場合に限られる.

キャッシュが働く場合など.

シナリオ4

シナリオ3と似ている. レイヤ1でイベントが検知されるがレイヤ3で伝達が中止される. たとえば,通信プロトコルでは,気の短いクライアントから少し前に送信されたリクエストが再送信される場合がある. その場合,再送信リクエストに対して,それ以上のアクションを起こさないようにすることができる.

シナリオ5

2つのNレイヤスタックをもつものである. このシナリオは,「プロトコルスタック」というよく知られている通信プロトコルの振る舞いを示している.

実装

以下のステップは,Layersアーキテクチャを定義するための「段階的詳細化」(step-wise refinement)のアプローチを記述したもの.

  1. タスクをグループ化してレイヤとするための抽象の機銃を定義する
  2. 抽象の基準に従って,抽象レベルの数を決定する
    • レイヤの数が多すぎるとオーバーヘッドがあり,少なすぎると貧弱な構造になりレイヤ化の意味が失われる
  3. レイヤに名前をつけ,タスクを割り振る.
  4. サービスを仕様化する
    • 上位レイヤのサービス数が,下位レイヤのサービスよりも多いことが望ましい
  5. レイヤ化を洗練する.
  6. 各レイヤをインターフェース化する
  7. 個々のレイヤを構造化する
  8. 隣接するレイヤ間の通信を仕様化する
  9. 隣接するレイヤの結合を解除する
    • 上位のレイヤは下位のレイヤを「知っているが」,下位のレイヤはその利用者を「知る」ことはない.すなわちレイヤ館には一方向の結合だけがある.
    • ボトムアップ通信では,コールバックを利用する.この場合には上位レイヤが下位レイヤにコールバック関数を登録する.
  10. 例外処理戦略を設計する.

バリエーション

リラックスレイヤシステム(Relaxed Layered System)

リラックスレイヤシステム(Relaxed Layered System)では,各レイヤがそれよりも下位にあるレイヤすべてのサービスを使用することできる. この場合,柔軟性とパフォーマンスが向上するが,その代わりに保守性は低下する.

継承によるレイヤ化(Layering Through Inheriance)

このバリエーションはオブジェクト指向システムで採用されている. 下位レイヤが基底クラスとして実装されており,この下位レイヤにサービスをリクエストする上位レイヤは,下位レイヤの実装を継承している. そのため基底クラスのサービスに対してもリクエストを発行することができる. この場合,上位レイヤが下位レイヤのサービスを変更できるが,一方で継承関係が上位レイヤを下位レイヤに密接に結びつけてしまう.

適用例

仮想マシン(Virtual Machine)

1つのレイヤを仮想マシンとして設計すると,そのレイヤの上位に位置するレイヤと,抽象度の低い実装詳細やさまざまなハードウェアを分離できる.

API

下位レイヤをカプセル化する役割をもつ.

情報システム

ビジネス・ソフトウェアにおける情報システムでは,複数レイヤからなるアーキテクチャが採用されやすい.

  • プレゼンテーション
  • アプリケーションロジック
  • ドメインレイヤ
  • データベース

結論

Layersパターンの利点

  • レイヤの再利用
  • 標準化の支援
  • 依存性の局所化
  • 交換容易性

Layersパターンの欠点

  • 振る舞いの変更がカスケードする
  • 低い効率性
  • 不必要な作業
  • レイヤの粒度を決定することが困難である

yusuke-ujitoko.hatenablog.com