おさかな日誌

魚類がプログラミング

Microservices Architecture と非同期 API

Microservice Architecture を採用すると解決が難しい問題である、部分的障害に対する仕組みとして非同期 API にしてしまう、という方法を思いついた。

まず、今までの同期 API を使っていると想定してあるケースを検討してみる。

フロントとなるあるサービス A がバックエンドサービスである B, C に依存しているケースを考えてみよう。A はユーザーの登録やユーザーの属性の管理を B に委譲していて、A はユーザーの投稿したレシピの管理を C に依存している。別の言い方をすると、B は汎用的なユーザー管理サービスであり、C は汎用的なレシピ管理サービスであり、A はそれぞれを利用している。

このようなケースでサービス A がユーザー(クライアント)から名前の変更をリクエストされたとする。

正常な場合では、A は B のユーザー属性変更 API を call し、無事 B からレスポンスが返ってくると A はユーザーへ処理の完了をレスポンスできる。

異常な場合、例えば B がなんらか原因でサービスダウンしている場合では、A は B のユーザー属性変更 API を call しても処理が完了できない。A はユーザー(クライアント)に自身のサービスがダウンしているようなレスポンスを返さなければならない。

同期 API を利用している場合、B のサービスダウンが直接 A のサービスダウンへ繋がってしまう。これは大きな問題でフロントとなるサービスが依存しているバックエンドサービスが多くなれば多くなるほど、フロントとなるサービスの可用性は著しく下がってしまう。

さて、ここで非同期 API を部分的に採用して先ほどのケースを再検討しよう。部分的に、つまり B のユーザー属性変更 API のみに対して非同期 API を採用する、ということである。

正常な場合では同期 API と変わらず A は B のユーザー属性変更 API を call し、B から正常なレスポンスを得る。この時違う点としては A が B からレスポンスをもらった際には A が変更したい B のリソースはまだ変更されていない可能性がある、ということである。A がユーザー(クライアント)にレスポンスする際もユーザーが行いたかった変更は反映されてない可能性がある。しかし、部分的障害が起こっていない場合は、可及的速やかに B にリクエストされたリソースの変更は処理されるので問題にはならない。

異常な場合、B がサービスダウンしている状況を検討してみる。A は B のユーザー属性変更 API を call しても B からは正常なレスポンスは返却されない。この時 A はユーザー(クライアント)が期待している結果が、リソースの変更ではなくリソースが変更される約束、なので A は自身がリソースの変更をバッファリングしB のサービスが復帰した際に B へリクエストすることを保証することで、ユーザー(クライアント)に正常なレスポンスを返却することができる。B のサービスダウンが一定の期間に収まるならば、A がユーザーへレスポンスしたリソースが変更される約束は無事実行される。

このように、サービス間の結合部分に対して非同期 API を採用することで、部分的障害の影響を狭められるかもしれない。

もちろん同期しなければならない処理もあるので (先の例で言うと B へのユーザー登録などは同期処理しなければならない)、全てに適用できるものではない。さらに、同期 API に比べてシステム全体が複雑度を増してしまうことは間違いない (先の例での A のバッファリング部分)。

以上の考察・アイディアはサービスデザインパターンの2章を読んで思いついたジャストアイディアであり、この本を読み進めていく内に消化されるのかもしれない。

追記

公開したらさっそく指摘があった、このアイディアを採用すると、B が復旧した時に B に依存しているサービス群がリクエストする順番を正しくするのがものすごく難しいので現実は厳しい。