前置き
マイクロサービス間のコントラクトと、通信形態の組み合わせによるアーキテクチャ特性の違いを見ていきたいと思います。
結合の強さの比較
まず、以下の4つのパターンの結合の強さを順位付けすると、以下のようになります。
1⃣ 同期通信 + 厳格なコントラクト ⇒ 最も強い結合(密結合)
2⃣ 同期通信 + 緩やかなコントラクト ⇒ 非常に強い結合(予測不能なリスクを伴う)
3⃣ 非同期通信 + 厳格なコントラクト ⇒ 管理された疎結合
4⃣ 非同期通信 + 緩やかなコントラクト ⇒ 最も疎な結合
各パターンの詳細
では、1⃣~4⃣まで、それ添えの特性を見ていきましょうか。
1⃣ 同期通信 + 厳格なコントラクト
例えば、厳密なOpenAPI仕様を持つREST APIによる直接呼び出しなどです。
モジュラーモノリスからエピックサーガ(同期的・アトミック整合)へ移行した直後は、このパターンになります。
境界をまたいだトランザクションの整合性を、高い信頼性で担保するためには、
サービス間の通信規約が厳格であることが絶対条件となるから です。
メリット
予測可能性と安全性: 契約が明確で、コンパイル時や実行直後にエラーを検知できるため、
非常に安全で予測可能です。
デメリット
①. 低い耐障害性(脆さ)
呼び出し先のサービスの遅延や障害が、呼び出し元に直接伝搬します(時間的な密結合)。
②. 低い俊敏性
スキーマ変更には、両サービスのデプロイ調整が必要です。
向いているシーン
結果を即座に返すべき、強く一貫したトランザクション
(例:決済前のクレジットカード有効性チェック)
絶対に使わない方が良いシーン
連携先が不安定、あるいは応答が遅い可能性がある場合。
高い耐障害性や、チームの独立したデプロイが求められる場合。
蜜結合状態なんだから、いずれかのサービスのダウンは全体のダウンになります。
2⃣ 同期通信 + 緩やかなコントラクト
例えば、仕様が曖昧なREST APIによる直接呼び出しなどです。
先に言っておくと、これは
本番環境のアーキテクチャでは、アンチパターンです
メリット
プロトタイピングの速さ
厳密な型定義などを省略できるため、開発初期段階での実装が速い。
デメリット
①. 最悪の組み合わせ
同期通信の脆さと、緩やかなコントラクトの実行時エラーのリスクを兼ね備えています。
実際の設計では、マジで辞めてください。
②. 暗黙的な契約
契約がコードやドキュメントにしか存在せず、すぐに陳腐化します。
向いているシーン
使い捨てのプロトタイプ。(たとえば、分割前のカオス実験用に使うプロトタイプなど)
同一チームが管理する、ごくごく内部的な連携。
これは、コミュニケーションで何とかカバーできるからです。
絶対に使わない方が良いシーン
ほとんど全ての本番環境における、チームをまたいだサービス間連携。
あとあと、余計な過剰コミュニケーションコストが発生し、
それがまんま組織アーキテクチャをいつの間にか構成し、
ITアーキテクチャとの矛盾による、負の因果ループにはまり出します。
3⃣ 非同期通信 + 厳格なコントラクト
例えば、AvroやProtobufを使ったKafkaでのイベント連携などです。
メリット:
①. 高い耐障害性
相手サービスが停止していても、メッセージブローカーが仲介するため、障害が伝搬しません(時間的疎結合)。
②. 高い信頼性
厳格なスキーマによって、データの品質と一貫性が保証されます。
デメリット:
①. 初期の複雑性
メッセージブローカーやスキーマレジストリといった、管理すべきインフラが増えます。
②. デプロイ調整の必要性
スキーマの破壊的変更には、「同期通信 + 厳格なコントラクト」と同様に慎重なバージョン管理とデプロイ調整が必要です。
向いているシーン
大規模マイクロサービスにおける、ほとんどのコアな業務プロセス。
データの正確性が求められる、非同期のワークフロー。
使わない方が良いシーン
スキーマ構造がコロコロ頻繁に変わるようなケースでは、やめた方が良いでしょう。
4⃣ 非同期通信 + 緩やかなコントラクト
例えば、スキーマ定義のないJSONを使ったイベント連携などです。
メリット
①. 最大限の柔軟性
送信側は、受信側を一切気にすることなく、自由に新しいフィールドを追加できます。
②. 高い拡張性
新しい受信サービスを、送信側に一切影響なく追加できます。
デメリット
①. 実行時エラーのリスク
受信側は、いつデータ構造が変わるか分からないため、常に防御的なプログラミングを強いられます。
②. 適応度関数の必要性
このパターンの信頼性を担保するには、メッセージを監視する適応度関数のような、
追加のテスト・監視機構が不可欠です。
向いているシーン
多少のデータ欠損が許容される、重要度の低い分析イベントの送信。
データの完全性よりも、変化の速さが重視される実験的な機能。
絶対に使わない方が良いシーン
金融取引など、データの完全性がミッションクリティカルなビジネスプロセス。
コントラクトの進化のロードマップ
先日投稿した、進化的なマイクロサービスアーキテクチャの話には、このコントラクトの話を含み忘れていたので、ここで触れることにします。
アーキテクチャと組織が育っていくと、徐々にサービス間の契約も動的に変化します。
そのコントラクトロードマップのお話をここでします。
まず、初期条件は、
同期通信 + 厳格なコントラクト
モジュラーモノリスからエピックサーガ(同期・アトミック整合)へ移行した直後
という状態だと仮定します。
ここからどのようにコントラクトを進化させていったらいいでしょうか?
望ましい進化:エピックから非同期+厳格なコントラクトへ
エピックからパラレルサーガ(オーケストレーション型)への進化を指します。
コレオグラフィ型でも一応、厳格なコントラクトは使えますが、
プロセス全体の流れを厳密に制御したいオーケストレーターは、各サービスとの対話が、
予測可能で信頼性の高い「厳格なコントラクト」 に基づいて行われることを強く要求します。
そのため、この2つは非常に相性が良い、自然な組み合わせです。
概要
システムの耐障害性(レジリエンス) を高めるために、時間的な結合をなくす
「非同期通信」へと移行します。
理由
この道筋では、データの信頼性を担保する「厳格なコントラクト」は維持したまま、
同期通信の弱点であった「時間的な密結合」だけを解消します。
これにより、システムの安全性と堅牢性を両立させることができます。
これが、アーキテクチャの成熟に向けた、段階的な進化のステップです。
対して、アンチパターンもあります。
避けるべき進化:同期通信+緩やかなコントラクトへ
概要
これは計画的な進化というよりは、アーキテクチャの腐敗または劣化の兆候であることが多いです。
理由
この道筋は、同期通信の「脆さ(時間的な密結合)」はそのままに、厳格なコントラクトが提供する「安全性」だけを放棄してしまっています。
これは、両方のパターンの悪いところ取りであり、システムを不安定にするリスクが非常に高いため、通常はアンチパターンと見なされます。
非同期+厳格なコントラクト ⇒ 非同期通信 + 緩やかなコントラクトへ
非同期かつ結果整合性のマイクロサービスのコントラクトをさらに緩やかにしたいケースです。
概要
この移行は、サービス間の契約の重点を
「事前の静的な安全性」から「実行時の動的な柔軟性」
へとシフトさせる、戦略的なアーキテクチャ変更です。
言い換えると、スキーマという厳格な「法律」によるガバナンスをやめ、各サービスが自律的に振る舞うことを許容し、その代わりに高度な「監視」によって秩序を保つ、というアプローチに変わります。
移行する理由
この移行を検討する主な理由は、開発のアジリティ(俊敏性)を極限まで高めるためです。
1. 開発速度の最大化
送信側(Producer)は、受信側(Consumer)との煩雑なスキーマバージョンの調整や、
デプロイ順序の調整から解放されます。
新しいビジネス要件に応じて、誰の許可も待つことなく、即座に新しいフィールドをイベントに追加して発行できます。
2. 実験と探索の促進
プロダクトの初期段階や、新しい分析を試みたい場合など、どのようなデータが最適解かまだ分からない状況において、スキーマを固定せずに、迅速に試行錯誤を繰り返すことができます。
移行時の注意点
この移行は、システムの信頼性に関する責任を、送信側から受信側に大きく移管するため、以下の注意点が極めて重要になります。
1. 消費者側の防御的プログラミングが必須
受信側は、いつ、どのようなデータが来るか予測できません。
したがって、全ての受信サービスは、
「期待するフィールドが存在しない」
「値のデータ型が予期せず変わる」
「未知のフィールドが追加される」
といった状況を常に想定し、それらを適切に処理してクラッシュしない、堅牢な実装(防御的プログラミング)が必須となります。
2. 適応度関数や監視の強化が不可欠
スキーマという事前の安全策がなくなるため、それに代わる実行時のセーフティネットが不可欠です。
緩やかなコントラクトでやり取りされるメッセージが、最低限の期待(例:「userIdはnullではなく、必ず存在する」など)を満たしているかを常に検証する、適応度関数のような監視メカニズムの重要性が、厳格なコントラクトの場合よりも遥かに高まります。
3. ドキュメントとコミュニケーションへの依存
機械可読な契約(スキーマ)がなくなるため、どのようなデータがやり取りされているかの信頼できる情報源が、人間が管理するドキュメントや、チーム間のコミュニケーションに大きく依存することになります。
これを維持するには、高いレベルの規律が求められます。
パラレルサーガ(オーケストレーション型)への適用とその際の問題点
非同期+厳格なコントラクト ⇒ 非同期通信 + 緩やかなコントラクト
へ移行する際の注意点(消費者側の防御実装や適応度関数の重要性)は、オーケストレーション型でも全く同じように当てはまります。
しかし、
オーケストレーション型と緩やかなコントラクトの組み合わせには、アーキテクチャ的な不協和音が生じます。
オーケストレーターの責務
プロセス全体の状態を管理し、厳密なフローを制御するのがオーケストレーターの役割です。
緩やかなコントラクトがもたらすもの
オーケストレーション型サーガの構造のまま、コントラクトを緩やかにすると、
各サービスから返ってくるメッセージ(イベント)の構造が予測不能
になります。
課題
厳密な制御をしたいオーケストレーターが、構造が不確かなメッセージを扱うことになるため、オーケストレーターの内部ロジックが極めて複雑で防御的にならざるを得ません。
「もしこのフィールドが無かったらどうするか」「もしデータ型が違ったらどうするか」
といった条件分岐だらけになりやすく、
オーケストレーター自体が脆く、保守困難になるリスクが非常に高い
です。
アンソロジーサーガ(コレオグラフィ型)との親和性
一方で、
緩やかなコントラクトはコレオグラフィ型と非常に相性が良い
です。
コレオグラフィの性質
各サービスは、関心のあるイベントだけを自律的に購読し、処理します。
緩やかなコントラクトとの相性
関心事の分離
送信側がイベントに新しいフィールドを追加しても、そのフィールドに関心のない受信サービスは、それを単純に無視すれば済みます。自身の動作に影響はありません。
自律性の尊重
各サービスは、受け取ったイベントの中から、自分が必要な情報だけを解釈し、自身の責任で処理を進めます。中央の管理者がいないため、メッセージの構造が多少変化しても、システム全体が柔軟に対応できます。
結論
パラレルサーガ(オーケストレーション型)のまま、コントラクトを緩やかにすることも一応技術的には可能のようですが、それはオーケストレーターという中央集権コンポーネントに、分散化された予測不能性を持ち込むことになり、アーキテクチャとして不安定な組み合わせになります。
したがって、
非同期通信 + 緩やかなコントラクトに移行する場合には、
アンソロジーサーガ(コレオグラフィ型)になっていることが望ましい
契約の柔軟性と、アーキテクチャ全体の柔軟性を一致させるという設計判断と言えます。