RAPで標準トランザクションデータを更新したいときに使えるパターン集

## はじめに
ABAP Restful Application Programming Model (RAP) の最もシンプルな使い方は、新規に作成したカスタムテーブルを更新するシナリオです。この場合、追加のロジックをほとんど書くことなく（Validation, Determinationなどは書くかもしれませんが）アプリケーションを作ることができます。

ただ、RAPを使ったシナリオとしてよくありそうなのは、標準のトランザクションデータを更新するケースではないでしょうか。たとえば、Fioriアプリからボタンを押したら伝票を登録する、伝票ステータスを更新する、などといったパターンです。

この記事の目的は、そのような場合にRAPのBehaviorのどこで何をしたらよいか、参考となるようなブログやリポジトリを紹介することです。いくつかの例を見ることにより、「こういう場合はこうする」というパターンが見えてくると思います。

## 標準トランザクションデータを更新する方法
標準トランザクションデータを更新する方法は、主に2つあります。
①標準BAPIを使う
②標準RAP BOを使う

②は比較的新しい方法で、リリース済みの標準RAP BOに対して[EML](https://help.sap.com/docs/ABAP_PLATFORM_NEW/fc4c71aa50014fd1b43721701471913d/af7782de6b9140e29a24eae607bf4138.html)を使って更新を行います。標準BAPIは開発者拡張用にリリースされていないものが多く、今後はRAP BOに置き換わっていくものと考えられます。

```abap:EMLを使った更新の例
      MODIFY ENTITIES OF i_journalentrytp
      ENTITY journalentry
      EXECUTE post FROM lt_entry
        MAPPED FINAL(ls_post_mapped)
        FAILED FINAL(ls_post_failed)
        REPORTED FINAL(ls_post_reported).
```

リリース済みの標準RAP BOは、ADTにRepository Treeを追加して確認することができます。
参考：[RAP610: Use ABAP Cloud for SAP S/4HANA (Cloud) extensions / Exercise 1.1: How to find a released API using an ABAP repository tree](https://github.com/SAP-samples/abap-platform-rap-workshops/tree/main/rap6xx/rap610/exercises/ex1#exercise-11-how-to-find-a-released-api-using-an-abap-repository-tree)

## RAPで標準トランザクションデータを更新するパターン
SAPブログやGitリポジトリなどで見つけた、標準トランザクションデータを更新する方法を紹介します。

### 1. Managedシナリオ：登録処理で会計伝票登録のBAPIを呼ぶ
[Exposing BAPI as OData API using RAP Facade | SAP Blog](https://blogs.sap.com/2023/08/01/exposing-bapi-as-odata-api-using-rap-facade/) 

Managedシナリオを使い、登録処理の中でBAPI_ACC_DOCUMENT_POSTを呼ぶパターンです。Late Numbering用の`adjust_numbers`メソッドでBAPIを呼ぶことにより、採番されたキーを呼び出し元に返すことができます。さらにsaverクラスの継承元をcl_abap_behavior_saver_failedに変えることでadjust_numbersでエラーを返せるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9b71d7b1-6334-609f-28e5-650a820cde9a.png)

注意点として、処理パターンによっては実行時エラーが発生します。RAPのBehaviorでは許可されていないオペレーションがいくつかあり（コミット、GUI画面の呼び出しなど）、BAPIに渡すデータの内容によっては許可されない処理に分岐することがあります。
このため、標準BAPIを利用する場合は想定する処理パターンでエラーが起きないか、念入りに確認することが必要となります。「さまざまな転記データが入ってくる」というシナリオではお勧めできず、限定された仕訳パターンで利用する方がよさそうです。

RAPからBAPIを呼べるケース、呼べないケースについては以下のブログで説明されています。
[Using BAPIs in RAP | SAP Blog](https://blogs.sap.com/2022/11/30/using-bapis-in-rap/)

### 2. Unmanaged Saveシナリオ：登録時に動くDeterminationから会計伝票登録のRAP BOを呼ぶ
[How to use I_JournalEntryTP in custom RAP BO | SAP Blog](https://blogs.sap.com/2023/10/26/how-to-use-i_journalentrytp-in-custom-rap-bo/)

[Unmanaged Save](https://help.sap.com/docs/abap-cloud/abap-rap/unmanaged-save)を使い、登録処理の際に動くDeterminationから会計伝票登録のRAP BO: I_JournalEntryTPを呼ぶパターンです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/391c25d7-d674-e1fd-35ef-ba749cc34b3b.png)

I_JournalEntryTPはLate Numberingでの採番となるため、キーが取得できるのはsave_modifiedの時点になります。そのため、仮のキー(preliminary key)をいったん保管しておき、save_modifiedのタイミングで採番されたキーを取得してカスタムテーブルに格納します。仮のキーを保管するクラスをシングルトン（※）で作成することにより、Interaction Phase、Save Sequenceとフェーズが分かれても同じデータを参照することができます。


```abap:※シングルトンでのクラスインスタンス作成方法
" インスタンスが存在すればそのインスタンスを返し、なければ新規作成する
  METHOD get_instance.
    IF go_instance IS NOT BOUND.
      go_instance = NEW #( ).
    ENDIF.
    result = go_instance.
  ENDMETHOD.
```
このパターンでの注意点は、採番されたキーを取得するのがsave_modifiedのタイミングになるため、レスポンスボディにキーを返せないことです。キーはレスポンスヘッダのsap-messagesに設定されます。
一方、Fiori UIから使う場合は登録後に自動的にデータが再取得されるため、画面上ですぐにキーを確認することが可能です。

```abap:採番されたキーを取得する箇所
  METHOD convert_temp_to_final.
    DATA: ls_final_key TYPE ty_final_key.
    IF temp_key IS NOT INITIAL.
      LOOP AT temp_key INTO DATA(ls_temp_key).
        CONVERT KEY OF i_journalentrytp
          FROM ls_temp_key-pid
          TO FINAL(lv_root_key).

        ls_final_key-cid = ls_temp_key-cid.
        ls_final_key-bukrs = lv_root_key-companycode.
        ls_final_key-belnr = lv_root_key-accountingdocument.
        ls_final_key-gjahr = lv_root_key-fiscalyear.

        APPEND ls_final_key TO result.
      ENDLOOP.
    ENDIF.
  ENDMETHOD.
  ```

### 3. Unmanaged Saveシナリオ：アクションから購買依頼登録のRAP BOを呼ぶ
[RAP610: Use ABAP Cloud for SAP S/4HANA (Cloud) extensions | GitHub](https://github.com/SAP-samples/abap-platform-rap-workshops/tree/main/rap6xx/rap610)

こちらも一つ前のパターンと似ていますが、アクションから購買依頼登録のRAP BO: I_PurchaseRequisitionTPを呼ぶパターンです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/70b03654-bb92-e6cb-8d00-88127fed12cc.png)

I_PurchaseRequisitionTPもLate Numberingとなるため、パターン2と同様な工夫が必要です。ここではグローバルクラスのスタティック属性に仮のキーを格納し、save_modifiedで採番されたキーを取得しています。効果はシングルトンのパターンと同じで、Interaction PhaseとSave Sequenceにまたがって同じデータを参照することができます。また、採番されたキーを呼び出し元に返せないという制約も、パターン2と同じです。

### 4. Unmanaged Saveシナリオ：アクションから未リリースのBAPIを呼び出す
[Cloud ready RAP object using legacy BAPI – Clean Core objective | SAP Blog](https://blogs.sap.com/2024/01/04/cloud-ready-rap-object-using-legacy-bapi-clean-core-objective/)

開発者拡張で未リリースのBAPIを呼びたい場合の対応です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/4083afdc-f037-2649-dd80-888929735af4.png)

Tier2相当のラッパークラスを作成し、ファクトリークラスを介してラッパークラスを呼んでいます。ファクトリークラスを利用するのは、後継のリリース済みオブジェクトが登場したときに、ファクトリークラスの呼び先を変えるだけでよいためだと考えられます。ここではハンドリングユニットを削除するBAPI: BAPI_HU_DELETE_FROM_DELを呼び出しています。採番はないためパターン2, 3のような工夫はいりません。

## 番外編：非同期処理のパターン
実行したい処理に時間がかかる場合や、RAPで許可されていないオペレーションのため直接実行できない場合、非同期処理を利用することができます。以下は標準トランザクションを使用した例ではありませんが、標準トランザクションでも利用できるパターンのため紹介します。

### 5. Unmanaged Saveシナリオ：アクションからアプリケーションジョブを起動する

[abap-platform-application-jobs | GitHub](https://github.com/SAP-samples/abap-platform-application-jobs)

アクションからアプリケーションジョブを起動し、ジョブの中で行われる処理でカスタムテーブルを更新します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d1028e6c-20b3-611d-070d-f94d4781b62d.png)

Gitリポジトリの中のソースコードが探しにくいので、関連する部分のリンクを貼っておきます。
[Behavior Definition](https://github.com/SAP-samples/abap-platform-application-jobs/blob/main/src/zappr_inventorytp_01.bdef.asbdef)
[Behavior Implementation Class](https://github.com/SAP-samples/abap-platform-application-jobs/blob/main/src/zappbp_r_inventory_01.clas.locals_imp.abap)

### 6. Unmanaged Saveシナリオ：アクションからbgPFを使用した処理を起動する

[【ABAP】Background Processing Framework (bgPF) でRAPから非同期処理を起動する](https://qiita.com/tami/items/b8a54ec953c7ed9b4824)

アクションからBackground Processing Framework (bgPF) で非同期処理を起動し、カスタムテーブルを更新します。流れは5のパターンとほとんど同じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/05232f4e-582c-7f46-53f3-2f97c4b55380.png)

## まとめ
RAPで標準トランザクションデータを更新するパターンを4つ（+非同期処理2つ）を見てきました。ほとんどのパターンでUnmanaged Saveを使用しています。これは、Interaction Phaseでトランザクションバッファに変更を加えておき、save_modifiedメソッドで変更があったデータを拾って後続処理を行うというパターンです。RAPの制約から、Interaction Phaseで許可されていない処理があるため、更新を伴う処理をsave_modifiedで実行しているのだと考えられます。

余談ですが、「RAPのBehaviorのどこで何をしていいかわからない」については、「実装してみて、実行時エラーにならなければOK」だと私は思っています。なぜなら、RAPのBehaviorの中で実行してよい処理については厳しい制限がかかっているので、やってはいけないことはエラーが出て気づくことができるからです。
上記のパターンを参考にしつつ、恐れずRAPを使っていきたいですね。