前置き
補償トランザクションの本質は、
単なる技術的な「ロールバック」ではなく、ビジネス価値を持つ
「正規の業務プロセス」 へボトムアップ式に昇華させることだと感じます。
「技術的な取り消し」から「ビジネスユースケース」へ
単純なロールバックは、データベース上で「何もなかった」ことにする技術的な処理です。
しかし、補償トランザクションは**「一度起きたことを、ビジネスルールに則って取り消す」という、それ自体が意味を持つ新たな業務活動(ビジネスユースケース)**です。
例:ECサイトにおける注文処理の失敗
あるサーガで「在庫の引き当て」には成功したが、その後の「決済」に失敗したとします。
この時に実行される補償トランザクションは、単に在庫数を元に戻すだけではありません。
顧客への通知
「決済エラーのため、ご注文をキャンセルいたしました」というメールを送信する。
在庫の再計上
在庫数を戻すだけでなく、「引当失敗」の理由コードと共に計上し、データ分析に活用する。
マーケティング連携
関連商品のリターゲティング広告の対象から、この顧客を一時的に除外する。
不正利用の検知
もし決済失敗の理由が不正利用の疑いであれば、その情報をセキュリティチームに通知する。
これらは全て「注文キャンセル」という、それ自体が独立したビジネスユースケースを構成する活動そのものです。
つまり、
起きたことをなかったことにする能力
ボトムアップでのビジネスプロセス発見
多くの場合、開発者がサーガを実装しようとして、
初めて「もしこのステップが失敗したら、ビジネス上はどう振る舞うべきか?」という異常系への問いが生まれます。
この技術的な必要性(ボトムアップ) がきっかけとなり、これまで明確に定義されていなかった「キャンセルポリシー」や「返金プロセス」といった、新たなビジネスユースケースが正式に定義されることが多々あります。
技術的な問いが、ビジネスルールをあぶり出す
補償トランザクションの設計は、単なるエラー処理の実装ではなく、
開発者がシステムの「ハッピーパス(正常系)」だけでなく、
システムの異常系(オフハッピーパス) を設計する過程で、これまで暗黙知であったり、
誰も深く考えていなかった異常系でのビジネスルールを発見・定義していく、
価値あるモデリング活動そのもののです。
では、実際に簡単なECサイトを例にその仮定のメカニズムを考えてみましょう。
登場するマイクロサービス
①. 注文サービス
注文の受付、注文全体のライフサイクル管理、そしてサーガ全体の進行を管理するオーケストレーターとしての役割を担います。
②. 在庫サービス
商品の在庫数を管理します。オーケストレーターからの指示で、
在庫の 「引き当て」や、失敗時の「解放(補償)」 を行います。
③. 決済サービス
クレジットカードや他の決済手段による支払い処理を実行します。
オーケストレーターからの指示で、「決済実行」や、失敗時の「返金(補償)」 を行います。
④. 配送サービス
決済完了後、オーケストレーターからの指示で、商品の配送手配を開始します。
⑤. 通知サービス
「注文キャンセル」や「発送完了」といったビジネス上の重要な出来事が発生した際に、
顧客へメールやSMSで通知します。
ステップ1:技術的な課題の発生(ボトムアップの起点)
状況
開発者が、ECサイトの「注文処理サーガ」を設計しています。
正常系のフローは「①在庫の引き当て → ②決済処理 → ③配送指示」です。
技術的な問い
ここで開発者は、純粋に技術的な観点から、「もし、ステップ①は成功したのに、ステップ②の決済処理が失敗したら、システムとしてどう振る舞うべきか?」という課題に直面します。引き当てた在庫をどうすべきか、システムは判断できません。
ステップ2:ビジネスへの問いかけ
状況
開発者は、この技術的な課題を、ビジネス上の問いとしてプロダクトオーナーや事業責任者に投げかけます。
ここがモデラーの腕の見せ所です。
アプリ的な関心から、ビジネスモデル上の関心のみにしたストーリーへと変換し、
それを以下のように問いにします。
ビジネス上の問い
「お客様の決済が失敗し、注文が完了しなかった場合、ビジネス上の正式なルールとして、引き当てた在庫はどうしますか? すぐに棚に戻しますか? それとも、お客様のために10分間だけ確保しておきますか? お客様への通知は必要ですか?」といったこと。
これは、イベントストーミング中に登場するポリシーの要素となります。
ステップ3:ビジネスユースケースの発見と定義
状況
この問いを受けて、ビジネス側のステークホルダーは、これまで明確に定義していなかった
「決済失敗に伴う注文キャンセル」という、新たなビジネスユースケースの仕様を定義する必要に迫られます。
定義されるユースケース(例)
・ユースケース名:決済失敗による注文キャンセル処理
・目的:顧客体験を損なわず、在庫の機会損失を最小化すること
・ビジネスルール
・引き当てた在庫は、即座に販売可能在庫に戻す。
・顧客に対し「決済エラーのご連絡と再注文のお願い」という内容のメールを自動送信する。
・この失敗イベントを、マーケティング部門が分析できるよう記録する。
(これはどう考えても、イベント履歴式ドメインモデル臭い)
ステップ4:補償トランザクションとしての実装
状況
開発者は、ステップ3で定義されたビジネスユースケースを、具体的な補償トランザクションとして実装します。
実装内容
オーケストレーターは、決済処理の失敗を検知したら、
「在庫解放コマンド」と「通知メール送信コマンド」をそれぞれのサービスに送信し、
「決済失敗イベント」を発行する、というロジックを実装します。
ここまでのまとめ
この一連のプロセスにより、単なる技術的なエラー処理だったはずの補償トランザクションが、明確なビジネス価値を持つ、正式なユースケースへと昇華されるのです。
補償トランザクションとワークフロー
図中で点線で表現してるので、伝わってるとは思いますが、
このモデルは、【非同期かつ結果整合性】のオーケストレーション型です。
前提となるシナリオ:在庫確保後の決済失敗
①. 注文受付:注文サービス(オーケストレーター)がユーザーから注文リクエストを受け付けます。
②. 在庫確保:注文サービスが在庫サービスに指示を出し、商品の在庫引き当てに成功します。
③. 決済処理:注文サービスが決済サービスに指示を出しますが、ここでクレジットカードの与信エラーなどにより決済が失敗します。
この失敗をトリガーとして、以下の補償トランザクションフローが開始されます。
補償トランザクションのビジネスフロー
以下に、この補償トランザクションに対応した、ワークフローを表す図を貼っておきます。
ステップ1:オーケストレーターによる失敗の検知と補償開始
担当:注文サービス (オーケストレーター)
決済サービスからの失敗応答を受け取ります。
これにより、注文サーガ全体を失敗と判断し、自身の状態を「キャンセル処理中」に変更して、補償プロセスを開始します。
ステップ2:在庫の解放指示
担当:注文サービス → 在庫サービス
注文サービスは、すでに処理が成功してしまっている在庫サービスに対して、
「在庫を解放せよ」という補償コマンドを送信します。
ステップ3:在庫の差し戻し
担当:在庫サービス
指示を受け、引き当てていた商品の在庫を、他の顧客が購入できる状態へと差し戻します。
ステップ4:顧客へのキャンセル通知
担当:注文サービス → 通知サービス
注文サービスは、通知サービスに対して、「注文キャンセルの通知を送信せよ」という
コマンドを送信します。
この際、「決済エラーのため」といった理由も伝達します。
理由も合わせて伝達ってことは、イベントカテゴリーで言うと、
イベントソーシング式です。
ステップ5:最終状態の確定
担当:注文サービス
全ての補償処理が完了したことを確認し、最終的に注文の状態を「キャンセル済み」として自身に状態記録し、プロセスを終了します。
なぜプロセスの順番が前後しないのか?
正しく設計されたオーケストレーターは、非同期通信を使いつつも、
内部の状態管理によって、ビジネスプロセス全体の厳密な順序性を保証します。
これにより、プロセスの前後関係が崩れることを防いでいるのです。
オーケストレーターは、ステートマシンとして
「各ステップの成功を確認してから、次のステップに進む」
という厳格なルール(制約)に従います。
決済処理の完了という「イベント」を受け取ることが、配送指示へ進むための
遷移条件(ガードコンディション) になっているのです。
とにかく、サーガのような時には、ステートマシン図描いた方が絶対いい!!