インフラレベルでの結合
エピックサーガやおとぎ話サーガのように、マイクロサービス間の通信が同期通信の形態を主としている場合、それはアプリケーションレベルではサービスが分割されていても、実質的に「インフラ部分(特にネットワーク層)で密結合」の状態に陥っていると言えます。
これは、マイクロサービスアーキテクチャにおける一般的なアンチパターンである「分散モノリス」の典型的な症状の一つです。
だからといって、エピックサーガやおとぎ話サーガがダメなわけではないです。
理想形に段階的に近づけるうえで、これらの一定期間分散モノリス構造を取ることは、理にかなっているからです。
エピックサーガやおとぎ話サーガは、アプリケーションという上物としては、確かに独立性が高まっています。
おとぎ話サーガに関していうのなら、
【上物としては、確かに単独でデプロイ可能】と言える状態です。
【インフラまで含めたら単独デプロイ不可能】という状態。
今回は、同期通信だと、なぜインフラレベルで蜜結合なのか?について説明していきます。
なぜ同期通信だとインフラレベルで密結合になるのか?
1. ネットワークの可用性・パフォーマンスへの直接依存
密結合のメカニズム
同期通信(例: REST APIの直接呼び出し)では、サービスAがサービスBを呼び出した際、
サービスBからの応答が返ってくるまでサービスAは処理をブロックして待ち続けます。
インフラ結合
このとき、サービスAの処理の可否やパフォーマンスは、
サービスBの可用性(Bがダウンしていないか)、Bのパフォーマンス(Bの応答が遅くないか)、そしてサービスAとB間のネットワークの安定性(ネットワーク遅延や障害がないか)に直接依存してしまいす。
結果
ネットワークのどこかで問題が発生したり、呼び出し先のサービスが応答しなかったりすると、呼び出し元のサービスがハングアップしたり、タイムアウトしたりして、障害が連鎖的に波及しやすくなります。
これは、インフラ(ネットワークやサービスインスタンスの健全性)が原因で、アプリケーションの処理が停止するという密結合の状態です。
2. デプロイ時の結合
密結合のメカニズム
同期通信は、呼び出し元と呼び出し先のAPIの互換性を強く要求します。
呼び出し先のサービスBがAPIの破壊的変更を伴う新しいバージョンにデプロイされた場合、
呼び出し元のサービスAも、その変更に対応するバージョンにデプロイされていなければ、通信が破綻します。
しかもこれは、サービスAは常にサービスBのバージョンを気にしながら
という2つのチームのコミュニケーション調整コストも増大させます。
インフラ結合
この依存関係は、デプロイメントパイプラインにおいて、
サービスAとサービスBが同時に、あるいは特定の順序でデプロイされる必要がある
というインフラレベルの制約を生み出します。
つまり、各サービスが独立してデプロイできるというマイクロサービスの大きなメリットが失われます。
3. スケーリングの制約
密結合のメカニズム
同期通信では、呼び出し元サービスの負荷が増大すると、それに比例して呼び出し先サービスの負荷も増大します。
インフラ結合
サービスAのインスタンスを増やしても、サービスBのキャパシティが不足していれば、
そこのパフォーマンスがボトルネックになります。(TOC制約理論)
インフラのパフォーマンス改善のためには、両方のインスタンスを増やす必要があるため、シンプルにコストも増大します。
これにより、個々のサービスを独立してスケールするという柔軟性が失われ、サービス間のインフラキャパシティが結合されてしまいます。
4. 単一障害点(SPOF)化のリスク
密結合のメカニズム
同期呼び出しのパスが連鎖すると、そのパス上のどこか一つでも障害が発生すると、
全体の処理が停止する可能性があります。
インフラ結合
サービス間のネットワーク経路や、呼び出されるサービスの特定のインスタンスが、事実上の単一障害点となり、アプリケーションレベルでの回復力(リトライ、サーキットブレーカーなど)が実装されていないと、脆弱になります。
正しい疎結合の追求
マイクロサービスアーキテクチャでは、アプリケーションのロジックだけでなく、
インフラレベルでもサービスが疎結合であることを目指します。
これを実現するためには、以下のようなアプローチが推奨されます。
非同期通信の採用
可能な限り、イベント駆動アーキテクチャ(メッセージキューやイベントバス経由)を導入し、サービス間の直接的な同期依存を減らします。
そのために、以下のように段階的に非同期にしていく、段階的サーガの進化というものがあると思ってください。
堅牢なAPI設計とバージョニング
各マイクロサービスを担当するチームは破壊的変更を避け、後方互換性を考慮したAPI設計と厳格なバージョニングを行うことで、デプロイ時の結合を緩和します。
適応度関数の導入によって、各サービスがどこまでなら単独で進化してもいいのか?
というガードレールの設計がされていると、全体を統治するアーキテクトには負荷がかかるものの、各マイクロサービスの担当チームは、より自分たちのプロダクトを磨き上げることに専念できますね。
回復力のパターン実装
アプリケーションコードでリトライ、サーキットブレーカー、タイムアウトなどの回復力パターンを実装するか、サービスメッシュ(サイドカー)によってインフラレベルでこれらのパターンを透過的に適用します。
個人的には、運用を楽にするためにも、高いかもしれないですが、
サービスメッシュを導入してしまった方が良いと感じます。
サービスメッシュの活用
サービスメッシュは、サービス間の同期通信が避けられない場合でも、その通信の信頼性、可観測性、セキュリティ、回復力をインフラレベルで提供することで、アプリケーションコードからの結合を分離し、より健全な疎結合を支援します。
サービスメッシュを使うと、
コードに変更を加えることなくアプリケーションに耐障害性機能を追加することができる
ため、関心も分離されており超便利です。
メッシュの提供する回復機能、耐障害性機能にはタイムアウト、タイムアウトを伴う
リトライ、サーキットブレーカー、ヘルスチェック、フォールトインジェクションなどがあります。(これらのフレームワークみたいなもんですね)
オブザーバビリティツールの提供するデータを便利に取得できることもあり、
チームトポロジーを使った組織構造変化
例として、おとぎ話サーガにおける組織構造を見て見ましょう。
図中では、サービスの所有する業務DBは端折ってます
おとぎ話サーガにおける組織構造
この段階では、まだ各種マイクロサービスチーム内には、インフラを担当する人はいません。
この段階では、プラットフォームチーム内部では、必死にプラットフォーム内の
境界付けられたコンテキストを探しているために、コミュニケーションが蜜状態で、
一種の混乱期と言えるでしょう。
そうする中で、この青いプラットフォームチーム内でも、
徐々に連携が疎で安定した部分が浮き彫りになってきて、そこがインフラ上の境界、
プラットフォームチーム内でのストリームアラインドチームの境界となります。
パラレルサーガにおける組織構造
この状態から、最終的には、以下のようなパラレルサーガにした際には、
各種マイクロサービスチーム内には、それぞれのマイクロサービス内部のインフラコンポーネントになるサイドカーの設定やらをしてくれるインフラメンバーが吸収されることになります。
そして、サービスメッシュという複数のマイクロサービスの土台となる部分を
組織全体から見たプラットフォームチームが担当することになるのです。
よって、
まとめ
以上のことから、同期通信を主体とするマイクロサービス間連携は、
アプリケーション上の独立性とは裏腹に、ネットワークというインフラの共有基盤を介して密結合を作り出してしまい、マイクロサービスのメリットを享受しにくくなります。
ですが、いきなりインフラレベルでの疎結合を目指すのではなく、
段階的にアプリも疎結合に ⇒ インフラも疎結合に という風に進化させてみてください。
またその際には、組織構造の進化も必須です。
是非、チームトポロジーやダイナミックリチーミングを手に取って、実践してみてください。