オーケストレーターは必ず状態を持つ
オーケストレーションサーガのオーケストレーターは、必ず状態(ステート)を持ちます。
それは、オーケストレーターの最も本質的な責務です。
なぜオーケストレーターは状態を持つのか
オーケストレーターの役割は、複数のステップにまたがる長大なビジネスプロセスを管理することです。
プロセスが時間と共に進んでいくため、オーケストレーターは
「今、プロセスのどの段階にいるのか」を記憶しておく必要があります。
もし途中のステップで障害が発生した場合、オーケストレーターは
自身の状態(=どこまで成功したかの記録)を参照して、どのサービスに対して補償トランザクションを実行すべきかを判断
します。状態を持たなければ、何を取り消すべきかを知ることができません。
オーケストレーターの状態とは何か
オーケストレーターのロジックは、一般的に**ステートマシンとして実装されます。
サーガの一回の実行(インスタンス)ごとに、現在の状態がデータベースなどに永続化されます。
そして、次の例のように、非同期であろうとも、
オーケストレーション型のサーガの場合には、状態変化に時系列的順序という制約があるため、それがコレオグラフィ型サーガに比べて結合度が高い要因となります。
例:注文処理サーガの状態遷移
1. 状態: 開始前
注文リクエストを受け付け、サーガを開始。
2. 状態: 在庫確保済み
在庫サービスを呼び出し、成功したら自身の状態を更新。
3. 状態: 決済完了済み
決済サービスを呼び出し、成功したら状態を出す。
4. 状態: COMPLETED(完了) or FAILED(失敗)
全て成功すればCOMPLETEDに。
もし決済が失敗すれば、状態はFAILEDとなり、オーケストレーターは
「現在の状態はINVENTORY_RESERVEDだから、在庫サービスの補償トランザクションを呼び出さなければならない」
と判断します。
オーケストレーターのDBはステートマシン vs. イベントストア?どちらか
さて、オーケストレーターの所有するデータは、そもそも皆さんもよく見かけてきた
シンプルなステートマシンモデルなのか? それともイベントストア形式なのか?
どちらでしょうか?
順番にそれぞれの場合で見ていきましょう
1. ステートマシン・モデル
これは、サーガの現在の状態をスナップショットとして保存するシンプルな方法です。
実装
一般的なリレーショナルデータベース(RDB)やNoSQLに、以下のような「サーガ状態管理テーブル」を作成します。
動作状態
①. サーガが開始されると、新しい行が CurrentState='Initial' などで作成されます。
②. オーケストレーターは、このテーブルから現在の状態を読み取り、次のサービスにコマンド(例:ProcessPayment)を発行します。
③. サービスから成功の返信イベント(例:PaymentSuccessful)を受け取ると、対応する行の CurrentState を AwaitingShipment に更新します。
④. 失敗した場合は Failed に更新し、補償トランザクションを開始します。
このモデルは、「今どこにいるか」だけが分かれば良いオーケストレーターの責務と完全に一致しており、実装が非常にシンプルで直感的です。
2. イベントストア・モデル
これは、サーガで発生した全ての出来事(イベント)を時系列で追記していく方法です。
実装
イベントストア専用のDB(例: EventStoreDB)や、汎用DBにイベントを記録するテーブルを用意します。
動作状態
現在の状態を知るためには、特定の SagaInstanceId のイベントを最初からすべて再生(リプレイ)して、メモリ上で状態を復元します。
このモデルは、「なぜこの状態に至ったのか」という完全な監査証跡 が手に入るという強力なメリットがありますが、状態を復元するための処理が複雑になり、
オーケストレーターの責務としては過剰すぎです。
ここまでの結論
そのため、
オーケストレーター自体には、シンプルで十分なステートマシン・モデルが推奨されます。
リードモデルは原則不要
次に、オーケストレーター自体には、下図のようにコマンド・クエリ分離(CQRS)のようなリードモデルが必要でしょうか?
オーケストレーターの内部状態は、オーケストレーター自身が次のステップを判断するため
だけに使われます。
外部のクライアント(UIなど)が直接クエリするためのものではありません。
コマンドモデルのみで良い理由
オーケストレーターが永続化する状態は、まさに 「コマンドモデル」 に相当します。
つまり、「現在の状態」と「次のコマンドを発行するために必要なデータ」です。
これさえあれば、オーケストレーターは自身の責務を全うできます。
なぜリードモデルが不要なのか?
例えば、ユーザーが「自分の注文状況を知りたい」と思ったとします。
このリクエストに応答するのは、オーケストレーターの役割ではありません。
これは 「注文サービス」の役割です。
①. オーケストレーターは、サーガの進行に合わせて各サービスにコマンドを発行します。
②. 各サービス(注文サービス、決済サービスなど)は、自身の状態を変更し、必要に応じて自身のリードモデルを更新します。
③. ユーザーからのクエリは、API Gatewayなどを経由して、注文サービスのリードモデルに対して行われます。
このように、クエリへの応答責任は、あくまでそのドメインデータを所有する各マイクロサービスが負います。
オーケストレーターのDBを外部に公開してリードモデルとして使うことは、オーケストレーターと外部クライアントの間に不要な密結合を生んでしまうため、避けるべきです。
まとめ
オーケストレーターのDB
イベントストアである必要はなく、シンプルな「ステートマシン」管理テーブルで十分です。これにより、現在の状態を簡単に把握・更新できます。
イベントストアは、完全な監査証跡が必要など、特別な要件がある場合に検討する高度な選択肢です。
リードモデルの要否
オーケストレーター自体に、外部向けのリードモデルは不要です。
永続化するのは、次のアクションを決定するための
「コマンドモデル」(現在の状態と関連データ)のみで十分です。
ユーザーへの状態表示などのクエリ機能は、オーケストレーターではなく、各ドメインサービスが自身のリードモデルを用意して提供するのが責務分離の観点から正しい設計です。


