🐝

精読「マイクロサービスアーキテクチャ 第2版」(第二部 実装 - 第5章 マイクロサービスの通信の実装)

2024/12/30に公開


マイクロサービスアーキテクチャ 第2版
マイクロサービスの設計、実装、運用に必要なベストプラクティスや最新技術を解説した、実践的なガイドブックです。これを読めば、マイクロサービスに関してそれっぽい会話もできますよ。

関連記事

理想的な技術の探索

後方互換性

マイクロサービスを変更する際、他のマイクロサービスとの互換性を壊さないように、後方互換性を保つ変更が重要。

インタフェースの明示化

マイクロサービスが公開するインタフェースは明示的であるべきで、スキーマを利用して変更の影響を明確にすることが推奨される。

APIの技術非依存性

マイクロサービス間のAPIは技術に依存しない設計をすることで、将来の技術変更に柔軟に対応できるようにすることが重要。

コンシューマにとって単純なサービスにする

マイクロサービスが使いやすく、技術選択に制約を与えず、クライアントライブラリを提供することで、コンシューマにとって使いやすいものにするべき。

内部実装の詳細を隠す

内部実装の詳細を公開しないことで、コンシューマとの結合度を低く保ち、変更がサービスに与える影響を最小限に抑えるべき。

技術選択

リモートプロシージャコール( RPC)

ローカルの呼び出しリモートサーバで実行する技術。RPCでは、SOAPgRPCなど、明示的なスキーマ(IDL)を使うことで、異なる技術スタック間でクライアント・サーバのスタブを生成できる。gRPCは、Protocol Buffersというシリアライズ方式を利用するが、他にもネットワークプロトコルに応じた選択肢があり、異なるユースケースに応じた最適な技術を選ぶことができる。

ただし、RPCには欠点もあり、ネットワーク経由での遅延やデータのシリアライズ/デシリアライズにかかるコスト障害対応、異なるプラットフォームでの相互運用性などが課題です。また、サービス仕様変更時にクライアントスタブの再生成が必要になる場合もあり、管理が複雑になることがある。

REST

一般的なHTTP メソッド(GET、POST)を使ってアクセスできるリソース(Customer、Orderなど)を公開するアーキテクチャスタイル。リソースの概念が重要。

さらに、RESTの重要な原則として「HATEOAS」がある。これにより、クライアントはサーバーから提供されるリンクを介して、リソースを動的に探し、操作することができる。これにより、クライアントとサーバーの結合を避け、リソースの場所が変更されてもクライアントが適切に対応できる柔軟性が生まれる。

GraphQL

クライアント側が複数のリクエストを行わずに必要な情報を1回のクエリで取得できるため、特にモバイルデバイスなど制約のある環境で優れたパフォーマンスを発揮する。これにより、無駄なデータ取得や通信量を削減できる。

しかし、いくつかの課題もある。

  • サーバ負荷
    クライアントが動的にクエリを生成できるため、サーバ側に高い負荷がかかる可能性があり、追跡や診断が難しい。
  • キャッシュの複雑さ
    GraphQLはRESTに比べキャッシュが複雑で、IDとの関連付けなどが必要になる場合がある。
  • 書き込み処理
    GraphQLは読み取りに優れていますが、書き込みには適していないとされ、RESTと併用されることがある。
  • データベースのラッパーとしての誤用
    GraphQLはデータベースへの単なるアクセス手段ではなく、マイクロサービスの内部ロジックと振る舞いを保つべき。

GraphQLは、モバイルデバイスや外部APIとの効率的なデータ取得を目的としており、特に複数のマイクロサービスへの呼び出しを集約する場面で効果的。

メッセージブローカー

キュートピックを使って、非同期通信を可能にするミドルウェア。

以下のような特徴がある

  • トピックとキューの使用
    メッセージブローカーは、キュー(ポイントツーポイント)トピック(公開/購読モデル)をサポートする。
    キューでは送信者がメッセージを配置し、1つのコンシューマ(サービスインスタンス)がそれを消費する。
    トピックでは、複数のコンシューマが同じメッセージを受け取ることができ、イベントの
    ブロードキャスト
    に利用される

  • 配信保証
    メッセージブローカーは、メッセージが必ず配信されることを保証する。これにより、下流のサービスが利用できない場合でもメッセージが保持され、再配信されるまで処理が遅れることなく行われる。

  • 信頼性の重要性
    配信保証はブローカーがメッセージを失わないようにするため、通常クラスタベースのシステムを利用する。しかし、ブローカーが正しく設定されていない場合、配信保証が損なわれる可能性があるため、ブローカーを選ぶ際は、ドキュメントをよく読み、設定方法に注意を払う必要がある。

  • 順序保証とトランザクション
    一部のブローカーは、メッセージの配信順序を保証する機能を提供する(例: Kafkaは単一パーティション内で順序を保証)。また、複数のトピックへの書き込みを1つのトランザクションで処理することも可能。

  • 一度だけの配信
    一部のブローカーは「厳密に一度だけの配信」を保証していますが、これは実装の難易度が高いため、実際には何度も再送信される可能性があることを前提にシステムを構築する方が現実的。

メッセージブローカーを選定し運用する際は、その特性(配信保証、順序保証、トランザクション処理など)をよく理解し、システム全体でどのように使用するかを計画することが重要。

シリアライゼーション形式

データの送受信方法には、テキスト形式とバイナリ形式があり、それぞれに特徴がある。

テキスト形式

  • 柔軟性
    JSONやXMLなどは人間が読め、システム間で簡単にデータをやり取りできる。
  • JSON
    XMLよりシンプルで軽量。特にブラウザでの利用に向いているが、スキーマを管理するのが難しい場合がある。
  • Avro
    JSONを基にした形式で、データと一緒にスキーマも送れるため、複数の形式を簡単にサポートできる。

バイナリ形式

  • 効率性
    バイナリ形式(例:Protocol Buffers)は、データのサイズが小さく、高速にやり取りできる。
  • 選択肢
    Protocol Buffersや他のバイナリ形式(Simple Binary Encoding、Cap'n Protoなど)を使用すると、非常に高速で効率的な通信が可能。
  • 最適化
    高速なデータ交換が求められるシステムでは、バイナリ形式を選ぶべきだが、多くのシステムではテキスト形式で十分な場合も多い。

要するに、テキスト形式柔軟で簡単に使えるが、バイナリ形式データの効率的な処理に優れています。それぞれの選択肢は、システムの要求に応じて使い分けることが重要。

スキーマ

以下の内容を簡潔にまとめました:

スキーマの利用について

スキーマを使うかどうかは、シリアライズ形式や技術選択によって決まる。
例えば、XMLの場合はXSD、JSONの場合はJSON Schemaを使う。また、いくつかの技術(例:gRPCやSOAP)では、明示的なスキーマの使用が必要。

明示的スキーマを使用する理由

公開する内容と受け入れる内容を明示的に表現でき、開発者やコンシューマにとって負担を軽減する。また、スキーマは、エンドポイントの偶発的な破壊を検出するのに役立ちます。

破壊の種類

  • 構造的破壊
    エンドポイントの構造(例:フィールドの削除や追加)が変更され、互換性が失われる。
  • 意味的破壊
    構造は変わらないが、振る舞いが変更され、コンシューマの期待を裏切る。

スキーマの役割

スキーマを使うことで、構造的破壊を簡単に検出できます。意味的破壊にはテストが必要。スキーマレスAPIでは、暗黙的にデータの構造を期待しているが、明示的なスキーマがないため、変更時に予期しない影響を受けやすい。

結論

明示的なスキーマを使う方が、マイクロサービス間で何を公開し、何を受け入れるかを明示的にし、チーム間のコミュニケーションを円滑にするために重要。

マイクロサービス間の変更に対処する

マイクロサービスの契約変更についてよく問われるのは、破壊的変更への対応方法。まずは破壊的変更を避けるためにできることを考え、その後に破壊的変更の影響を説明する。

破壊的変更の回避

破壊的変更の回避には、いくつかの重要なアイデアがある

  • 拡張変更
    マイクロサービスのインターフェースに新しいフィールドを追加することで、既存の機能を壊さずに拡張可能。
  • 耐性のあるリーダー
    クライアントが柔軟に対応できるように設計し、不要なフィールドを無視することで、変更に耐えることができる。
  • 適切な技術選択
    技術的に柔軟性の高い方法(例:Protocol BuffersやAvro)を選択することで、変更を容易にします。
  • 明示的なインターフェース
    マイクロサービスが提供するスキーマを明示的に定義することで、クライアントが何を期待するかを明確にし、変更のリスクを減らします。
  • 偶発的な破壊的変更の早期把握
    本番環境で破壊的な変更を早期に検出できるメカニズムを導入することが重要。これにより、予期しない変更によってクライアントが壊れるリスクを最小化できる。

これらのアプローチは、マイクロサービス間での変更を円滑に進めるために重要。

破壊的変更の管理

マイクロサービスにおける破壊的変更の管理には主に3つの方法があります。

  • ロックステップデプロイ
    すべてのコンシューマが同時に新しいインタフェースにアップグレードする必要があり、独立したデプロイができなくなる。これは頻繁には使いたくない方法。

  • 互換性のないバージョンの共存: サービスの古いバージョンと新しいバージョンを並行して運用し、トラフィックを適切にルーティングする。ただし、サービスコードが分岐し、管理が複雑になるため慎重に扱うべき。

  • 旧インタフェースのエミュレーション: 新旧インタフェースを共存させる方法。これにより、破壊的変更を段階的に導入でき、コンシューマに移行時間を与えることができる。

エミュレーションの方が管理しやすいとされているが、選択肢は変更の影響範囲に依存する。

マイクロサービスの世界における、DRYとコード再利用の危険性

DRY(Don't Repeat Yourself) はコードや知識の重複を避ける原則。しかし、マイクロサービスで共有コードを使うと、サービス間の結合が強くなり変更が広範囲に影響を与えるリスクがある。

共有コードの問題

共通のライブラリを使うと、バージョン管理やメンテナンスが難しくなる。サービスごとに独立したコード管理が推奨される。

クライアントライブラリのリスク

クライアントライブラリを利用するとサーバーのロジックがクライアントに漏れる可能性があり、独立性を保つためには使用を最小限にするべき。
例えば、Amazon Web Services(AWS) のように、API呼び出しを抽象化したソフトウェア開発キット(SDK) を使用する方法が効果的。これにより、サービス間の結合を避けつつ、クライアントコードの一貫性を保つことができる。

マイクロサービスでの再利用は慎重に行い、ライブラリやクライアントライブラリの影響範囲を最小限にするべき

サービス検出

マイクロサービスが増えると、サービスがどこで動作しているかを把握する必要があり、この課題に対応するのが「サービス検出」。サービス検出は、サービスインスタンスが自己登録し、検索可能な方法を提供する。

代表的な解決策には、DNS動的サービスレジストリがある。

  • DNS
    サービス名をDNSエントリに関連付け、IPアドレスに解決する。ロードバランサを利用して、複数のインスタンスへのトラフィックを分散する。ただし、動的環境ではDNSの更新が手間になる。

  • 動的サービスレジストリ
    サービスが中央のレジストリに登録され、検索可能になる。ZooKeeperConsulなどがこれを提供し、サービス検出を強化する。

サービスメッシュとAPIゲートウェイ

まず、APIゲートウェイサービスメッシュは、どちらもマイクロサービスアーキテクチャにおいて重要な役割を果たすが、処理するトラフィックの種類目的が異なります。

APIゲートウェイ

APIゲートウェイは、南北トラフィック(外部から内部へのトラフィック)を処理する。

具体的には、外部クライアント(モバイルアプリやWebブラウザ)からのリクエストを受け取り、適切なマイクロサービスにルーティングする役割を担う。APIゲートウェイは、リバースプロキシのように機能し、以下のような多くの機能を提供する:

  • 認証認可
    外部リクエストが内部サービスにアクセスする前に、適切な認証と認可を行う。
  • APIキー管理
    APIゲートウェイは、APIキーの管理を行い、リクエストの認証する。
  • レート制限
    過剰なリクエストを防ぐため、リクエストの数に制限を設ける。
  • ロギングと監視
    リクエストをロギングし、モニタリングしてトラフィックの状態を監視する。

APIゲートウェイは、特にインターネット上で公開されるサービス(例えば、外部顧客が利用するAPI)を扱う際に非常に有用。

サービスメッシュ

一方、サービスメッシュは東西トラフィック(マイクロサービス間の通信)を管理します。マイクロサービス間での通信を効率的かつ安全に行うために使用される。サービスメッシュの主な役割は以下の通り

  • サービス間通信の管理
    マイクロサービス間のリクエストをルーティングし、サービスの可用性を保つ。
  • トラフィックの制御
    リトライ、タイムアウト、サーキットブレーカーなどを設定して、通信の安定性を確保する。
  • セキュリティ
    通信内容を暗号化し、相互認証(mTLS)を使って、マイクロサービス間のセキュリティを強化する。
  • トレーシングとロギング
    サービスメッシュは、サービス間通信のトレーシングやロギングを提供し、デバッグやパフォーマンスの監視に役立つ。

サービスメッシュは、複雑なマイクロサービス環境において、マイクロサービス間の通信を効率的に管理するために使用されます。

役割が重なる場合

APIゲートウェイとサービスメッシュは、似たような役割を持つこともある。
例えば、どちらもマイクロサービスの間でトラフィックをルーティングしたり、ロギングや監視を行ったりするが、その主な違いはトラフィックの方向
APIゲートウェイは、外部から内部へのアクセスを管理するために使われ、サービスメッシュは、内部のマイクロサービス同士の通信を管理する。

実際には、最近のAPIゲートウェイはサービスメッシュ機能を備えていることも多く、両者が重なり合う部分も増えてきている。そのため、どちらを選択すべきかは、特定のユースケースやシステムの規模に依存する。

サービスの文書化

文書化は重要で、APIを利用する方法を理解するために良いドキュメントが必要。

明示的なスキーマ

  • スキーマの役割
    明示的なスキーマは、エンドポイントが何を公開しているかを把握するのに役立つが、エンドポイントの振る舞いを伝えるには不十分。
  • ドキュメントの重要性
    OpenAPI形式のスキーマやツール(例: KubernetesのAmbassador)が自動的にドキュメントを生成するのが効果的。

自己記述型システム

  • 初期のサービス発見方法
    UDDI(サービスディスカバリ)などが登場したが、複雑で使いづらいため、軽量なアプローチに進化。
  • 人向けのレジストリ
    Martin Fowlerの提案した「人向けのレジストリ」は、Wikiのような簡単な方法でサービスの情報を記録し、全体像を把握しやすくする手法。
  • ツールとダッシュボード
    企業はシステム情報を集約するために、カスタムダッシュボードを使う。Biz Opsツールは、グラフデータベースを基にして、サービスに関する情報を一元化し、システム運用性スコア(System Operability Score)を計算するなどの機能がある。

結論

  • マイクロサービスの可視化とドキュメンテーション: システム全体やサービス間の相互作用を把握するためには、ドキュメントやツールを使いこなすことが重要。OpenAPIやCloudEvents形式を使うことで、より効率的に情報を管理・利用できる。

このように、マイクロサービスやAPIを適切に管理・文書化するためには、明示的なスキーマや自己記述型システムを活用し、ドキュメントの更新とツールの導入が求められます。

参考


https://qiita.com/mmake/items/7d4bfb45c3aa8b34a983

Discussion