前提
この投稿はZOZO アドベントカレンダー2022 #1 9日目の記事になります。
マイクロサービスの特にSagaパターンについてあれこれ調査したので、そのアウトプットになります。
今回の記事は、マイクロサービスの詳細や実装、コレオグラフィ型のパターン、envoy周りの実装、またはAkkaなどフレームワークの使用については述べず、単にオーケストレーション型のSagaパターンの特にトランザクション周りについて記事にしたいと思います。
非同期API連携
Sagaパターンで分散トランザクションを実現しようとしたとき、各サービスとやりとりについては、非同期APIを利用してすることになるかと思います。(一旦envoyのようなPROXY経由でのアクセスについては置いておきます)
図にするとこんな感じになるかと。
ポイントになるのはWコミットを避けるため、トランザクションは各サービスごとに行い、問題があれば、各サービスで実施した内容の逆をもう一度行いリセットするいわゆる「補償トランザクション」を行う点になります。
このとき、自分が気になったのは、各サービスの実行とレスポンスを返す処理(メッセージ送信)間のトランザクションです。
たとえば、下図の場合、SeviceBが失敗した場合、レスポンスし、SeriveAのオーケストレーターでServiceAのDBを元に戻す処理を行います。
気になるのはこの後で、最初のServiceAの処理でDBには登録できたが、Request1のリクエストでコケた場合どうなるか、これも図にしてみました。
補償トランザクションの処理を書く場所は一つにまとめられているし、オーケストレーションするサービス内に全てあるので、複数箇所から呼ばれるのはあまり問題ないようにも見えるのですが、複雑度はあがるなぁと思いました。
Outboxパターンを併用
すごく細かいですが、Outboxパターンを併用すると各補償トランザクションは1箇所からしか呼ばれないのでスッキリします。図にするとこんな感じ。
まとめ
基本的にアーキテクチャをシンプルにするとコードは複雑になりがちで、コードをシンプルに保とうとするとアーキテクチャが複雑になりがちです。
そのあたりはトレードオフかと思いますが、良い選択をしていくためにもまずはこういった考察は必要かなと思っています。