23c Oracle Saga Frameworkを試してみた その2：実行編

# はじめに
Oracle Saga Frameworkは、Oracle Databaseでマイクロサービス・アプリケーション用のsagaを実装および管理するフレームワークです。
Oracle Databaseをプラットフォームとして、Sagaベースのアプリケーションを構築することができます。
私自身マイクロサービスはまだまだ勉強中の身で試行錯誤ですが、中間層なしでできるPL/SQLを使ってどんなものか試してみました。
[その1:設定編](https://qiita.com/goo2000/items/fe55699f33567918a97a)で、設定まで実施したので、この記事では実際にSagaを実行してみます。

# 1.Sagaを実行(成功のケース）
旅行代理店サービスで航空券を航空会社United、ホテルの部屋タイプをtwinを指定して予約します。Unitedの座席の残数は10席、ホテルのTwinの部屋の残数は1部屋ある状態です。

```sql
-- @AirlinePDB
SQL> connect admin/WElcome12345##@AirlinePDB
-- flightsを検索する
SQL> SELECT * FROM flights;
FLIGHT          SEATS
---------- ----------
United             10
Delta               5
-- @HotelPDB
SQL> connect admin/WElcome12345##@HotelPDB
-- roomsを検索する
SQL> SELECT * FROM rooms;
ROOMTYPE                    CNT
-------------------- ----------
twin                          1
single                        3
```
イニシエータであるTravelAgencyPDBでDBMS_SAGA.BEGIN_SAGAを使ってSagaを開始し、開始したSagaにSEND_REQUESTで航空券予約サービスの参加者（Airline)を登録します。以下のコードで実行します。

```sql
-- @TravelAgencyPDB
connect admin/WElcome12345##@TravelAgencyPDB

DECLARE
  saga_id RAW(16);
  request JSON;
begin
  saga_id := DBMS_SAGA.BEGIN_SAGA(
                 initiator_name=>'TravelAgency', timeout => 900);
  request := JSON('{"flight":"United","room":"twin"}');
  DBMS_SAGA.SEND_REQUEST(saga_id, 'Airline', request);
END;
/
```

実行状況をチェックするための表を作成していたのでそちらを確認してみます。

```sql
-- @AirlinePDB
SQL> connect admin/WElcome12345##@AirlinePDB
Connected.
SQL> SELECT * FROM flighttest;  
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
TRAVELAGENCY         {"flight":"United","room":"twin"}

-- @HotelPDB
SQL> connect admin/WElcome12345##@HotelPDB
SQL> SELECT * FROM roomtest;
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
TRAVELAGENCY         {"flightresult":"success","flight":"United","room":"twin"}

-- @TravelAvencyPDB
SQL> connect admin/WElcome12345##@TravelAgencyPDB
Connected.
SQL> SELECT * FROM travelagencytest;
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
AIRLINE              {"flightresult":"success","flight":"United","room":"twin"}
HOTEL                {"roomresult":"success","flightresult":"success","flight":"United","room":"twin"}
```

TravelAgencyから送信されたメッセージがAirlineで受信されています。
Airlineで処理され、"flightresult":"success"を加えたメッセージがTravelAgencyで受信されています。HotelPDBはTravelAgencyからそのメッセージを受信し、処理した結果"roomresult":"success"を加えたメッセージがTravelAgencyで受信されています。

どちらも予約も結果はsuccessなので、航空券とホテルの管理表を確認してみます。

```sql
-- @AirlinePDB
SQL> connect admin/WElcome12345##@AirlinePDB
Connected.

SQL> SELECT * FROM flights;
FLIGHT          SEATS
---------- ----------
United              9
Delta               5

-- @HotelPDB
SQL> connect admin/WElcome12345##@HotelPDB
SQL> SELECT * FROM rooms;
ROOMTYPE                    CNT
-------------------- ----------
twin                          0
single                        3
```

各サービスで席と部屋数が1ずつ減っていることが確認できました。ホテルのtwinタイプの部屋数は0になったため、これ以上の予約はできません。

# 2.Sagaを実行（補償トランザクションのケース）
すべてのチェック表をtruncateコマンドでクリアしてから、もう1度同じSagaを実行してみます。

```sql
-- @TravelAgencyPDB
connect admin/WElcome12345##@TravelAgencyPDB

DECLARE
  saga_id RAW(16);
  request JSON;
begin
  saga_id := DBMS_SAGA.BEGIN_SAGA(
                 initiator_name=>'TravelAgency', timeout => 900);
  request := JSON('{"flight":"United","room":"twin"}');
  DBMS_SAGA.SEND_REQUEST(saga_id, 'Airline', request);
END;
/
```

成功のケースと同じように実行状況を確認します。

```sql
-- @AirlinePDB
SQL> connect admin/WElcome12345##@AirlinePDB
Connected.
SQL> SELECT * FROM flighttest;  
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
TRAVELAGENCY         {"flight":"United","room":"twin"}

-- @HotelPDB
SQL> connect admin/WElcome12345##@HotelPDB
SQL> SELECT * FROM roomtest;
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
TRAVELAGENCY         {"flightresult":"success","flight":"United","room":"twin"}

-- @TravelAvencyPDB
SQL> connect admin/WElcome12345##@TravelAgencyPDB
Connected.
SQL> SELECT * FROM travelagencytest;
SENDER               MESSAGE
-------------------- ----------------------------------------------------------------------------------------------------
AIRLINE              {"flightresult":"success","flight":"United","room":"twin"}
HOTEL                {"roomresult":"no-room"}
```
成功のケースと同じようにAirlineのほうは"flightresult":"success"ですが、TravelAgencyがHotelから受信したメッセージは"roomresult":"no-room"となっています。ホテルのtwinタイプの部屋数は0だったからです。
続いて、航空券と部屋の管理表を確認してみます。

```sql
-- @AirlinePDB
SQL> connect admin/WElcome12345##@AirlinePDB
Connected.

SQL> SELECT * FROM flights;
FLIGHT          SEATS
---------- ----------
United              9
Delta               5

-- @HotelPDB
SQL> connect admin/WElcome12345##@HotelPDB
SQL> SELECT * FROM rooms;
ROOMTYPE                    CNT
-------------------- ----------
twin                          0
single                        3
```

どちらの表も更新が行われていません。flights表の更新はsucesssでしたが、room表の更新ができなかったことにより、flights表の更新をロールバックする補償トランザクションが自動的に実行されたということになります。
これは、更新対象の列(seats)を**ロックフリー予約**で指定したことで実現されています。ロックフリー予約を使用するとその列に対する補償トランザクションが自動的に実行されます。補償トランザクションのためのコードの記述が不要なのでSagaのコードがシンプルになります。
もちろん、アプリケーションによっては、ロックフリー予約ではなく独自の補償トランザクションの処理が必要な場合があります。そのときはコールバックパッケージでBEFORE_ROLLBACKメソッド、AFTER_ROLLBACKメソッドを用いて、明示的に補償トランザクションのための処理をコードに記述することができます。

# 3.Sagaの情報確認
[その1：設定編]()で確認したSaga参加者を表示するCDB_SAGA_PARTITIONSのほかにもOracle DatabaseにはOracle Saga Frameworkのためのディクショナリが用意されています。いくつか確認してみます。

::: note info
ここではCDB_xxxで確認していますが、DBA_xxx、USER_xxxでも確認可能です。
:::

## アクティブなSaga
**CDB_SAGAS**はデータベース内のすべてのアクティブなSagaを表示します。

``` sql
connect / as sysdba
SQL> SELECT * FROM CDB_SAGAS ORDER BY TO_TIMESTAMP_TZ(start_time);
ID                                  INITIATOR       IS_ COORDINATOR     OWNER      PARTICIPANT     STATUS            VERSION   DURATION START_TIME                                      COMPLETION_TIME     CON_ID
----------------------------------- --------------- --- --------------- ---------- --------------- -------------- ---------- ---------- ----------------------------------------------- --------------- ----------
00FDB4DDC5852EA7E0633C00640ABCC6    TRAVELAGENCY    NO  TACOORDINATOR   ADMIN      AIRLINE         Joined                  1        900 21-JUL-23 10.27.06.455251 AM +00:00                                      5
```
他にSagaの状況に応じて以下のディクショナリで確認ができます。
+ **CDB_SAGA_DETAILS** : データベース内のすべてのSagaの詳細
+ **CDB_SAGA_PENDING** : データベース内のすべての保留中のSaga
+ **CDB_SAGA_FINALIZATION** : データベース内のすべてのSagaの保留中のファイナライズアクション
+ **CDB_INCOMPLETE_SAGAS** :　データベース内のすべての未完了のSaga

## Sagaの履歴
**CDB_HIST_SAGAS**はデータベース内で完了したすべてのSagaの履歴を表示します。SAGA_HIST_RETENTION初期化パラメータで指定された期間（デフォルト30日）保持されます。

``` sql
connect / as sysdba
SQL> SELECT id,participant,status,TO_TIMESTAMP_TZ(start_time) startdate,con_id FROM CDB_HIST_SAGAS ORDER BY startdate;
ID                                  PARTICIPANT     STATUS         STARTDATE                                                                       CON_ID
----------------------------------- --------------- -------------- --------------------------------------------------------------------------- ----------
00FE414767EB564DE0633C00640AEEF4    TRAVELAGENCY    Committed      21-JUL-23 11.06.22.188277000 AM +00:00                                               4
00FE414767EB564DE0633C00640AEEF4    HOTEL           Committed      21-JUL-23 11.06.22.188277000 AM +00:00                                               6
00FE414767EB564DE0633C00640AEEF4    AIRLINE         Committed      21-JUL-23 11.06.22.188277000 AM +00:00                                               5
011C5DFBDF9E266EE0633C00640A6F78    TRAVELAGENCY    Rolledback     22-JUL-23 11.01.52.791887000 PM +00:00                                               4
011C5DFBDF9E266EE0633C00640A6F78    AIRLINE         Rolledback     22-JUL-23 11.01.52.791887000 PM +00:00                                               5
011C5DFBDF9E266EE0633C00640A6F78    HOTEL           Rolledback     22-JUL-23 11.01.52.791887000 PM +00:00                                               6
```

## Sagaのエラー
**CDB_SAGA_ERRORS**はデータベースのすべてのSagaによって生成されたエラーを表示します。以下はTravelAgencyのチェック表のmessage列のサイズを小さくして明示的にエラーを発生させた場合の表示です。

```sql
connect / as sysdba
SQL> SELECT * FROM CDB_SAGA_ERRORS ORDER BY TO_DATE(error_time,'yy-mm-dd hh24:mi:ss');
SAGA_ID                          PARTICIPANT     SQL_ERROR                                                                                            SQL_COD ERROR_TIME              CON_ID
-------------------------------- --------------- ---------------------------------------------------------------------------------------------------- ------- ------------------- ----------
011DC834B36D8F16E0633C00640A90B6                 ORA-12899: value too large for column "ADMIN"."TRAVELAGENCYTEST"."MESSAGE" (actual: 58, maximum: 20) -1289   2023-07-23:00:43:11          4
```


# 4.おわりに
Oracle Database 23cで提供されたOracle Saga FramworkをPL/SQLで試してみました。データベースにSagaの実装が統合されるので、Sagaの運用管理が容易になり、データベースの整合性、可用性や拡張性をマイクロサービスの基盤として生かすことができます。PL/SQLだけでなく、OSagaインタフェースを使うことでJavaで実装可能ですので、触ってみていただければと思います。

# 参考資料
+ [28 Sagaを使用したアプリケーションの開発](https://docs.oracle.com/cd/F82042_01/adfns/developing-applications-saga.html#GUID-DE723847-0907-48E8-87C8-9A5D2DB63A8D)
+ [OracleデータベースのSagaによるマイクロサービス・トランザクションの簡素化]( https://blogs.oracle.com/oracle4engineer/post/simplify-microservice-transactions-with-oracle-database-sagas-ja)
+ [23c Oracle Saga Frameworkを試してみた その1：設定編](https://qiita.com/goo2000/items/fe55699f33567918a97a)