SA.01.1 アーキテクチャの設計原則と主要パターン (重要度: 4)

アーキテクチャの設計原則

システムの配置

集約型の配置では、すべてのリソースや機能が中央集権的に管理される単一の場所に配置されます。これは、リソースの効率的な利用や統制の容易さを提供しますが、障害や障害のリスクを高める可能性があります。また、単一の場所での処理や集中型の制約により、スケーラビリティや可用性の問題が発生する可能性もあります。

一方、分散型の配置では、複数のリソースや機能が分散して配置されます。これにより、負荷の分散、冗長性の向上、スケーラビリティの向上、および耐障害性の向上などの利点があります。ただし、分散型システムの管理と同期化は複雑であり、リソースの利用率を最適化する必要があります。

どちらのアプローチが最適かは、システムの要件、目標、負荷パターンなどによって異なります。集約型は単純で効率的ですが、障害の危険性があります。一方、分散型は柔軟で耐障害性が高いですが、管理と同期化が困難な場合があります。

モノリシック/分散

システムの分割方法

layer構造は、「論理階層構造」とも訳され、システムの機能を論理的な役割で分割する方法です。各layerは特定の責任を持ち、上位のlayerは下位のlayerを利用してサービスを提供します。典型的な例は、アプリケーション層、ビジネスロジック層、データアクセス層などの3つのlayerです。このアプローチでは、各レイヤーは独立して開発および保守され、変更が他のレイヤーに影響を及ぼすことは少ない傾向があります。

一方、tier構造は、「物理階層構造」と訳され、システムのコンポーネントを物理的な役割で分割する方法です。各tierは独立して実行され、通信を介してやり取りします。代表的な例は、クライアント層、アプリケーションサーバ層、データベースサーバ層などの3つのタイアです。このアプローチでは、各タイアは独立してスケールアウトや保守が可能であり、性能や可用性の向上が期待できます。

どちらのアプローチが最適かは、システムの要件、目標、アーキテクチャ上の制約などに依存します。レイヤー構造は論理的な組織であり、重要な機能の切り分けや保守の容易さに適しています。ティア構造は物理的な組織であり、性能やスケーラビリティの向上に適しています。

サブシステム間・コンポーネント間の機能制御・連携集約

システムやアプリケーション内の機能制御や連携を実現するための方式には、以下のようなものがあります。

  1. モノリシックアーキテクチャ:
  • システム内の機能は単一のモノリシックなコードベースに統合されています。
  • 機能の制御や連携は、直接的な関数呼び出しやデータ共有によって実現されます。
  • 大規模かつ複雑なシステムでは保守性やスケーラビリティに問題が生じる場合があります。
  1. レイヤードアーキテクチャ:
  • システムの機能は論理的なレイヤーに分割されます(プレゼンテーション層、ビジネスロジック層、データアクセス層など)。
  • 各レイヤーは自身の責任範囲内でのみ処理を行い、上位のレイヤーを利用して機能の制御や連携を行います。
  • レイヤー間の通信はインターフェースやAPIなどを介して行われます。
  1. マイクロサービスアーキテクチャ:
  • システムは独立した小さなマイクロサービスに分割されます。各サービスは単体で実行可能であり、特定のビジネス機能を責任として持ちます。
  • サービス間の機能制御や連携は、通信プロトコル(たとえばREST API)やメッセージキュー、イベントバスを介して行われます。
  • マイクロサービスアーキテクチャは、保守性、拡張性、スケーラビリティなどの利点がありますが、適切なグランularityや運用管理が課題となる場合もあります。

これらの方式は、システムの特性や要件に応じて選択されます。機能制御や連携を実現するための最適なアーキテクチャは、開発者やアーキテクトによって注意深く検討されるべきです。

業務とデータの関係, ノード間のデータ共有/非共有

システムやアプリケーション内の機能制御や連携を実現するための方式には、以下のようなものがあります。

  1. モノリシックアーキテクチャ:
  • システム内の機能は単一のモノリシックなコードベースに統合されています。
  • 機能の制御や連携は、直接的な関数呼び出しやデータ共有によって実現されます。
  • 大規模かつ複雑なシステムでは保守性やスケーラビリティに問題が生じる場合があります。
  1. レイヤードアーキテクチャ:
  • システムの機能は論理的なレイヤーに分割されます(プレゼンテーション層、ビジネスロジック層、データアクセス層など)。
  • 各レイヤーは自身の責任範囲内でのみ処理を行い、上位のレイヤーを利用して機能の制御や連携を行います。
  • レイヤー間の通信はインターフェースやAPIなどを介して行われます。
  1. マイクロサービスアーキテクチャ:
  • システムは独立した小さなマイクロサービスに分割されます。各サービスは単体で実行可能であり、特定のビジネス機能を責任として持ちます。
  • サービス間の機能制御や連携は、通信プロトコル(たとえばREST API)やメッセージキュー、イベントバスを介して行われます。
  • マイクロサービスアーキテクチャは、保守性、拡張性、スケーラビリティなどの利点がありますが、適切なグランularityや運用管理が課題となる場合もあります。

サブシステム呼び出しやデータ共有のタイミング

同期通信は呼び出し側が呼び出し(リクエスト)に対する呼び出し元からの応答(レスポンス)を待機する通信手法のことで、非同期通信は呼び出しと応答との間にタイムラグを許容する通信手法のことです。

同期通信はデータ同期、デッドロックの回避、競合状態、デバッグの効率などの性能面や設計、実装などに優れている一方、非同期通信はスケーラビリティやタイムアウト時などの信頼性の面で優れています。

標準的には同期通信を選択し、非同期通信は状況に応じて選択することが一般的です。

サブシステム間・コンポーネント間の機能制御・連携集約に関する具体的なアーキテクチャパターン

パイプライン (パイプ/フィルター)

パイプラインアーキテクチャとは、フィルターとパイプの2種類からなるアーキテクチャのことです。

フィルターは一般的にステートレスな要素で、以下の4種類によって構成されます。

  • プロデューサー
    処理の開始点であり、入力を持たない出力のみのフィルターです。ソースとも呼ばれます。
  • トランスフォーマー
    入力データの一部分、あるいは全てに変換をした結果を出力するフィルターです。
  • テスター
    入力データを1つ以上の基準でテストし、その結果を出力するフィルターです。
  • コンシューマー
    パイプラインの終了地点で、この出力をデータベースやユーザーインターフェースに反映します。

パイプはフィルター間の通信チャンネルを形成するもので、一般的に一方向かつPoint to Point(送信されたデータは単一の受信先に送られる)です。

パイプラインアーキテクチャはフィルターをパイプで接続するアーキテクチャであり、そのため主に一方通行の処理の際に用いられます。

コーディネーション, オーケストレーション

コーディネーション/オーケストレーションとは、中央のコーディネーター/オーケストレーターという調停者(メディエーター)がモジュールやコンポーネントのトランザクションのフローを制御するシステムのことです。

イベント駆動, コレオグラフィ

イベント駆動アーキテクチャは、分散非同期型のアーキテクチャです。

ほとんどのアーキテクチャは、リクエストベースモデルというモデルを採用しています。このモデルでは、リクエストはリクエストオーケストレーターに一度送られます。その後、リクエストオーケストレーターが様々なリクエストプロセッサーにこれらのリクエストを割り振ります。しかし、イベントベースモデルというモデルでは特定の状況(イベント)に基づいてアクションが行われます。

コレオグラフィとは、複数のモジュール/コンポーネントが中央の指示なしに協働することを指します。コレオグラフィには前述したコーディネーション/オーケストレーションのような中央集権的な調停者はいないので、各サービスが必要に応じて他のサービスを呼び出します。これはブローカー型のイベント駆動アーキテクチャと同様の通信スタイルです。

これらの中央の調停者を持たないソリューションはサービスを分離しているメリットを最大限に享受できるというメリットがある一方で、エラー処理や調整のような問題の解決は苦手としています。

なお、

メディエーター, ブローカーによる仲介

イベント駆動アーキテクチャには、ブローカーとメディエーターという2つの主要なトポロジーがあります。

メディエーターとブローカーは、システム内のコンポーネントやサービス間でメッセージングキューやPub/Subパターンを利用してメッセージの受け渡しを仲介する役割を果たします。

メディエーターパターンでは、一つのコンポーネント(メディエーター)が他のコンポーネント間の通信を仲介します。各コンポーネントはメディエーターとだけ通信し、相互に直接的な通信を行いません。メディエーターはメッセージを受け取り、それを受信者コンポーネントにルーティングします。このパターンにより、各コンポーネントは相互依存性を減らすことができ、疎結合な設計を実現します。

一方、ブローカーパターンでは、エンティティ(ブローカー)がメッセージングキューを介してパブリッシャー(メッセージを送信するコンポーネント)とサブスクライバー(メッセージを受信するコンポーネント)の間でメッセージを配信します。パブリッシャーはブローカーにメッセージを送信し、ブローカーはそれをキューに投稿します。そしてサブスクライバーはキューからメッセージを取得して処理します。このパターンにより、パブリッシャーとサブスクライバーは互いの存在を意識せずに非同期に連携できます。

どちらのパターンも、システム内のコンポーネント間の通信を仲介するための手段として使われます。メディエーターパターンは、それぞれのコンポーネントが他のコンポーネントに直接連携することを避けるために使われ、ブローカーパターンは非同期かつ疎結合なコンポーネント間のメッセージングを実現するために使われます。メディエーターパターンはワークフローなどの制御が必要なときに利用されることが多く、ブローカーはイベント処理に対して高速な応答性と動的な制御が必要な場合に用いられます。

サブシステム間のデータ連携や整合に関する具体的なアーキクチャパターン

分散トランザクションとエラーハンドリングのパターン

分散トランザクションは、複数の独立したオペレーションやデータベース間で行われる複数のトランザクションの組み合わせのことを指します。これらのトランザクションは、一連の処理が原子的(すべて成功するかすべて失敗する)であることが必要であり、データ整合性を保つために一括してコミットまたはロールバックされる必要があります。

分散トランザクションは、跨いだシステムやデータベースとの間でデータの永続性や整合性を確保するために使用されます。たとえば、銀行間の資金移動、複数のデータベース間でのデータの一貫性の確保などがその例です。分散トランザクションを扱うパターンとして、ここではTCC(Try operation, Confirmation, and Cancellation)とSagaを扱います。

TCCは、「仮登録状態」を挟むことでエラーハンドリングを実現する手法のことです。具体的には、まずコーディネーターと呼ばれる調停者がトランザクションの対象となるサービス群に「Try」し、それらにトランザクションの仮の状態を登録させます。これに全てのサービスが成功した場合、コーディネーターは「本登録(Confirm)」を行い、それぞれの状態を確定させます。仮登録の時点でいずれかのサービスが失敗した場合にはトランザクションをキャンセル(Cancel)します。なお、仮登録で成功したにも関わらず本登録では失敗した、というような場合には、コーディネーターは疎通するまで試行を続ける、バッチ処理や手動での修正でConfirmしてしまったトランザクションの巻き戻しを図る、などの対応が考えられます。

TCCのメリットは仮登録状態を作ることによって分散トランザクションのエラーハンドリングを容易にしている(一度Commitしたトランザクションを修正するのは困難)ことですが、これは全てのサービスからの応答を待機することによる遅延とのトレードオフになっています。

Sagaは、一連の小さなトランザクションのステップで構成され、各ステップは個別にコミットまたはロールバックされます。サーガでは、各ステップが失敗した場合には修復操作が実行され、データ整合性が維持されます。サーガは、分散トランザクションを柔軟に扱い、スケーラブルなシステムを構築するために使用されます。

なお、この際に使用されるトランザクションのことを補償トランザクションと言い、逆方向の操作により擬似的なROLLBACKが可能になっています。ただし、この擬似ROLLBACKも失敗する可能性があります。