はじめに
ABAP Restful Application Programming Model (RAP)のアクションから、更新系の汎用モジュールを呼び出したい場面があります。
しかし、単純にアクションのイベントハンドラから汎用モジュールを呼び出すと、このフェーズで更新が許可されていないため"BEHAVIOR_ILLEGAL_STATEMENT"という実行時エラーになります。
CALL FUNCTION DESTINATION 'NONE'は?
SAP Communityなどでは、そういった場合にCALL FUNCTION DESTINATION 'NONE'
が使えるという情報を目にします。
ただ、BTP ABAP Environmentで試してみると"RFC_ILLEGAL_CALL_ON_SAPCP"という実行時エラーになりました。以下のブログのスレッドでは、「ABAP CloudではRFC宛先はシステム間の連携のみで許可され、DESTINATION 'NONE'
は使えない」とありました。
よって、CALL FUNCTION DESTINATION 'NONE'
はオンプレミス(言語バージョン:Standard ABAP)でのみ使える方法のようです。
ABAP Cloudではどうするか
クラウド、オンプレ、言語バージョンを問わず使える方法とはどのようなものでしょうか。私が知っている方法は以下のようなものです。
- アクションの中で、エンティティに「更新が必要である」ことを示すフラグを設定する
- save_modifiedメソッドで更新系汎用モジュールを呼ぶ
この方法はSAPのAndre Fisher氏が作成した以下のリポジトリでも使用されています(汎用モジュールの呼び出しではなく、BGPFの実行ですが)。
※save_modifiedメソッドを利用するにはUnmanaged SaveまたはAdditional Saveを使用します。CRUD処理をフレームワークに任せる場合はAdditional Saveを使用してください。
サンプルシナリオ
本のオーダーと在庫を管理するアプリケーションを作成します。
オーダーを入力した後、"sendOrder"というアクションを実行すると本の在庫が更新されます。
以下では、「アクションから更新系汎用モジュールを呼び出す」という箇所に絞って実装を紹介します。
CDS View
UpdateRequired
という項目が、save_modifiedに更新があることを伝える役割をします。この項目はDBに保持しません。
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Book Order'
@Metadata.ignorePropagatedAnnotations: true
define root view entity ZI_BookOrder as select from zbook_order
{
key order_id as OrderId,
book_id as BookId,
book_title as BookTitle,
quantity as Quantity,
order_date as OrderDate,
creation_date as CreationDate,
abap.char'' as UpdateRequired
}
Behavior Definition
ここでのポイントは、with unmanaged save
とすることでsave_modifiedメソッドを実装できるようにすることです。
※今回は登録処理も汎用モジュールで実施したためUnmanaged Saveを使用していますが、CRUD処理をフレームワークに任せる場合はwith additional save
としてください。
managed implementation in class zbp_i_bookorder unique;
strict ( 2 );
with draft;
define behavior for ZI_BookOrder //alias <alias_name>
lock master
total etag CreationDate
authorization master ( instance )
with unmanaged save
late numbering
draft table zbook_order_d
//etag master <field_name>
{
create;
update;
delete;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
action sendOrder;
field ( readonly )
OrderId,
CreationDate,
BookTitle;
}
Behavior Implementation
アクションの実装
オーダー送信のアクションでは、はじめに在庫更新の汎用モジュールを呼んで在庫のチェックをします。save_modifiedメソッドではエラーを返せない(※)ため、アクションで事前チェックをしています。
※Saverクラスの継承元をデフォルトのcl_abap_behavior_saver
からcl_abap_behavior_saver_failed
に変えるとエラーを返すことができます(以下のブログ参照)。
https://community.sap.com/t5/technology-blogs-by-sap/exposing-bapi-as-odata-api-using-rap-facade/ba-p/13571926
METHOD sendOrder.
DATA modify_order TYPE TABLE FOR UPDATE ZI_BookOrder.
READ ENTITIES OF ZI_BookOrder IN LOCAL MODE
ENTITY ZI_BookOrder
FIELDS ( BookId Quantity )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" check stock
LOOP AT orders INTO DATA(order).
CALL FUNCTION 'Z_F_UPDATE_BOOKSTOCK'
EXPORTING
i_book_id = order-BookId
i_quantity = order-Quantity
i_check_only = abap_true
EXCEPTIONS
insufficient_stock = 1
OTHERS = 2.
IF sy-subrc <> 0.
APPEND VALUE #( %tky = order-%tky ) TO failed-zi_bookorder.
APPEND VALUE #( %tky = order-%tky
%msg = new_message(
id = 'ZBOOK_ORDER'
number = 001
severity = if_abap_behv_message=>severity-error
v1 = order-BookId
) ) TO reported-zi_bookorder.
ELSE.
APPEND VALUE #( %tky = order-%tky
UpdateRequired = abap_true ) TO modify_order.
APPEND VALUE #( %tky = order-%tky
%msg = new_message(
id = 'ZBOOK_ORDER'
number = 002
severity = if_abap_behv_message=>severity-success
v1 = order-OrderId
) ) TO reported-zi_bookorder.
ENDIF.
ENDLOOP.
"notify the save_modified method that update is required
MODIFY ENTITIES OF ZI_BookOrder IN LOCAL MODE
ENTITY ZI_BookOrder
UPDATE FIELDS ( UpdateRequired )
WITH modify_order.
ENDMETHOD.
save_modifiedの実装
アクションの中でUpdateRequired
の項目を更新したため、save_modifiedメソッドのupdate
パラメータに更新したレコードが入ってきます。UpdateRequired
にフラグが立っていれば在庫更新の汎用モジュールを呼び出します。
METHOD save_modified.
" get book id
READ ENTITIES OF ZI_BookOrder IN LOCAL MODE
ENTITY ZI_BookOrder
FIELDS ( BookId UpdateRequired )
WITH VALUE #( for updated in update-zi_bookorder
( %key = updated-%key ) )
RESULT DATA(orders).
LOOP AT orders INTO DATA(order).
IF order-UpdateRequired = abap_true.
CALL FUNCTION 'Z_F_UPDATE_BOOKSTOCK'
EXPORTING
i_book_id = order-BookId
i_quantity = order-Quantity
i_check_only = abap_false
EXCEPTIONS
insufficient_stock = 1
OTHERS = 2.
IF sy-subrc <> 0.
"error should not happen at this point
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
在庫更新の汎用モジュール
以下が在庫更新の汎用モジュールの定義です。重要なことは、この中でコミットしないことです。コミットはRAPのフレームワークが行います。本来はテーブルロックをおこなうべきですが、ここでは省略しています。
FUNCTION z_f_update_bookstock
IMPORTING
VALUE(i_book_id) TYPE int4
VALUE(i_quantity) TYPE int4
VALUE(i_check_only) TYPE abap_boolean OPTIONAL
EXCEPTIONS
insufficient_stock.
"Check stock
"In a real life scenario, table lock is required
SELECT SINGLE stock FROM zbook_stock
WHERE book_id = @i_book_id
INTO @DATA(current_stock).
IF current_stock < i_quantity.
RAISE insufficient_stock.
ENDIF.
IF i_check_only = abap_true.
RETURN.
ENDIF.
DATA(new_stock) = current_stock - i_quantity.
DATA(updated_at) = cl_abap_context_info=>get_system_date( ).
UPDATE zbook_stock
SET stock = @new_stock,
updated_at = @updated_at
WHERE book_id = @i_book_id.
ENDFUNCTION.
動作確認
おわりに
このパターンは汎用モジュールの呼び出しに限らず、アクションから以下のような処理を行いたいときに使うことができます。
- DB更新
- メール送信
- リモートサービスへの接続
- BGPFの呼び出し
- アプリケーションジョブのスケジュール