28 Sagaを使用したアプリケーションの開発

この章では、マイクロサービスの原則を使用して構築されたアプリケーションに、マイクロサービス間の効率的なトランザクションを組み込むことを可能にするSaga APIについて説明します。Saga APIは、Eclipse MicroProfileの長時間実行アクション(LRA)仕様に密接に準拠しながら実装されます。

28.1 Oracle Databaseを使用したSagaの実装

Oracle Databaseをプラットフォームとして使用して、Sagaベースのオンクラウド・アプリケーションまたはオンプレミス・アプリケーションを構築できます。分散トランザクションを処理するためにOracle Databaseを使用してSagaを実装する場合、Oracle Databaseに組み込まれた堅牢な基礎となるインフラストラクチャを活用できます。Sagaインフラストラクチャは、マイクロサービスのグローバルなアプリケーション状態を維持しすることで、開発とスループットを向上します。

Saga実装にOracle Databaseを使用する主な利点を次に示します:

  • Saga実装はデータベースに統合されるため、Sagaのコード化、デプロイおよびメンテナンスが簡単になります。

  • EclipseのLRA標準に従って簡易化されたSaga注釈により、Oracle Databaseを使用したマイクロサービス間のトランザクションを簡単に有効化できます

  • マイクロサービス全体でデータの一貫性を実現できます。

  • Sagaのロールバック時にロールバックを処理するために、データベースにはロックフリーの予約可能列に基づいた自動補正ロジックが用意されています。

  • Advanced Queuing (AQ)およびOracle Transactional Event Queues (TxEventQ)メッセージング・プラットフォームがOracle Databaseに組み込まれているため、データベース・トランザクションの一部としてマイクロサービスでメッセージとイベントを作成および使用できます。

  • PL/SQLやJavaアプリケーションなど、各種の言語でサポートされています。

  • Sagaフレームワークでは、アプリケーション・ペイロードをJSON型で表すことをお薦めします。

  • マイクロサービス間のトランザクションが必要になるアプリケーションの可用性とスケーラビリティが向上します。そのようなトランザクションは、マイクロサービスに変換するモノリス・アプリケーション(トランザクションが複数の境界付けられたコンテキストにまたがる可能性があるもの)では一般的なものです。境界付けられたコンテキストとは、その内側にビジネス・ドメイン・モデルが存在する明示的な境界のことです。

28.2 Oracle Saga Frameworkの概要

Oracle Saga Frameworkは、Oracle Databaseを使用して構築されたマイクロサービス・アプリケーション用のSagasを実装および管理する基盤を提供します。フレームワークには、管理インタフェースとクライアント・インタフェースが用意されています。管理インタフェースを使用すると、SagaアプリケーションでSaga参加者とメッセージ・ブローカを構成して管理できます。クライアント・インタフェースを使用すると、アプリケーションでSagaの開始、参加およびファイナライズができます。Saga参加者は、メッセージ・チャネルおよびメッセージ伝播を使用して自動的に構成されます。Sagaフレームワークは補正に対応していて、Sagaがロールバックされた場合に影響を受けるデータを自動的にロールバックできます。Sagaフレームワークには、PL/SQLパッケージ、ディクショナリ表およびデータベースでのSagasを円滑化するAdvanced Queuing (AQ)統合が含まれています。

Oracle Sagaフレームワークは、Oracle Databaseの2つの重要な機能を利用します。予約可能列が更新されたときに適切なメタ情報の記録を可能にする、予約可能列がサポートされているトランザクション・レイヤーがあります。予約可能列のサポートにより、補正トランザクションが自動的に起動され、Sagaがロールバックされたときに予約可能列の更新の状態が元に戻されます。予約可能列は、トランザクション同時実行性を向上するためにOracleが提供するロックフリー予約機能の一部です。

データベースに統合されているもう1つの重要なレイヤーは、Oracle Advanced Queuing (AQ)テクノロジで構築されたイベント・キューです。AQは、多様なマイクロサービス参加者を接続する非同期メッセージング・プラットフォームを提供します。Sagaフレームワークでは、ハブアンドスポーク・トポロジをデプロイして、Sagaイニシエータ、コーディネータおよび参加者を接続します。Sagaメッセージ・ブローカは、このトポロジではハブとして機能します。

28.3 Sagaフレームワークの機能

Sagaフレームワークは、Oracle DatabaseにSagaを実装するために次の機能を備えています。

  • アクティブなSagaとSaga参加者を追加および管理するためのPL/SQL管理インタフェース

  • アプリケーションがSagaフレームワークと相互作用し、データベースSagaに参加してファイナライズ(コミットまたはロールバック)するためのPL/SQLおよびJavaインタフェース

  • Oracleのマイクロサービスに特化したAQメッセージ伝播インフラストラクチャによって円滑化される、Sagaに関与する複数の参加者間の非同期メッセージ通信

  • 予約可能列の更新の記録および取り消されたSagaに対する変更をロールバックするトランザクションの自動補正を可能にするための予約可能列ベースのSagaファイナライズ(補正および完了)のサポート

  • ユーザーがSagaファイナライズを明示的に定義し、自動的に実行できるようにするためのユーザー定義のSagaファイナライズ(補正および完了)のサポート

  • 確実にサービス間メッセージを1回のみ配信し、紛失や重複を防止するための強力なメッセージ・セマンティクス

  • 個々のトランザクションをSaga IDにバインドするためのSaga IDのサポート

  • Sagaの参加者ごとのグローバルに一意な名前

  • システムにプロビジョニングされたSaga参加者のためのメッセージ・チャネルおよびメッセージ伝播の自動構成

  • Sagaの状態を維持するための各種ディクショナリ表

  • システム内のSagaおよびSaga参加者についてレポートするシステム定義のビュー

  • Eclipse Microprofile LRA仕様のエミュレーション

28.4 Sagaフレームワークの概念

Sagaの参加者イニシエータ

Saga参加者は、Sagaトポロジ内のスポーク(参加者マイクロサービス)を表し、Sagaを開始してオーケストレートするか、Sagaに参加するために登録します。参加者はメッセージ・ブローカ(ローカルまたはリモート)に関連付けられ、オプションでローカルのSagaコーディネータに関連付けられます。このドキュメントでは、Sagaを開始するSaga参加者を「イニシエータ」と表現し、非イニシエータ参加者を「参加者」と表現します。SagaイニシエータはSagaコーディネータに関連付けられている必要があります。初期バージョンでは、Sagaフレームワークは、イニシエータのローカルSagaコーディネータ(同じPDB)のみをサポートしています。一方、Saga参加者はコーディネータに関連付ける必要はなく、イニシエータに対してリモートにできます。

参加者は誰でもSagaを開始できます。Sagaイニシエータは、Sagaを開始し、非同期メッセージを送信することで他のマイクロサービス参加者を登録するマイクロサービスです。

次の旅行代理店の例では、参加者マイクロサービスはフライト・サービス、ホテル・サービスおよび自動車サービスです。参加者マイクロサービスは、Sagaにかわって1つ以上の参加者トランザクションを実行します。参加者トランザクションの補正アクションはローカルPDBに保持されます。参加者は、各自のSagaのローカル状態を管理します。

トランザクション・コーディネータ

トランザクション・コーディネータは、Sagaトポロジ内でトランザクション・マネージャとして機能します。Sagaコーディネータは、SagaイニシエータにかわってSaga状態を維持します。

Sagaトポロジには、複数のSagaコーディネータを指定できます。初期リリースのSagaフレームワークでは、Saga参加者は、参加者に対してローカル(同じPDBおよびスキーマ)であるコーディネータにのみ関連付けることができます。ただし、コーディネータは、ローカルまたはリモートのメッセージ・ブローカに関連付けることができます。

メッセージ・ブローカ

メッセージ・ブローカは仲介サービスとして機能し、Sagaトポロジ内のハブを表します。ブローカは、複数のSaga参加者とそのコーディネータの間のメッセージ伝播のためのメッセージ配信サービスを提供します。各Saga参加者またはコーディネータは、ローカルまたはリモートのいずれかである1つのブローカに関連付けられます。ブローカはSagaの状態を管理しません。

管理者インタフェースとクライアント・インタフェース

Sagaフレームワークには、DBMS_SAGA_ADMDBMS_SAGAの2つのPL/SQLパッケージがあります。

DBMS_SAGA_ADMパッケージは、Sagaフレームワークの管理インタフェースを提供します。管理インタフェースを使用すると、Sagaエンティティおよび進行中のSagaを管理できます。

DBMS_SAGAパッケージには、マイクロサービス・アプリケーションを構築するときにSagaを開始および完了するための開発者APIが用意されています。

Sagaを使用したマイクロサービスを作成しようとしているJava開発者は、このドキュメントで個別に定義されているSaga注釈の使用が必要になります。

Saga注釈

Sagasのイニシエータと参加者になるJavaアプリケーションは、開発の柔軟性と簡易性のためにSaga注釈を使用する必要があります。詳細は、このドキュメントの「Saga注釈を使用したJavaアプリケーションの開発」の項を参照してください。

アドバンスト・キューイング

Sagaインフラストラクチャでは、キューとイベント通知の間のメッセージ伝播などに、既存のOracle Advanced Queuing (AQ)テクノロジを利用します。AQは、Saga参加者およびコーディネータ用のトランザクション通信メカニズムです。AQには、Sagaに関与する複数の参加者間で非同期メッセージ通信を円滑にするために必要な配管チャネルおよびメッセージ・チャネルが用意されています。AQは、分散トランザクションなしで確実に1回かぎりのメッセージ伝播を提供します。

予約可能列

予約可能列は、自動補正機能を持つ列タイプです。予約可能列を使用すると、個々のデータベースで、参加者トランザクションの補正アクションを記録する予約ジャーナルを保持できます。補正アクションは、Sagaロールバックが発生した場合に予約可能列の値に対して自動的に実行され、ローカル・トランザクション変更を元に戻します。

参加者マイクロサービスが実行するsagaローカル・トランザクションは、データベース表の1つ以上の予約列を変更できます。Sagaでは、トランザクション変更時に予約可能列を利用して補正アクションを記録し、トランザクションの失敗時に補正アクションを自動的に起動します。予約可能列の補正アクションは、予約ジャーナルに記録されます。補正トランザクションにより、以前にロックされたリソースが解放されます。

ディクショナリ・ビュー

Sagaフレームワークには、システム内のSagaおよびSaga参加者についてレポートするシステム定義のビューが用意されています。システム定義のビューALL_MICROSERVICESCDB_MICROSERVICESDBA_MICROSERVICESおよびUSER_MICROSERVICESでは、システム内のSagaおよびSaga参加者をモニターできます。

次のビューには、システム内のすべての参加者が表示されます。

  • CDB_SAGA_PARTICIPANTS
  • DBA_SAGA_PARTICIPANTS
  • USER_SAGA_PARTICIPANTS

次のシステム定義のビューには、進行中のSagaの動的状態が表示されます:

  • CDB_SAGAS
  • DBA_SAGAS
  • USER_SAGAS

次のシステム定義のビューに、完了したSagaの状態が表示されます:

  • CDB_HIST_SAGAS
  • DBA_HIST_SAGAS
  • USER_HIST_SAGAS

完了したSagaに関する情報は30日間保持されます。この期間は、saga_hist_retentionデータベース・パラメータを使用して構成できます。

次のシステム定義のビューには、未完了のSagaが表示されます:

  • CDB_INCOMPLETE_SAGAS
  • DBA_INCOMPLETE_SAGAS
  • USER_INCOMPLETE_SAGAS

次のビューには、未完了のSagaの予約可能列の詳細が示されます:

  • CDB_SAGA_PENDING
  • DBA_SAGA_PENDING
  • USER_SAGA_PENDING

次のビューには、各Sagaの詳細が示されます:

  • CDB_SAGA_DETAILS
  • DBA_SAGA_DETAILS
  • USER_SAGA_DETAILS

次のビューには、Sagasの保留中のファイナライズ・アクションが示されます:

  • CDB_SAGA_FINALIZATION
  • DBA_SAGA_FINALIZATION
  • USER_SAGA_FINALIZATION

関連項目:

『Oracle Databaseリファレンス』ガイド

ファイナライズ・メソッド

すべての参加者がそれぞれの参加者トランザクションをコミット(完了)またはロールバック(補正)すると、Sagaはファイナライズされます。Saga commit()またはrollback()操作により、Sagaは完了します。Sagaフレームワークを使用すると、暗黙的な予約可能列ベースのファイナライズに加えて、アプリケーション固有のファイナライズも実装できます。

Eclipse Microprofile LRA仕様

Sagaフレームワークは、Eclipse Microprofile LRA仕様をエミュレートし、一定の制限付きで同等の機能を提供します。Sagaフレームワークの初期バージョンには、次の制限があります。

  • ネストされたSagaはサポートされていません。

  • Sagaイニシエータとコーディネータが共存します。

28.5 Sagaフレームワークの初期化

初期化パラメータ

データベース初期化パラメータ・ファイルinit.oraで、max_saga_durationパラメータは、sagaが未完了とみなされるまでの時間(秒)を指定します。このパラメータのデフォルト値は86400秒です。この構成可能な期間を超えるSagaは未完了とみなされ、dbms_saga.rollback_saga() APIを使用して終了する必要があります。Sagaイニシエータは、期間を超えたSagaを自動的にロールバックします。

init.oraファイルで、_use_saga_qtypパラメータを使用して、Sagaインフラストラクチャで使用されるメッセージ・キューイング・タイプを設定できます。デフォルトでは、_use_saga_qtypパラメータは0 (クラシックAQキューを使用)に設定されていますが、トランザクション・イベント・キュー(TEQ)に切り替える場合は、_use_saga_qtypパラメータを1に設定する必要があります。_use_saga_qtypパラメータの変更は、Sagaエンティティ(ブローカ、コーディネータまたは参加者)を作成する前に行う必要があります。Sagaエンティティの作成後に_use_saga_qtypパラメータを変更しても、Sagaで使用されるキューのタイプは変更されません。

アクセス制御のロール

Sagaフレームワークを使用すると、データベース管理者がデータベース・ユーザーに必要な権限を付与し、ユーザーによるSagaの管理と参加が可能になります。ユーザーには次のロールおよび権限を指定できます。

ユーザー・ロール アクセス

SAGA_ADM_ROLE

DBMS_SAGA_ADMパッケージからAPIを起動できます。このロールは、初期設定のためにSaga管理者にとって必要なものであり、 DBMS_SAGA_ADM APIへのフル・アクセスを提供します。Sagaフレームワークの設定例では、メールボックス・ユーザーMBにSAGA_ADM_ROLE権限が付与されます。

SAGA_PARTICIPANT_ROLE

このロールは、Saga参加者サービスに必要です。Sagaプリミティブは、SAGA_PARTICIPANTロールが付与されているユーザーのみが起動できます。

SAGA_CONNECT_ROLE このロールは、リモートのdblinkユーザーに付与されます。

SYSは、データ・ディクショナリのすべてのディクショナリ表およびユーザーがアクセスできるビューを所有します。

28.6 Sagaトポロジの設定

Sagaフレームワークには、DBA (SAGA_ADM_ROLE権限を保有)がSaga参加者、コーディネータおよびブローカの定義と管理に使用できる管理APIが用意されています。SYS.DBMS_SAGA_ADMというSaga PL/SQLパッケージは、Saga管理インタフェースを実装します。参加者は、SYS.DBMS_SAGA_ADMパッケージのプロシージャをスキーマおよびPDB内から起動する必要があります。

管理インタフェースには、Saga参加者およびブローカをプロビジョニングするために次のAPIが用意されています。

  • add_participant(): 参加者とそのコーディネータを追加します

  • add_broker(): ブローカを明示的に作成します

  • add_coordinator(): コーディネータを明示的に作成します

  • drop_participant(): 参加者を削除します

  • drop_coordinator(): コーディネータを削除します

  • drop_broker(): ブローカを削除します

システムにプロビジョニングされたSaga参加者は、メッセージ・チャネルおよびメッセージ伝播を使用して自動的に構成されます。参加者を追加すると、SYS.SAGA_PARTICIPANT$ディクショナリ表に対応するエントリが作成され、参加者の着信および送信のJavaトピックが作成されます。インバウンドおよびアウトバウンドのJavaトピックは、参加者の名前から導出されたシステム生成名でプロビジョニングされます。次の各項では、管理インタフェースの処理についてさらに詳しく説明します。

関連項目:

SYS.DBMS_SAGA_ADMパッケージAPIの詳細な説明は、DBMS_SAGA_ADMを参照してください

28.6.1 メッセージ・ブローカの追加

Sagaフレームワークでは、メッセージ・ブローカはSaga参加者からのメッセージを受信してSaga受信者に伝播するメッセージ配信サービスとして機能します。

Sagaフレームワークでは、dbms_saga_adm.add_broker() APIを使用してブローカをフレームワークに明示的に追加します。メッセージ・ブローカを作成すると、Saga参加者のメールボックスとして機能するJavaトピックが1つ作成されます。Javaトピックのシステム生成名の形式はSAGA$_<broker_name>_INOUTであり、メッセージ・ブローカ名(broker_name)はadd_broker()コールへの入力として指定された識別子です。

SAGA_ADM_ROLE (管理者)ロールはSagaメッセージ・ブローカの作成に必須です。

28.6.2 コーディネータの追加

Sagaコーディネータは、Sagaのトランザクション・マネージャとして機能します。Sagaコーディネータは、様々な参加者にわたってSagaの状態を管理します。

Sagaフレームワークでは、dbms_saga_adm.add_coordinator() APIを使用してコーディネータをフレームワークに追加します。

ノート:

add_participant()には引数としてコーディネータ名が必要であるため、add_coordinator() APIは、add_participant() APIを起動する前にコールする必要があります。準備ステップとプロビジョニング・ステップの説明は、「例: Sagaフレームワークの設定」を参照してください。

add_coordinator()インタフェースは、次を実行します。

  • コーディネータのインバウンドとアウトバウンドのメッセージ・チャネル用にシステム定義のAQキューを作成します。

  • コーディネータとメッセージ・ブローカの間に双方向のメッセージ伝播チャネルを確立します。

28.6.3 参加者の追加

参加者は、データベースSagaへの参加を希望するアプリケーションまたはマイクロサービスを表す名前付きエンティティです。参加者を追加すると、参加者とメッセージ・ブローカの間に双方向のメッセージ伝播チャネルが設定されます。

Sagaフレームワークでは、dbms_saga_adm.add_participant() APIを使用して参加者をフレームワークに追加します。

ノート:

add_participant()を起動する前に、参加者およびブローカのデータベース(PDB)に対して前提条件ステップを完了します。準備ステップとプロビジョニング・ステップの説明は、「例: Sagaフレームワークの設定」を参照してください。

add_participant()インタフェースは、次を実行します。

  • 参加者のインバウンドおよびアウトバウンドのメッセージ・チャネル用にシステム定義のJavaトピックを作成します。

  • 参加者とメッセージ・ブローカの間に双方向のメッセージ伝播チャネルを確立します。

28.6.4 参加者およびメッセージ・ブローカの管理

drop_participant()drop_coordinator()およびdrop_broker() APIを使用して、参加者、コーディネータおよびメッセージ・ブローカを削除できます。参加者、コーディネータまたはメッセージ・ブローカを削除すると、関連するJMSトピックも削除されます。

ノート:

  • 参加者を削除できるのは、進行中のSagaがなく、参加者の着信キューに保留中のメッセージがない場合のみです。

  • コーディネータを削除できるのは、コーディネータに関連付けられた参加者がない場合のみです。

  • メッセージ・ブローカを削除できるのは、登録済の参加者および保留中のメッセージがない場合のみです。

関連項目:

SYS.DBMS_SAGA_ADMパッケージの管理APIの詳細は、DBMS_SAGA_ADMを参照してください

28.6.5 メッセージ伝播

メッセージ伝播により、Sagaエンティティ(イニシエータ、参加者およびブローカ)間でメッセージが転送されます。参加者をSagaフレームワークに追加すると、メッセージ伝播が設定されます。メッセージ伝播ジョブは、参加者のアウトバウンド・トピックをブローカのINOUTトピックに接続し、ブローカのINOUTトピックを参加者のインバウンド・トピックに接続します。

  • 参加者のアウトバウンド・トピックをブローカのINOUTトピックに伝播するには、メッセージ伝播ジョブでadd_participant()インタフェースのdblink_to_brokerパラメータからのdblinkを使用します。

  • ブローカのINOUTトピックを参加者のインバウンド・トピックに伝播するには、メッセージ伝播ジョブでadd_participant()インタフェースのdblink_to_participantパラメータからのdblinkを使用します。

    特定の参加者へのメッセージの伝播は、その参加者宛のメッセージに対してのみ発生します。

28.6.6 ディクショナリ表について

グローバル一意識別子(GUID)は、すべてのSagaトランザクションを識別するために使用されます。sys.saga_finalization$ディクショナリ表には、Sagaの補正アクションを完了するために必要な個々のステップが記録されます。

いくつかのディクショナリ表では、参加者データベースでSagaトランザクションに関連付けられた状態(メタデータ)を追跡します。このような表は、次のとおりです。

  • sys.saga_message_broker$: この表には、Sagaブローカの情報が格納されます。行は、明示的なadd_broker()コールを使用してsaga_message_broker$表に挿入されます。ブローカはリモートまたはローカルにすることができ、この情報はsaga_message_broker$.remote列を使用して取得されます。

  • sys.saga_participant$: この表には、Sagaフレームワークの参加者およびコーディネータに関する情報が格納されます。行は、add_participant()またはadd_coordinator()コールを使用してsaga_participant$表に挿入されます。参加者はリモートまたはローカルにすることができ、この情報はsaga_participant$.remote列を使用して取得されます。

  • sys.saga$: sys.saga$表には、指定されたPDBで開始されるか(joinSaga()を使用して)参加されたSagaのエントリが格納されます。

  • sys.saga_finalization$: 参加者データベースのsaga_finalization$表には、参加者トランザクションの一部として更新された一意の予約可能表ごとに、順序番号および予約ジャーナル情報が記録されます。saga_finalization$表には、アプリケーション固有の補正に関する情報は保持されません。

  • sys.saga_participant_set$: Sagaイニシエータが、Saga参加者を登録するためにAQ JMSメッセージを送信します。Saga AQ JMSメッセージでは、特別なJMSメッセージ・プロパティを使用して、saga_idおよびその他のSaga属性を参加者に示します。sys.saga_participant_set$表のエントリは、Sagaに登録されている各参加者を追跡します。これらのエントリは、各参加者の登録およびファイナライズのステータスを追跡します。

  • sys.saga_pending$: この表には、タイムアウトしたSagaの予約ジャーナル補正情報が記録されます。Sagaインフラストラクチャではこの情報を使用して、Sagaを強制的にコミットまたはロールバックします。

  • sys.saga_errors$: この表には、様々なSagaに対応するエラー・メッセージが記録されます。

次の2つのデータベース・パラメータはsagaに影響します。

  • max_saga_duration: このデータベース・パラメータは、Sagaがアクティブであるとみなされ、未完了とマークされない最大時間(秒)を定義します。max_saga_durationはSaga期間のシステム・デフォルトであり、begin() APIを使用してこの値をオーバーライドできます。いずれかの参加者がSaga期間内にSagaをファイナライズできない場合、Sagaは未完了とマークされます。未完了のSagaは、dbms_saga.rollback_saga()インタフェースを使用してファイナライズできます。

  • saga_hist_retention: このデータベース・パラメータは、完了したSagaに関する情報を保持するための最大時間(日数)を定義します。saga_hist_retentionのデフォルト値は30日です。

28.6.7 例: Sagaフレームワークの設定

次の例では、参加者マイクロサービスのセットと1つのブローカを構成します。

準備ステップ

この例では、1つのブローカ、2つの参加者(TravelAgencyとAirline)およびそれぞれのコーディネータを設定して構成します。次の準備ステップおよびプロビジョニング・ステップに従うと、2つの参加者が含まれるSagaトポロジが作成されます。

ステップ1から7はbrokerPDBで、8から11はTravelAgencyで、12から15はAirlinePDBで実行されます。

  1. ブローカをホストするために、brokerPDBというプラガブル・データベース(PDB)をプロビジョニングまたは指定します。
  2. brokerPDBで、ブローカとそのJMSトピックを所有するメールボックス・ユーザーを作成します。例: MB
  3. ロールSAGA_ADM_ROLEをユーザーMBに付与します。
  4. プロキシ・ユーザーAirlineatMBを作成します(ロール: SAGA_CONNECT_ROLE)。
  5. プロキシ・ユーザーTravelAgencyatMBを作成します(ロール: SAGA_CONNECT_ROLE)。
  6. MBatAirlineスキーマを使用して、Airline PDBへのdblink LinkToAirlineを作成します。
  7. MBatTravelスキーマを使用して、Travel PDBへのdblink LinkToTravelAgencyを作成します。
  8. 旅行予約サービスをホストするために、TravelAgencyというPDBをプロビジョニングまたは指定します。
  9. TravelAgency PDBで、ユーザーTAを作成します。
  10. プロキシ・ユーザーMBatTravelAgencyを作成します(ロール: SAGA_CONNECT_ROLE)。
  11. TravelAgencyatMBスキーマを使用して、brokerPDBへのdblink LinktoBrokerを作成します。
  12. Airlineをホストするために、AirlinePDBというPDBをプロビジョニングまたは指定します。
  13. AirlinePDBで、ユーザーAirlineを作成します。
  14. プロキシ・ユーザーMBatAirlineを作成します(ロール: SAGA_CONNECT_ROLE)。
  15. AirlineatMBスキーマを使用して、brokerPDBへのdblink LinktoBrokerを作成します。

この例のディクショナリ・エントリに関連付けられているDBAビューおよびエントリは、DBA_SAGA_PARTICIPANTSビューにあります。DBA_SAGA_PARTICIPANTSビューおよび次のDBAビューの詳細は、データベース・リファレンス・ガイドを参照してください。

  • DBA_SAGAS

  • DBA_HIST_SAGAS

  • DBA_INCOMPLETE_SAGAS

  • DBA_SAGA_DETAILS

  • DBA_PARTICIPANT_SET

  • DBA_SAGA_FINALIZATION

  • DBA_SAGA_PENDING

  • DBA_SAGA_ERRORS

プロビジョニング・ステップ

BrokerPDB:

--Add a broker
dbms_saga_adm.add_broker(name=>'TravelBroker',  schema=>'MB');

TravelAgencyPDB:

--Add the Saga coordinator(local to the initiator)
 dbms_saga_adm.add_coordinator(
 coordinator_name => 'TACoordinator',
 dblink_to_broker => 'LinktoBroker',
 mailbox_schema => 'MB',
 broker_name => 'TravelBroker',
 dblink_to_coordinator => 'LinkToTravelAgency'
);
--Add the local Saga participant TravelAgency and its coordinator as below
 dbms_saga_adm.add_participant(
 participant_name=> 'TravelAgency',
 coordinator_name=> 'TACoordinator',
 dblink_to_broker=> 'LinktoBroker',
 mailbox_schema=> 'MB',
 broker_name=> 'TravelBroker',
 dblink_to_participant=> 'LinktoTravelAgency',
 callback_package => 'dbms_ta_cbk'
);

AirlinePDB:

--Add the local Saga participant Airline as below
 dbms_saga_adm.add_participant(
 participant_name=> 'Airline', 
 dblink_to_broker=> 'LinktoBroker',
 mailbox_schema=> 'MB',
 broker_name=> 'TravelBroker',
 dblink_to_participant=> 'LinktoAirline'
 callback_package => 'dbms_airline_cbk'
);
--Add a table with reservable column which maintains flight information
Create table flights(id NUMBER primary key,
 seats NUMBER reservable constraint flights_const check (seats > 0));

28.7 PL/SQLインタフェースを使用したSagaの管理

PL/SQLを使用して、中間層を必要とせずに、データベースでパッケージ化されたマイクロサービス・アプリケーションを開発します。

PL/SQLパッケージDBMS_SAGAを使用すると、PL/SQLを使用して、中間層でデータベースと通信する必要なく、データベースでパッケージ化されたマイクロサービス・アプリケーションを開発できます。DBMS_SAGAパッケージには、クライアント・プログラムでデータベースSagaを開始して相互作用できるようにするPL/SQLインタフェースが用意されています。

関連項目:

28.7.1 例: Saga PL/SQLプログラム

Sagaイニシエータ(TravelAgency)とSaga参加者(Airline)を表すPL/SQLサンプル・プログラムを次に示します。

例28-1 Sagaイニシエータ

declare
  saga_id RAW(16);
  request JSON;
begin
  saga_id := dbms_saga.begin_saga('TravelAgency');
  request := json('{"flight":"United"}');
  dbms_saga.send_request(saga_id, 'Airline', request);
end;
/

例28-2 Airline

create or replace package dbms_airline_cbk as
function request(saga_id in RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL) return JSON;
end dbms_airline_cbk;
/

create or replace package body dbms_airline_cbk as
function request(saga_id in RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL) return JSON as
response JSON;
tickets NUMBER;
BEGIN
  BEGIN
    select seats into tickets from flights where id = json_value(payload, '$.flight') for update;
    IF tickets > 0 THEN
      response := json('{"result":"success"}');
      update flights set seats = seats - 1 where id = json_value(payload, '$.flight');
    ELSE
      response := json('{"result":"failure"}');
    END IF; 
  EXCEPTION
      WHEN OTHERS THEN
        response := json('{"result":"failure"}');
  END;
    return response;
end;

end dbms_airline_cbk;
/

例28-3 TravelAgency

create or replace package dbms_ta_cbk as
procedure response(saga_id in RAW, saga_sender IN varchar2, payload IN JSON DEFAULT NULL);
end dbms_ta_cbk;
/

create or replace package body dbms_ta_cbk as

procedure response(saga_id in RAW, saga_sender IN varchar2, payload IN JSON DEFAULT NULL) as
booking_result VARCHAR2(10);
begin
  booking_result := json_value(payload, '$.result');
  IF booking_result = 'success' THEN
    dbms_saga.commit_saga('TRAVELAGENCY', saga_id);
  ELSE
    dbms_saga.rollback_saga('TRAVELAGENCY', saga_id);
  END IF;
end;
end dbms_ta_cbk;
/

28.8 Saga注釈を使用したJavaアプリケーションの開発

Sagasのイニシエータと参加者になるJavaアプリケーションは、開発の柔軟性と簡易性のためにSaga注釈を使用する必要があります。この項では、Javaアプリケーションで利用できるSaga注釈とLRA注釈の詳細について説明します。

ノート:

Sagaトポロジは、個別に作成する必要があります。

関連項目:

Sagaトポロジの作成の詳細は、「Sagaトポロジの設定」を参照してください

Saga注釈では、次の参加者タイプが使用されます。

Sagaイニシエータ

Sagaイニシエータは、Sagaのライフサイクルを担う特別なSaga参加者です。イニシエータがSagaを開始し、その他の参加者にSagaへの参加を招待し、Sagaをファイナライズします。イニシエータのみが、その他の参加者にSagaへの参加をリクエストするメッセージを送信できます。Sagaイニシエータは、Sagaコーディネータに関連付けられています。Sagaイニシエータは、MicroProfile LRA仕様で定義された標準の@LRA注釈を使用して、JAX-RSアプリケーションがSagaを開始できるようにします。Sagaフレームワークの@LRA@Completeおよび@Compensate注釈のサポートは、それに対応するMicroProfile LRA仕様のLRA注釈と同様のものです。LRA IDのかわりに、Sagaフレームワークの@LRA注釈では、Saga IDを初期化してアプリケーションに返します。イニシエータのJavaクラスは、このフレームワークに用意されているSagaInitiatorクラスを拡張する必要があります。

関連項目:

項タイトル"Sagaのインタフェースとクラス"の「SagaInitiatorクラス

Saga参加者

Saga参加者は、SagaイニシエータのリクエストでSagaに参加します。参加者はイニシエータと直接通信できますが、その他の参加者とは直接通信できません。Saga参加者は、@Request注釈を使用して、受信Sagaペイロードを処理するために呼び出したメソッドにマークを付けます。こうしたメソッドは、Sagaに必要なすべてのメタデータとペイロードが格納されるSagaMessageContextオブジェクトを使用します。@Request注釈付きのメソッドは、Sagaフレームワークがレスポンスで自動的にSagaイニシエータに送信するJSONオブジェクトを返します。参加者クラスは、SagaParticipantを拡張する必要があります。

関連項目:

項タイトル"Sagaのインタフェースとクラス"の「SagaParticipantクラス」と「SagaMessageContextクラス」

28.8.1 LRA注釈とSaga注釈

Sagaフレームワークは、LRAフレームワークをエミュレートし、同等の機能を提供します。次に、Sagaフレームワークで使用される注釈のリストを示します。

表28-1 LRA注釈とSaga注釈

注釈 説明:

@LRA

Sagaの場合、LRA注釈はSaga開始動作を制御します。

現在、Sagaは非同期モデルのみを使用して処理されます。現在、end=trueの使用はサポートされていません。一般に、Sagaのファイナライズは、@Response注釈の付いたメソッドからSaga.commitSaga()またはSaga.rollbackSaga()をコールして処理する必要があります。Sagaの実装では、注釈@LRA.type.NESTEDを除いて、LRA仕様に従った各種のLRA値がサポートされています。

Saga JAX-RSフィルタは、参加者(@Participant注釈を使用して記述される)のかわりに必要に応じて新しいSagaを初期化します。新しく作成したSagaのSaga IDは、仕様で定義されているようにヘッダー・パラメータ"Long-Running-Action"に挿入されます。

@LRA注釈の付いたメソッドは、SagaInitiatorクラスを拡張するクラスによって実装されている必要があります。これにより、@LRA注釈の説明にあるように、Sagaオブジェクトを自動インスタンス化してから、HTTPヘッダーにLRA IDを挿入できます。Sagaに参加するように参加者を招待するには、SagaクラスのsendRequest()メソッドを使用します。詳細は、Sagaインタフェースのメソッドを参照してください。イニシエータが、ある参加者に向けて同じSagaのsendRequest()を複数回起動した場合でも、その参加者はSagaに参加するのは1回のみです。

現時点では、Sagaフレームワークは、イニシエータのJAX-RSエンドポイント(JAX-RSフィルタを使用)に対して@LRAのみをサポートしています。beginSaga() APIを使用すると、Sagaを手動で開始できます。

@SagaConnection

@SagaConnection注釈が付いたメソッドは、Saga参加者のインスタンス化中にSagaプロデューサまたはコンシューマを作成するためのJDBC接続をフェッチするために使用されます。

@SagaConnection注釈が付いたメソッドを呼び出すたびに、JDBC接続の新しいインスタンスが生成されます。

ノート:

参加者に対してclose()がコールされると、@SagaConnectionで装飾されたメソッドをコールすることで流用された接続が閉じられます(connection.close()が起動されます)。この動作を変更するには、@SagaConnectionclosable()フィールドを変更します。

@SagaConnection(closable=true)
     public java.sql.Connection getConnection() {
     .....
     }

@Complete

@Completeは、SagaがコミットされたときにSagaフレームワークが自動的に起動するメソッドを示すLRA注釈です。

Sagaフレームワークは、注釈付きメソッドへの入力引数としてSagaMessageContextオブジェクトを提供しています。詳細は、「SagaMessageContextクラス」を参照してください。@Complete注釈付きメソッドのメソッド・シグネチャは、次のようになります。
public void airlineComplete(SagaMessageContext info)

追加のメソッド・シグネチャについては、この項の最後にある「ノート」を参照してください。

@Complete注釈付きメソッドは、Sagaのかわりにローカル・トランザクションの変更をファイナライズし、Saga補正を可能にするために保持していたSaga状態をクリアする必要があります。このメソッドは、データベース・アクションの実行に、SagaMessageContextクラスで使用可能な接続オブジェクトを使用する必要があります。また、このメソッドでは、データベース・トランザクションを明示的にコミットまたはロールバックしないでください。

@Compensate

@Compensateは、SagaがロールバックされたときにSagaフレームワークが自動的に起動するメソッドを示すLRA注釈です。

Sagaフレームワークは、注釈付きメソッドへの入力引数としてSagaMessageContextオブジェクトを提供しています。詳細は、「SagaMessageContextクラス」を参照してください。@Compensate注釈付きメソッドのメソッド・シグネチャは、次のようになります。
public void airlineCompensate(SagaMessageContext info)

追加のメソッド・シグネチャについては、この項の最後にある「ノート」を参照してください。

@Compensate注釈付きメソッドでは、Saga中に実行されたローカル・トランザクションを補正して、すべてのSaga状態をクリアする必要があります。このメソッドは、データベース・アクションの実行に、SagaMessageContextクラスで使用可能な接続オブジェクトを使用する必要があります。このメソッドでは、データベース・トランザクションを明示的にコミットまたはロールバックしないでください。

@Participant

@Participantは、Saga参加者にクラスをマップするメソッドを示すためのSaga固有の注釈です。

Saga参加者は、データベース内でdbms_saga_adm.add_participant() APIを使用して事前に定義しておく必要があります。参加者の名前は、追加のパラメータ(リスナーの数やパブリッシャの数など)を関連付けるためにプロパティ・ファイルでも使用されます。

ノート:

@SagaConnection@SagaParticipantで装飾されたクラスのインスタンス化を必要とする変数を使用する場合、@SagaParticipantdelay()フィールドをtrueに設定する必要があります。これにより、@SagaParticipantで装飾されたクラスに対してstart()メソッドがコールされるまで、Sagaプロデューサおよびコンシューマの作成が遅延します。

@Request

@Requestは、Sagaイニシエータからの着信リクエストを受信するメソッドを示すためのSaga固有の注釈です。

Sagaフレームワークは、注釈付きメソッドへの入力としてSagaMessageContextオブジェクトを提供します。参加者が複数のイニシエータと連動している場合は、それらを区別するために、オプションの送信者属性を指定できます(正規表現を使用できます)。

@Response

@Responseは、sendRequest() APIを使用してSagaに登録された参加者から、Sagaレスポンスを収集するメソッドを示すためのSaga固有の注釈です。

Sagaフレームワークは、注釈付きメソッドへの入力としてSagaMessageContextオブジェクトを提供します。イニシエータが複数の参加者と連動している場合は、それらを区別するために、オプションの送信者属性を指定できます(正規表現を使用できます)。

@BeforeComplete

@BeforeCompleteは、Saga固有の注釈です。Sagaがコミットされる前にSagaのファイナライズ中に呼び出されるメソッドを示します。

@BeforeComplete注釈が付いたメソッドは、Sagaによって実行されるロックフリー予約の自動完了の前に呼び出されます。

@BeforeCompleteの使用はオプションです。

@BeforeCompensate

@BeforeCompensateは、Saga固有の注釈です。Sagaがロールバックされる前にSagaのファイナライズ中に呼び出されるメソッドを示します。

@BeforeCompensate注釈が付いたメソッドは、Sagaによって実行されるロックフリー予約の自動補正の前に呼び出されます。

@BeforeCompensateの使用はオプションです。

@InviteToJoin

@InviteToJoinは、Saga固有の注釈です。イニシエータが、指定のSagaに参加するように特定の参加者にリクエスト(sendRequest() APIを使用)するときに呼び出されるメソッドを示します。

このメソッドがtrueを返す場合、参加者はSagaに参加します。それ以外の場合は、否定応答が返され、@Rejectが呼び出されます。

@InviteToJoinの使用はオプションです。

@Reject

@Rejectは、Saga固有の注釈です。次の場合に起動されるメソッドを示します。

  • 参加者が、@InviteToJoinで定義されたSagaへの参加を拒否しているとき。

  • @Requestで示されたメソッドで、参加者に未対応の例外が発生したとき。

@Reject注釈は、イニシエータに対してのみ適用できます。@Reject注釈が付いたメソッドは、イニシエータ・レベルでSagaのブックキーピングを実行できます。

ノート:

メソッドが関連付けられているすべての注釈には、3つのメソッド・シグネチャがサポートされます。たとえば、@Request注釈に対する次のメソッド・シグネチャを考えてみます。

  • ProcessPayload (SagaMessageContext info)

  • ProcessPayload (URI sagaId)

  • ProcessPayload (URI sagaId, URI parentId)

SagaフレームワークはNESTEDのSagaをサポートしていないため、parentIdは常にnullになります。これらの代替シグネチャを使用するために、ユーティリティ・メソッドgetSagaMessageContext()が用意されています。このメソッドにより、指定のSaga IDからSagaMessageContextオブジェクトを取得します。

ノート:

  • すべてのデータベース操作は、SagaMessageContextクラスにあるデータベース接続オブジェクトを使用して実行する必要があり、明示的なトランザクションのコミットまたはロールバックはサポートされていません。

  • 送信者値に複数の一致がある場合は、例外が発生します。

28.8.2 パッケージ化

Sagaフレームワークは、次の2つのライブラリで構成されています:

  • Sagaクライアント・ライブラリ

  • JAX-RSフィルタ

JAX-RSフィルタは、アプリケーションがJAX-RSアプリケーションであり、Sagasの開始に@LRA注釈を使用する場合にのみ必要になります。

この2つのライブラリに関連するMaven座標は次のとおりです:

Sagaクライアント・ライブラリ

<dependency>
  <groupId>com.oracle.database.saga</groupId>
  <artifactId>saga-core</artifactId>
  <version>${SAGA_VERSION}</version>
</dependency>

JAX-RSフィルタ

<dependency>
  <groupId>com.oracle.database.saga</groupId>
  <artifactId>saga-filter</artifactId>
  <version>${SAGA_VERSION}</version>
</dependency>

28.8.3 構成

JAX-RSフィルタ構成

次のJAX-RSフィルタのパラメータは、プロパティ・ファイル(sagafilter.propertiesapplication.propertiesのいずれかまたは両方)を使用して構成できます:

プロパティ名 説明:

osaga.filter.database.tnsAlias

tnsnames.oraファイルから使用するTNS別名

osaga.filter.database.walletPath

フィルタ接続に使用する必要があるウォレットへのパス

osaga.filter.database.tnsPath

tnsnames.oraファイルの場所へのパス

osaga.filter.initiator.publisherCount

Sagasの開始に使用されるパブリッシャ・プールでインスタンス化するパブリッシャの合計数

新しいSagaの開始が必要になるたびに、プールにある既存のパブリッシャが使用されます。Sagaが開始されると、パブリッシャは解放されてプールに戻されます。

osaga.filter.initiator.name

osaga.filter.initiator.nameプロパティは、そのフィルタがイニシエータのかわりに役割を果たすため、@Participant注釈値と一致する必要があります。

ノート:

Saga JAX-RSフィルタは、環境内で唯一のJAX-RS LRAフィルタであることが必要です。複数のLRAフィルタがあると、不整合や予測できない結果が発生する可能性があります。

参加者構成

次の参加者ごとのパラメータは、プロパティ・ファイル(osaga.app.propertiesapplication.propertiesのいずれかまたは両方):を使用して構成できます

プロパティ名 説明:

osaga.<participant_name>.tnsAlias

tnsnames.oraファイルから使用するTNS別名

ノート:

@SagaConnectionを使用する場合、このパラメータは必要ありません。

osaga.<participant_name>.walletPath

参加者接続に使用する必要があるウォレットへのパス

ノート:

@SagaConnectionを使用する場合、このパラメータは必要ありません。

osaga.<participant_name>.tnsPath

tnsnames.oraファイルの場所へのパス

ノート:

@SagaConnectionを使用する場合、このパラメータは必要ありません。

osaga.<participant_name>.numListeners

この参加者に割り当てられているキュー・パーティションごとのリスナーの数。

リスナーは、受信メッセージの処理に使用されます。実際には、参加者またはイニシエータに構成されているリスナーの数は、それと通信できるその他の参加者とイニシエータの数に応じて異なります。この数は、ペイロードの処理時間が長くなっているときや、同時リクエストの数が増えたときなど、その他の要因に応じて増やすことが必要になる場合があります。

osaga.<participant_name>.numPublishers

パブリッシャは、参加者にリクエストを送信する必要があります。実際には、パブリッシャの数は、予想される同時Sagasの数に応じて異なります。

<participant_name>は、@Participant注釈から導出されます。たとえば、@Participant(name="TravelAgency")のマークが付いたクラスは、osaga.travelagency接頭辞を使用して構成されます(例: osaga.travelagency.numListeners=2)。

ノート:

numListenersnumPublishersは独立したエンティティです。numListenersは、着信リクエストに応答するスレッドの数を表します。numPublishersは、イニシエータのパブリッシャ・スレッド数を表します。イニシエータごとに、信頼性のあるスループットのために、numListeners=numPublishersを設定することが理想的です。

28.8.4 Sagaインタフェースとクラス

Sagaインタフェースとクラスでは、Sagaのサポートに必要な機能を記述します。この項では、Javaアプリケーションのインタフェースおよびクラスについて説明します。

28.8.4.1 Sagaインタフェース

Sagaインタフェースは、データベースSagaに参加、完了およびメタデータのリクエストのための手段を提供します。Sagaインタフェースは、次のメソッドを提供しています:

sendRequest(String recipient, String payload)メソッド

構文:
* @param recipient - name of the participant to be enrolled
* @param payload - saga payload
public void sendRequest(String recipient, String payload)

説明: sendRequest(String recipient,String payload)メソッドは、イニシエータ・レベルで呼び出され、参加者にメッセージ(ペイロード)を送信します。参加者がSagaに参加すると、イニシエータは@Response注釈が付いたメソッドを呼び出します。それ以外の場合、イニシエータは@Reject注釈が付いたメソッドを呼び出します。

ノート:

sendRequest(String recipient,String payload)メソッドは、Saga注釈付きメソッドの有効範囲外で使用されると、デフォルトでトランザクションを自動的にコミットします。トランザクション境界のファイングレイン制御は、sendRequest(java.sql.Connection connection, String recipient, String payload)メソッドを使用することで実現できます。

sendRequest(AQjmsSession session, TopicPublisher publisher, String recipient, String payload)メソッド

ノート:

このメソッドは非推奨です。かわりに、sendRequest(java.sql.Connection connection, String recipient, String payload)メソッドを使用します。

構文:
* @param session - user supplied saga session
* @param publisher - user supplied topic publisher
* @param recipient - name of the participant to be enrolled
* @param payload - saga payload
public void sendRequest(AQjmsSession session, TopicPublisher publisher, String recipient, String payload)

説明: このメソッドは、sendRequest(String recipient,String payload)と同じですが、パラメータAQjmsSessionTopicPublisherを使用しています。

AQjmsSessionセッションとTopicPublisherパブリッシャのパラメータ値が同じ場合は、単一のデータベース・トランザクションで複数のsendRequest()コールを実行できます。その他のデータベース操作(DMLなど)も同じトランザクションに含めることができますが、その場合は、AQjmsSessionクラスのgetDBConnection()メソッドを使用して取得したAQjmsSessionセッションに埋め込まれているデータベース接続を使用します。トランザクションは、AQjmsSessionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

ノート:

TopicPublisher (パブリッシャ)インスタンスは、SagaInitiatorクラスで宣言されたgetSagaOutTopicPublisher (AQjmsSessionセッション)メソッドを使用して取得されます。

sendRequest(java.sql.Connection connection, String publisher, String recipient, String payload)メソッド

構文:
* @param connection - user supplied JDBC connection
* @param publisher - user supplied topic publisher
* @param recipient - name of the participant to be enrolled
* @param payload - saga payload
public void sendRequest(java.sql.Connection connection, String publisher, String recipient, String payload)

説明: このメソッドは、sendRequest(String recipient,String payload)と同じですが、パラメータjava.sql.Connection connectionpublisherを使用します。

指定されたjava.sql.Connection接続を使用する場合は、単一のデータベース・トランザクションで複数のsendRequest()コールを実行できます。その他のデータベース操作(DMLなど)も、指定されたデータベース接続を使用する場合、同じトランザクションに含めることができます。トランザクションは、Connectionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

getSagaId()メソッド

構文:
public String getSagaId()

説明: getSagaId()メソッドは、SagaInitiatorクラスのメソッドbeginSaga()またはgetSaga(String sagaId)を使用してインスタンス化されたSagaオブジェクトに関連付けられたSaga識別子を返します。

commitSaga()メソッド

構文:
public void commitSaga()

説明: commitSaga()メソッドは、Sagaをコミットするためにイニシエータによって呼び出されます。その実行の一環として、@BeforeComplete@Completeの注釈が付いたメソッドは、イニシエータ・レベルと参加者レベルの両方で呼び出されます。予約可能列の操作があれば、その操作は、@BeforeCompleteコールと@Completeコールの間でファイナライズされます。

ノート:

commitSaga()メソッドは、Saga注釈付きメソッドの有効範囲外で使用される場合、デフォルトでトランザクションを自動コミットします。トランザクション境界のファイングレイン制御は、commitSaga(java.sql.Connection connection)メソッドを使用することで実現できます。

commitSaga(AQjmsSession session)メソッド

ノート:

このメソッドは非推奨です。かわりに、commitSaga(java.sql.Connection connection)メソッドを使用します。

構文:
* @param session - user supplied saga session
public void commitSaga(AQjmsSession session)

説明: このメソッドは、commitSaga()メソッドと同じですが、AQjmsSessionセッションを使用します。

その他のデータベース操作(DMLなど)も同じトランザクションに含めることができますが、その場合は、AQjmsSessionクラスのgetDBConnection()メソッドを使用して取得したAQjmsSessionセッションに埋め込まれているデータベース接続を使用します。トランザクションは、AQjmsSessionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

commitSaga(java.sql.Connection connection)メソッド

構文:
* @param connection - user supplied JDBC connection
public void commitSaga(java.sql.Connection connection)

説明: このメソッドは、commitSaga()メソッドと同じですが、java.sql.Connection接続を使用します。

その他のデータベース操作(DMLなど)も、指定されたデータベース接続を使用する場合、同じトランザクションに含めることができます。トランザクションは、Connectionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

commitSaga(boolean force)メソッド

構文:
* @param force - force commit flag
public void commitSaga(boolean force)

説明: このメソッドは、commitSaga()メソッドと同じですが、forceフラグを使用します。

forceフラグは、Saga参加者が特別な状況で使用し、Sagaをローカルにコミットし、Sagaイニシエータからのファイナライズを待機することなくSagaコーディネータに通知します。

commitSaga(AQjmsSession session, boolean force)メソッド

ノート:

このメソッドは非推奨です。かわりに、commitSaga(java.sql.Connection connection, boolean force)メソッドを使用します。

構文:
* @param session - user supplied saga session
* @param force - force commit flag
public void commitSaga(AQjmsSession session, boolean force)

説明: このメソッドは、commitSaga()メソッドと同じですが、AQjmsSessionセッションとforceフラグを使用します。これは、commitSaga(AQjmsSession session)メソッドとcommitSaga(boolean force)メソッドの機能を組み合せます。

ノート:

メソッドcommitSaga(AQjmsSession session)commitSaga(boolean force)およびcommitSaga(AQjmsSession session, boolean force)は、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

commitSaga(java.sql.Connection connection, boolean force)メソッド

構文:
* @param connection - user supplied JDBC connection
* @param force - force commit flag
public void commitSaga(java.sql.Connection connection, boolean force)

説明: このメソッドは、commitSaga()メソッドと同じですが、java.sql.Connection接続とforceフラグを使用します。これは、commitSaga(java.sql.Connection connection)メソッドとcommitSaga(boolean force)メソッドの機能を組み合せます。

ノート:

メソッドcommitSaga(java.sql.Connection connection)commitSaga(boolean force)およびcommitSaga(java.sql.Connection connection, boolean force)は、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

rollbackSaga()メソッド

構文:
public void rollbackSaga()

説明: rollbackSaga()メソッドはSagaをロールバックし、@BeforeCompensate@Compensateの注釈が付いたメソッドを呼び出します。予約可能列の操作があれば、その操作は、@BeforeCompensateコールと@Compensateコールの間でファイナライズされます。

ノート:

rollbackSaga()メソッドは、Saga注釈付きメソッドの有効範囲外で使用される場合、デフォルトでトランザクションを自動コミットします。トランザクション境界のファイングレイン制御は、rollbackSaga(java.sql.Connection connection)メソッドを使用することで実現できます。

rollbackSaga(AQjmsSession session)メソッド

ノート:

このメソッドは非推奨です。かわりに、rollbackSaga(java.sql.Connection connection)メソッドを使用します。

構文:
* @param session - user supplied saga session
public void rollbackSaga(AQjmsSession session)

説明: このメソッドは、rollbackSaga()メソッドと同じですが、AQjmsSessionセッションを使用します。

その他のデータベース操作(DMLなど)も同じトランザクションに含めることができますが、その場合は、AQjmsSessionクラスのgetDBConnection()メソッドを使用して取得したAQjmsSessionセッションに埋め込まれているデータベース接続を使用します。トランザクションは、AQjmsSessionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

ノート:

rollbackSaga(AQjmsSession session)メソッドは、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

rollbackSaga(java.sql.Connection connection)メソッド

構文:
* @param connection - user supplied JDBC connection
public void rollbackSaga(java.sql.Connection connection)

説明: このメソッドは、rollbackSaga()メソッドと同じですが、java.sql.Connection接続を使用します。

その他のデータベース操作(DMLなど)も、指定されたデータベース接続を使用する場合、同じトランザクションに含めることができます。トランザクションは、Connectionのメソッドcommit()またはrollback()を使用すると、コミットまたはロールバックできます。

ノート:

rollbackSaga(java.sql.Connection connection)メソッドは、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

rollbackSaga(boolean force)メソッド

構文:
* @param force - force rollback flag
public void rollbackSaga(boolean force)

説明: このメソッドは、rollbackSaga()メソッドと同じですが、forceフラグを使用します。

forceフラグは、Saga参加者が特別な状況で使用し、Sagaをローカルにロールバックし、Sagaイニシエータからのファイナライズを待機することなくSagaコーディネータに通知します。

rollbackSaga(AQjmsSession session, boolean force)メソッド

ノート:

このメソッドは非推奨です。かわりに、rollbackSaga(java.sql.Connection connection, boolean force)メソッドを使用します。

構文:
* @param session - user supplied saga session
* @param force - force commit flag
public void rollbackSaga(AQjmsSession session, boolean force)

説明: このメソッドは、rollbackSaga()メソッドと同じですが、AQjmsSessionセッションとforceフラグを使用します。これは、rollbackSaga(AQjmsSession session)メソッドとrollbackSaga(boolean force)メソッドの機能を組み合せます。

ノート:

メソッドrollbackSaga(AQjmsSession session)rollbackSaga(boolean force)およびrollbackSaga(AQjmsSession session, boolean force)は、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

rollbackSaga(java.sql.Connection connection, boolean force)メソッド

構文:
* @param connection - user supplied JDBC connection
* @param force - force commit flag
public void rollbackSaga(java.sql.Connection connection, boolean force)

説明: このメソッドは、rollbackSaga()メソッドと同じですが、java.sql.Connection接続とforceフラグを使用します。これは、rollbackSaga(java.sql.Connection connection)メソッドとrollbackSaga(boolean force)メソッドの機能を組み合せます。

ノート:

rollbackSaga(java.sql.Connection connection)rollbackSaga(boolean force)およびrollbackSaga(java.sql.Connection connection, boolean force)は、Saga注釈付きメソッドの有効範囲外でのみ使用できます。Saga注釈付きメソッドは、デフォルトで新しいトランザクションを開始します。このトランザクションは、メソッドの実行後にコミットまたはロールバックされます。

isSagaFinalized()メソッド

構文:
public boolean isSagaFinalized()

説明: isSagaFinalized()メソッドは、イニシエータ・レベルまたは参加者レベルで呼び出すことで、Sagaがいずれかのファイナライズ状態に達したかどうかをチェックできます。Sagaの状態がJOININGJOINEDまたはTIMEDOUTの場合はfalseを返し、それ以外の場合はtrueを返します。

beginSagaTransaction(AQjmsSession session, TopicPublisher publisher)メソッド

ノート:

このメソッドは非推奨です。かわりに、beginSagaTransaction(java.sql.Connection connection)メソッドを使用します。

構文:
* @param session - user supplied saga session
* @param publisher - user supplied topic publisher
public void beginSagaTransaction(AQjmsSession session, TopicPublisher publisher)

説明: beginSagaTransaction(AQjmsSession session, TopicPublisher publisher)メソッドは、新しいSagaトランザクションを開始するために、Saga注釈付きメソッド(@LRA annotatedメソッドなど)の有効範囲外で使用されます。イニシエータ・レベルでのみ呼出しできます。Sagaトランザクション内で実行されるすべての操作は、AQjmsSessionのメソッドcommit()またはrollback()を明示的にコールすることで、コミットまたはロールバックされます。beginSagaTransaction(AQjmsSession session, TopicPublisher publisher)コールには、Sagaトランザクションを完了するための対応するendSagaTransaction()コールが必要です。

ノート:

beginSagaTransaction(AQjmsSession session, TopicPublisher publisher)メソッドは、その他のSaga関連メソッドのcommitSaga()rollbackSaga()などを呼び出すことでシリアライズされます。

beginSagaTransaction(java.sql.Connection connection, String publisher)メソッド

構文:
* @param connection - user supplied JDBC connection
* @param publisher - user supplied topic publisher
public void beginSagaTransaction(java.sql.Connection connection, String publisher)

説明: beginSagaTransaction(java.sql.Connection connection)メソッドは、新しいSagaトランザクションを開始するために、Saga注釈付きメソッドの有効範囲外(@LRA注釈付きメソッドの下など)で使用します。イニシエータ・レベルでのみ呼出しできます。Sagaトランザクション内で実行されるすべての操作は、Connectionのメソッドcommit()またはrollback()を明示的にコールすることで、コミットまたはロールバックされます。beginSagaTransaction(java.sql.Connection connection)コールには、Sagaトランザクションを完了するための対応するendSagaTransaction()コールが必要です。

ノート:

beginSagaTransaction(java.sql.Connection connection)メソッドは、その他のSaga関連メソッドのcommitSaga()rollbackSaga()などを呼び出すことでシリアライズされます。

endSagaTransaction()メソッド

構文:
public void endSagaTransaction()

説明: endSagaTransaction()メソッドは、beginSagaTransaction()コールで開始したSagaトランザクションを終了します。endSagaTransaction()メソッドは、Sagaトランザクションがロック・フラグをtrueに設定して開始されていた場合、Sagaのロックを解除します。

28.8.4.2 SagaMessageContextクラス

SagaMessageContextオブジェクトは、Saga関連イベントのSaga注釈が付いたメソッドに引数として渡されます。SagaMessageContextオブジェクトは、Saga注釈付きメソッドをトリガーしたSagaに関連付けられたメタデータをフェッチするために使用できます。

SagaMessageContextクラスには、次のメソッドがあります。

getSagaId()メソッド

構文: public String getSagaId()

説明: getSagaId()メソッドは、Sagaの識別子を返します。

getSender()メソッド

構文: public String getSender()

説明: getSender()メソッドは、メッセージ送信者の名前を返します。

getPayload()メソッド

構文: public String getPayload()

説明: getPayload()メソッドは、Sagaメッセージに関連付けられたペイロードを返します。

getConnection()メソッド

構文: public java.sql.Connection getConnection()

説明: getConnection()メソッドは、Saga注釈付きメソッドで、Sagaメッセージ・リスナー・スレッドのデータベース接続オブジェクトを返すために使用できます。イニシエータまたは参加者レベルで使用できます。この接続は、アプリケーションでデータベース操作を実行するために使用できます。

Saga注釈付きメソッドでは、明示的なcommit()またはrollback()はサポートされていません。

getSaga()メソッド

構文: public Saga getSaga()

説明: getSaga()メソッドは、Saga注釈付きメソッドをトリガーしたSagaに関連付けられたSagaオブジェクトを返します。このSagaオブジェクトは、そのSagaのファイナライズや、そのSagaのメタデータのリクエストに使用できます。

28.8.4.3 SagaParticipantクラス

SagaParticipantクラスは、Saga参加者インスタンスの作成と管理のための手段を提供します。Saga参加者は、DBMS_SAGA_ADMパッケージで提供されるadd_participant() PL/SQL APIを使用して作成されます。

関連項目:

SYS.DBMS_SAGA_ADMパッケージAPIの詳細な説明は、DBMS_SAGA_ADMを参照してください。

SagaParticipantクラスは、次のメソッドを公開しています。

addSagaMessageListener()メソッド

構文:
public void addSagaMessageListener()

説明: addSagaMessageListener()メソッドによって、Saga参加者はメッセージ・リスナー・スレッドを追加できます。Sagaメッセージ・リスナー・スレッドを追加することで、Sagaメッセージの処理時の同時実行性が向上します。

ノート:

addSagaMessageListener()では、メッセージ・リスナー・スレッドが1つのみ追加されます。複数のリスナーを一度に追加する場合は、addSagaMessageListener(int numListeners)メソッドを使用する必要があります。

addSagaMessageListener(int numListeners)メソッド

構文:
* @param numListeners - number of listeners to add
public void addSagaMessageListener(int numListeners)

説明: addSagaMessageListener(int numListeners)メソッドを使用すると、Saga参加者は、さらにnumListenersと同数のメッセージ・リスナー・スレッドを追加できます。Sagaメッセージ・リスナー・スレッドを追加するほど、多くのSagaメッセージを同時に処理できるようになります。

removeSagaMessageListener()メソッド

構文:
public void removeSagaMessageListener()

説明: removeSagaMessageListener()メソッドによって、Saga参加者はメッセージ・リスナー・スレッドを削除できます。負荷が低い状況下では、Sagaメッセージ・リスナー・スレッドを減らすことでシステム・リソースを解放できます。

ノート:

removeSagaMessageListener()メソッドでは、メッセージ・リスナー・スレッドが1つのみ削除されます。複数のリスナーを一度に削除する場合は、removeSagaMessageListener (int numListeners)メソッドを使用する必要があります。

removeSagaMessageListener(int numListeners)メソッド

構文:
* @param numListeners - number of listeners to remove
public void removeSagaMessageListener(int numListeners)

説明: removeSagaMessageListener(int numListeners)メソッドによって、Saga参加者はnumListeners個のメッセージ・リスナー・スレッドを削除できます。負荷が低い状況下では、Sagaメッセージ・リスナー・スレッドを減らすことでシステム・リソースを解放できます。

removeAllSagaMessageListeners()メソッド

構文:
public void removeAllSagaMessageListeners()

説明: Saga参加者は、removeAllSagaMessageListeners()メソッドですべてのメッセージ・リスナー・スレッドを削除できます。このメソッドを呼び出すと、Sagaメッセージの受信時にSaga固有の注釈が付いたメソッドが実行されなくなります。このメソッドは、Saga注釈が付いたメソッドのビジネス・ロジックの変更が必要な場合に使用できます。未処理のSagaメッセージは、Sagaメッセージ・キューに保持され、メッセージ・リスナーの再起動後に処理できます。

getSagaMessageListenerCount()メソッド

構文:
public int getSagaMessageListenerCount()

説明: getSagaMessageListenerCount()メソッドは、Saga参加者に関連付けられたSagaメッセージ・リスナー・スレッドの合計数を返します。

ノート:

AQキュー設定の場合、getSagaMessageListenerCount()は、Saga参加者の単一パーティションに関連付けられたメッセージ・リスナー・スレッド数を返します。合計メッセージ・リスナー・スレッド数は、getSagaMessageListenerCount() * numPartitionsによって取得されます。これは、add_participantコール時にされています。参加者に構成されたパーティションの数は、DBA_SAGA_PARTITIPANTSディクショナリ・ビューを使用することでも問合せできます。

getSaga(String sagaId)メソッド

構文:
* @param sagaId - Identifier for the saga to be retrieved
public Saga getSaga(String sagaId)

説明: getSaga(String sagaId)メソッドは、指定したSaga識別子のSagaに関連付けられているSagaオブジェクトを返します。

close()メソッド

構文:
public void close()

説明: close()メソッドは、Saga参加者に関連付けられたすべてのメッセージ・リスナー・スレッドと接続を削除します。Saga参加者がクローズされると、それ以降、Saga注釈が付いたメソッドは呼び出されません。Saga参加者は、データベースSagaに対して参加、完了およびメタデータのリクエストはできません。その参加者の保留中のSagaメッセージは保持され、その後で使用できます。

28.8.4.4 SagaInitiatorクラス

SagaInitiatorクラスは、Sagaイニシエータ・インスタンスの作成と管理のための手段を提供します。Sagaイニシエータは、dbms_saga_admパッケージで提供されるadd_participant() PL/SQL APIを使用して作成されます。SagaInitiatorクラスは、SagaParticipantクラスで指定されたすべてのメソッドを継承します。さらに、SagaInititorクラスは、次のメソッドを公開しています。

beginSaga()メソッド

構文:
public Saga beginSaga()

説明: beginSaga()メソッドは、Sagaを起動してsagaIdを返します。sagaIdを使用すると、その他の参加者を登録できます。

ノート:

beginSaga()メソッドは、デフォルトの84600秒のタイムアウトを使用してSagaを起動します。タイムアウトのファイングレイン制御は、beginSaga(int timeout)を使用することで実現できます。

beginSaga(int timeout)メソッド

構文:
* @param timeout - saga timeout
public Saga beginSaga(int timeout)

説明: beginSaga(int timeout)メソッドは、ユーザーが指定したタイムアウトでSagaを起動してSaga識別子を返します。Saga識別子を使用すると、その他の参加者を登録できます。ノート: 指定したタイムアウトの前にSagaがファイナライズされない場合、Sagaはデータベース初期化パラメータ_saga_timeout_operation_type(default=rollback)に応じて自動的にファイナライズします。

addSagaMessagePublishers(int numPublishers)メソッド

構文:
* @param numPublisher - number of publishers to add
public void addSagaMessagePublishers(int numPublishers)

説明: addSagaMessagePublishers(int numPublishers)メソッドを使用すると、Sagaイニシエータは、さらにnumPublisherと同数のメッセージ・パブリッシャ・スレッドを追加できます。Sagaメッセージ・パブリッシャ・スレッドを追加するほど、イニシエータは多くのSagasを同時に作成および管理できます。

removeSagaMessagePublishers(int numPublishers)メソッド

構文:
* @param numPublisher - number of publishers to remove
public void removeSagaMessagePublishers(int numPublishers)

説明: removeSagaMessagePublishers(int numPublishers)メソッドを使用すると、SagaイニシエータはnumPublishers個のメッセージ・パブリッシャ・スレッドを削除できます。負荷が低い状況下では、Sagaメッセージ・パブリッシャ・スレッドを減らすことでシステム・リソースを解放できます。

removeAllSagaMessagePublishers()メソッド

構文:
public void removeAllSagaMessagePublishers()

説明: Sagaイニシエータは、removeAllSagaMessagePublishers()メソッドですべてのメッセージ・パブリッシャ・スレッドを削除できます。今後イニシエータがSagaを開始または管理しない場合は、すべてのメッセージパブリッシャ・スレッドを削除できます。Sagaメッセージ・パブリッシャを削除しても、それまでに公開された保留中のメッセージに影響はありません。

getSagaMessagePublisherCount()メソッド

構文:
public int getSagaMessagePublisherCount()

説明: getSagaMessagePublisherCount()メソッドは、Sagaイニシエータに関連付けられたSagaメッセージ・パブリッシャ・スレッドの合計数を返します。

ノート:

AQキュー設定の場合、getSagaMessagePublisherCount()メソッドは、Saga参加者の単一パーティションに関連付けられたメッセージ・パブリッシャ・スレッド数を返します。合計メッセージ・パブリッシャ・スレッド数は、getSagaMessagePublisherCount() * numPartitionsによって取得されます。これは、add_participant()コール時に定義されています。参加者に構成されたパーティションの数は、DBA_SAGA_PARTITIPANTSディクショナリ・ビューを使用することでも問合せできます。

getSagaOutTopicPublisher(AQjmsSession session, int partition)メソッド

構文:
* @param session - user supplied saga session 
* @param partition - partition for which the publisher is needed
public TopicPublisher getSagaOutTopicPublisher(AQjmsSession session, int partition)

説明: getSagaOutTopicPublisher(AQjmsSession session, int partition)メソッドは、Sagaトピック・パブリッシャ・インスタンスを返します。TopicPublisherインスタンスは、手動でトランザクション境界を管理するためのユーザー定義のAQjmsSessionを受け入る各種メソッドにパラメータとして指定できます。

ノート:

AQキュー設定の場合、パーティションの値はadd_participantコールで指定された[1-numPartitions]にすることもできます。TxEventQキュー設定の場合、パーティションの値は常に1にする必要があります。

28.8.5 サンプル・プログラム

次の例では、単純なTravel Agency Sagaアプリケーションを通じて、重要なSaga注釈のサブセットの使用について説明します。Travel Agencyは、実際の旅行代理店をエミュレートして、そのエンド・ユーザーのためにAirline (航空便)の予約を実行します。Travel Agencyは、JAX-RSアプリケーション(イニシエータ)であり、1つの参加者Airlineを使用します。Airlineは、標準Javaアプリケーションとして実装されています(JAX-RSではありません)。

Travel AgencyとAirlineは、それらのタスクの実行にとって適切なSaga注釈を活用します。この例では、TravelAgencyがSagaイニシエータで、エンド・ユーザーのために航空便チケットを購入するSagaを開始します。Airlineは、Saga参加者です。この例は、参加者がSagaに1つのみの予約を入れる単純な解説です。実際には、単一のSagaが複数の参加者に対応することもあります。

ノート:

アプリケーション開発者に求められることは、この例に示すように、注釈付きメソッドを実装することのみです。Sagaフレームワークは、必要な通信を提供して、分散トポロジ全体でSagaの状態を維持します。

ノート:

注釈の@LRA@Compensate@CompleteはLRA注釈です。また、注釈の@participant@Requestおよび@ResponseはSaga注釈です。

例28-4 TravelAgency (Sagaイニシエータ)

@Participant(name = "TravelAgency")
/* @Participant declares the participant’s name to the saga framework */
public class TravelAgencyController extends SagaInitiator {
/* TravelAgencyController extends the SagaInitiator class */
 @LRA(end = false)
 /* @LRA annotates the method that begins a saga and invites participants */
 @POST("booking")
 @Consumes(MediaType.TEXT_PLAIN)
 @Produces(MediaType.APPLICATION_JSON)
  public jakarta.ws.rs.core.Response booking( 
      @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
      String bookingPayload) {
    Saga saga = this.getSaga(lraId.toString());
    /* The application can access the sagaId via the HTTP header
       and instantiate the Saga object using it */
    try {
      /* The TravelAgency sends a request to the Airline sending
         a JSON payload using the Saga.sendRequest() method */
      saga.sendRequest ("Airline", bookingPayload);
      response = Response.status(Response.Status.ACCEPTED).build();
    } catch (SagaException e) {
      response=Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
  }
  @Response(sender = "Airline.*")
  /* @Response annotates the method to receive responses from a specific
     Saga participant */
  public void responseFromAirline(SagaMessageContext info) {
    if (info.getPayload().equals("success")) {
      saga.commitSaga ();
      /* The TravelAgency commits the saga if a successful response is received */
    } else {
      /* Otherwise, the TravelAgency performs a Saga rollback  */
      saga.rollbackSaga ();
    }
  }
}

例28-5 Airline (Saga参加者)


@Participant(name = "Airline")
/* @Participant declares the participant’s name to the saga framework */
public class Airline extends SagaParticipant {
/* Airline extends the SagaParticipant class */
  @Request(sender = "TravelAgency")
  /* The @Request annotates the method that handles incoming request from a given
     sender, in this example the TravelAgency */
  public String handleTravelAgencyRequest(SagaMessageContext    
      info) {
     
    /* Perform all DML with this connection to ensure 
       everything is in a single transaction */  
    FlightService fs = new 
      FlightService(info.getConnection());
    fs.bookFlight(info.getPayload(), info.getSagaId());
    return response;
    /* Local commit is automatically performed by the saga framework.  
       The response is returned to the initiator */  
  }
  
  @Compensate 
  /* @Compensate annotates the method automatically called to roll back a saga */
  public void compensate(SagaMessageContext info) {
    fs.deleteBooking(info.getPayload(), 
        info.getSagaId());
  }
  @Complete 
  /* @Complete annotates the method automatically called to commit a saga */
  public void complete(SagaMessageContext info) {
    fs.sendConfirmation(info.getSagaId());
  }
}

ノート:

このサンプル・プログラムには、importディレクティブは含まれていません。

その他のマイクロサービス(HotelCarRentalなど)は、Airlineマイクロサービスと同様の構造を持ちます。

Travel Agencyは、別の参加者に対応するように次の方法で変更できます。レンタカー・サービスの追加について考えてみます:

  • TravelAgencyControllerのbookingメソッドでは、"Car"参加者にペイロードを送信するために別のsendRequest()が必要になります。

    Saga.sendRequest ("Car", bookingPayload.getCar().toString());
  • Carサービスからのレスポンスを処理するために、新しい注釈付きメソッドが必要です。

    @Response(sender = "Car”)
  • @Responseメソッドには、参加者からのメッセージの受信後に予約の状態を把握するための追加のロジックが必要になります。

    @Response(sender = "Car") 
    public void responseFromCar(SagaMessageContext info) {
      if (!info.getPayload().equals("success")) {
        /* On error, rollback right away */
        Saga.rollbackSaga ();
      } else {
        /* Store the car's response */
        cache.putCarResponse(info.getSagaId(), info.getPayload());
    
        /* Check to see if Airline has responded 
           If a response is found, commit */
        if (cache.containsAirlineResponse(info.getSagaId())) {
          Saga.commitSaga ();
        } 
      }
    }
    

Airlineのレスポンスを処理する方法は同様ですが、 cache.containsAirlineResponse(info.getSagaId())cache.containsCarResponse(info.getSagaId())に、cache.putAirlineResponse()cache.putCarResponse()に置換します。

次の非同期フロー図は、前述のコード例で示したSagaアプリケーションのプロセス・フローを示しています。フロー図の番号は、コード・スニペットに示した番号に対応しています。

関連項目:

Sagaライフサイクルの詳細は、「付録: Sagaフレームワークのトラブルシューティング」を参照してください

図28-1 非同期フロー

非同期Sagaフロー

Sagaプロパティ・ファイル

次に、サンプル・プログラムで使用したSagaプロパティ・ファイルを示します。

# JAX-RS filter properties 
osaga.filter.database.tnsAlias=ta
osaga.filter.database.walletPath=/Path/to/wallet
osaga.filter.database.tnsPath=/Path/to/tns
osaga.filter.initiator.publisherCount=2
osaga.filter.initiator.name=TravelAgency
osaga.travelagency.tnsAlias=ta

# Property values for the Initiator Travelagency
osaga.travelagency.walletPath=/Path/to/wallet
osaga.travelagency.tnsPath=/Path/to/tns
osaga.travelagency.numListeners=2
osaga.travelagency.numPublishers=2

# Properties pertaining to the Airline participant
osaga.airline.tnsAlias=airline1
osaga.airline.walletPath=/Path/to/wallet
osaga.airline.tnsPath=/Path/to/tns
osaga.airline.numListeners=2
osaga.airline.numPublishers=2

28.9 明示的なSagaのファイナライズ

自動予約可能列ベースのファイナライズに加えて、Sagaフレームワークでは、アプリケーション固有の明示的な(ユーザー定義の)ファイナライズ(完了または補正)をサポートしています。フレームワークは、Sagaファイナライズ時にアプリケーション固有のファイナライズ・ルーチンを自動的に起動します。

アプリケーション固有のファイナライズは、PL/SQLクライアントにPL/SQLコールバック・ルーチンを使用することで指定できます。

ノート:

PL/SQLコールバック・パッケージを使用して実装されているユーザー定義のコールバックから、明示的なCOMMITコマンドが発行されていないことを確認してください。Sagaフレームワークは、アプリケーションのかわりに変更をコミットします。

Javaについては、「Saga注釈を使用したJavaアプリケーションの開発」で、アプリケーション固有の補正の実装にJavaプログラムで使用できる注釈の@BeforeComplete@BeforeCompensateおよび@Complete@Compensateついて説明しています。その場合、ロックフリー予約は、@BeforeCompensate@Compensate注釈の付いたメソッド間で自動的に補正されます。

28.9.1 PL/SQLクライアント用のPL/SQLコールバック

PL/SQLベースのイニシエータまたは参加者は、Sagaに参加するためにコールバック・パッケージを実装する必要があります。コールバック・パッケージは、各参加者の通知コールバックから内部的にコールされます。コールバック・パッケージを使用すると、Sagaインフラストラクチャの内部詳細を無視してビジネス・ロジックに集中できます。

アプリケーションでは、コールバック・パッケージの実装によってデータベース・トランザクション制御操作(コミット/ロールバック)が起動されないようにする必要があります。

コールバック・パッケージは、次の機能を提供します:

requestファンクション

構文: function request(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL) return JSON;

説明: requestファンクションは、オペコードREQUESTが指定されたSagaメッセージを受信したときに呼び出されます。アプリケーションはこのメソッドを実装する必要があります。イニシエータにレスポンスとして送り返されるJSONペイロードが戻されます。

responseプロシージャ

構文: procedure response(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: responseプロシージャは、オペコードRESPONSEが指定されたSagaメッセージを受信したときに起動されます。アプリケーションはこのメソッドを実装する必要があります。

before_commitプロシージャ

構文: procedure before_commit(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: before_commitプロシージャは、同じトランザクションのオペコードCOMMITが指定されたSagaメッセージを受信したときに、現在の参加者に対してSagaコミットを実行する前に起動されます。before_commit()プロシージャは、ロックフリー予約のコミット前に呼び出されます。アプリケーションは、このメソッドを実装しているものと想定されていて、ロックフリー予約を使用するアプリケーションは、before_commit()プロシージャとafter_commit()プロシージャのどちらを実装するかを選択できます。

after_commitプロシージャ

構文: procedure after_commit(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: after_commitプロシージャは、同じトランザクションのオペコードCOMMITが指定されたSagaメッセージを受信したときに、現在の参加者に対してSagaコミットを実行した後に起動されます。after_commitプロシージャは、ロックフリー予約のコミット後に呼び出されます。アプリケーションは、このメソッドを実装しているものと想定されていて、ロックフリー予約を使用するアプリケーションは、before_commit()プロシージャとafter_commit()プロシージャのどちらを実装するかを選択できます。

before_rollbackプロシージャ

構文: procedure before_rollback(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: before_rollbackプロシージャは、同じトランザクションのオペコードROLLBACKが指定されたSagaメッセージを受信したときに、現在の参加者に対してSagaロールバックを実行する前に起動されます。before_rollbackプロシージャは、ロックフリー予約のロールバック前に呼び出されます。アプリケーションは、このプロシージャを実装しているものと想定されていて、ロックフリー予約を使用するアプリケーションは、before_rollbackプロシージャとafter_rollbackプロシージャのどちらを実装するかを選択できます。

after_rollbackプロシージャ

構文: procedure after_rollback(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: after_rollbackプロシージャは、同じトランザクションのオペコードROLLBACKが指定されたSagaメッセージを受信したときに、現在の参加者に対してSagaロールバックを実行した後に起動されます。after_rollbackプロシージャは、ロックフリー予約のロールバック後に呼び出されます。アプリケーションは、このプロシージャを実装しているものと想定されていて、ロックフリー予約を使用するアプリケーションは、before_rollbackプロシージャとafter_rollbackプロシージャのどちらを実装するかを選択できます。

forgetプロシージャ

構文: procedure forget(saga_id IN RAW, saga_sender IN VARCHAR2, payload IN JSON DEFAULT NULL)

説明: Sagaコーディネータは、forgetプロシージャを起動して参加者にSagaを忘れたとマークするようリクエストします。忘れたとマークされたSagaはファイナライズできず、管理者の介入が必要です。forgetプロシージャは、コーディネータから否定確認を受信するとトリガーされます。コーディネータは、否定確認で応答してリクエストを結合できます。たとえば、Sagaにタイムアウトが設定されており、タイムアウト後に参加者の結合リクエストを受信した場合、否定確認が参加者に送信されます。否定確認のもう1つの例は、Sagaがイニシエータによってすでにファイナライズされた後に行われた参加要求で否定確認が送信された場合です。

is_joinプロシージャ

構文: procedure is_join (saga_id IN saga_id_t, saga_sender IN VARCHAR2);

説明: Saga参加者は、is_joinプロシージャを起動して進行中のSagaとの関連付けを解除します。このプロシージャにより、参加者はこのSagaで先に進むか、REJECTをイニシエータに送り返すかを選択できます。

rejectプロシージャ

構文: procedure reject (saga_id IN saga_id_t, saga_sender IN VARCHAR2);

説明:イニシエータがrejectプロシージャを起動し、参加者がSagaへの参加の招待を拒否した場合に通知を受け取ります。参加者は、is_joinメソッドでFALSEを返すことができ、これにより、REJECTがトリガーされイニシエータに返されます。

after_sagaプロシージャ

構文: procedure after_saga(saga_id in RAW, status in varchar2)

説明: PL/SQLベースのイニシエータの場合、after_sagaプロシージャはコールバック・パッケージに定義されていればコールされます。PL/SQLベースの参加者の場合、コールバック・パッケージで定義され、イベント10855がレベル512に設定されていると、after_sagaメソッドがコールされます。

引数statusには、enum(org.eclipse.microprofile.lra.annotation.LRAStatus)の文字列化された値があります。

ノート:

payloadとともに、実装されたメソッドは引数としてsaga_Idおよびsaga_sender (このメッセージの送信者)を提示します。クライアントは、saga_Idsaga_senderおよびpayloadパラメータを記帳、内部状態の維持あるいはその両方に使用できます。

28.9.2 ロックフリー予約との統合

ロックフリー予約は、予約可能列への変更の自動補正(ロールバック)を可能にします。予約ジャーナル表には、データベースの予約可能列に対する更新ごとにエントリが記録されます。これらのエントリは、予約可能列の変更を補正するために必要な情報を提供します。予約可能列に対するSaga関連の変更に関連付けられた予約ジャーナル・エントリは、Sagaがファイナライズされるまで保持されます。

Sagaフレームワークでは、saga_finalization$ディクショナリ表を使用して、Sagaの影響を受ける一連の予約可能な表と対応するジャーナルを追跡します。予約ジャーナル・エントリの作成は、SQLの更新時に実行されます。たとえば、一連の更新とそれぞれのsaga_finalization$エントリは次のとおりです。

Update hotel_rooms set rooms = rooms – 2 where date=to_date(‘20201010’);
Update hotel_rooms set rooms = rooms - 1 where date=to_date(‘20201011’);
Update dinner_tables set available_tables= available_tables - 3 where date=to_date(‘20201010’);

表28-2 saga_finalization$表のエントリ

saga_id 参加者 Txn_Id ユーザー# reservable_table reservation_journal ステータス

ABC123

Hotel

1

124

hotel_rooms

SYS_RESERVJRNL_81696

アクティブ

ABC123

Hotel

2

124

dinner_tables

SYS_RESERVJRNL_81697

アクティブ

Sagaフレームワークは、Sagaロールバック中に補正アクションを実行します。予約ジャーナル・エントリは、Sagaトランザクションの補正アクションを実行するために必要なデータを提供します。Sagaフレームワークは、Sagaブランチのsaga_finalization$表を順番に処理し、予約ジャーナルを使用して補正アクションを実行します。Saga_finalization$エントリは、Saga補正が成功すると"補正済"とマークされます。Saga_finalization$エントリは、Sagaコミットが成功すると"コミット済"とマークされます。saga_finalization$エントリが"補正済"または"コミット済"とマークされた後は、それ以降のアクションは実行できません。

Sagaがファイナライズ(コミットまたは補正の成功)されると、Sagaに関連付けられたすべての予約ジャーナルに対応する予約ジャーナル・エントリは削除されます。指定されたSagaのsaga_finalization$エントリも削除されます。

関連項目:

ロックフリー予約

28.10 AfterSagaコールバック

AfterSaga (またはAfterLRA)コールバック・メソッドは、LRA (Sagaの場合と同様の長時間実行アクション)の完了後にコールされます。AfterLRAメソッドを使用して、Sagaが完了したことをSagaの参加者に通知します。

JavaベースまたはPL/SQLアプリケーションのSagaの場合、ユーザーはAfterLRAコールバック・メソッドを定義できます。コールバック・メソッドは、Sagaのすべての参加者が最終的な状態になった後、つまりSagaのすべての参加者がSagaを正常にコミットまたはロールバックした後に呼び出されます。Sagaコーディネータは、イニシエータおよびすべての参加者に対してAfterLRAメソッドを呼び出します。これにより、Javaベース・アプリケーションの@afterLRA注釈付きメソッドおよびPL/SQLベース・アプリケーションのafterLRA()メソッドがトリガーされます。

ノート:

イニシエータのAfterSagaコールバックは、デフォルトで起動されます。参加者のAfterSagaコールバックは、イベント10855がレベル512に設定されている場合にのみ呼び出されます。

@afterLRAメソッド

Javaベースのイニシエータおよび参加者の場合、@afterLRAで装飾されたメソッドがトリガーされます。@afterLRA注釈で装飾されたメソッドには、次のシグネチャが必要です:

@AfterLRA
public void testAfterLRA(SagaMessageContext ctx, LRAStatus status) {
}

SagaMessageContextはSagaメッセージ・コンテキスト・オブジェクトであり、LRAStatusLRAStatus (Eclipse Microprofileで定義)によって定義されます。

after_sagaメソッド

PL/SQLベースのイニシエータおよび参加者の場合は、コールバック・パッケージで定義されている場合、次のメソッドが呼び出されます:

procedure after_saga(saga_id in RAW, status in varchar2)

statusには、enum(org.eclipse.microprofile.lra.annotation.LRAStatus)の文字列化された値があります。

Sagaコーディネータは、完了したすべてのSagaに対して5分(300秒)の間隔でAfterLRAメソッドを呼び出そうとします。この間隔は、アンダースコア・パラメータ_saga_afterlra_intervalを使用して変更できます。

ディクショナリ・ビュー

ユーザー定義AfterLRAメソッドをコールすると、USER_SAGASディクショナリ・ビューでイニシエータのステータスが次のように変更されます:

  • イニシエータがcommitまたはrollbackを送信した後の、committingまたはrolling back

  • 参加者のcommitまたはrollbackを処理した後、およびすべての参加者がイニシエータのcommitまたはrollbackを完了した後の、committedまたはrolled back

関連項目:

ビュー関連の情報については、データベース・リファレンス・ガイド「USER_SAGAS」を参照してください