はじめに
この記事は、以下のブログおよびGitリポジトリを参考にRAP (ABAP Restful Application Programming Model) でEvent driven side effectを実装してみた記録です。
Event driven side effectとは
Side effectとは、Fiori elementsでアクションや項目の入力などの何らかの変化をトリガとして、他の項目またはエンティティをリロードさせるための仕組みです。RAPにはイベントを発生させてそれを外部に連携させる仕組みがあり、イベントをトリガにSide effectを発生させることをEvent driven side effectと呼びます。バックエンドからのイベントはWebsocketを通じてクライアントに通知されます。(参考:Event-Driven Side Effects | UI5 Demo Kit)
イベントトリガのSide effectの利点は、バックグラウンド処理など非同期の処理が完了した時点で画面を更新できることです。
Behavior DefinitionでのSide effectの定義はオンプレミスではS/4HANA 2023から利用可能です。ただし、Event driven side effectは現時点ではクラウド環境のみで利用可能で、on-premiseでは2025から利用可能になるということです。
Event driven side effectは以下のバージョンでサポートされます。
- SAPUI5: 1.132~
- OData: V4
サンプルシナリオ
冒頭のGitリポジトリのシナリオをそのまま使っています。品目の在庫を管理するFioriアプリケーションを作成します。
処理の流れは以下のようになります。
- UIから「在庫更新」のアクションを実行
- バックグラウンド処理を起動
- バックグラウンドで呼ばれる処理の中でRAP BOを呼び在庫を更新する
- RAP BO側では、在庫が更新されたことを確認するとイベントを起動する
バックグラウンド処理では、Background Processing Framework (BGPF)という技術が使われています。BGPFについては以下のブログで紹介しています。
動作
Gitリポジトリを参考に実装してみた画面が以下です。ADTのプレビューから実行しています。
"Calculate Inventory"ボタンをクリックします。
しばらく待つとQuantityが更新され"New data is available for this item"というメッセージが表示されます。バックグラウンド処理の中で5秒のWAITを入れているため、5秒くらいの待ち時間になっています。
Side effectが発生したときにどのような動作をさせるかは、manifest.jsonで設定可能です。デフォルトの動作は"Notification"で、上の例のように通知が表示されます。
{
"sap.fe": {
"app": {
"sideEffectsEventsInteractionType": "Notification/Confirmation/None"
}
}
}
ネットワークタブを見たところなぜかWebSocket接続は確認できず、アクション実行と結果更新用の$batch
リクエストのみ表示されていました。
実装
以下では、RAPでEvent driven side effect実装でのポイントになる箇所を紹介します。全体のコードは冒頭で紹介したGitリポジトリを参照してください。
Behavior Definition
Unmanaged saveの利用
BGPFの処理はsave_modified
メソッドから実行する必要があるため、Unmanaged saveを利用します。
managed with unmanaged save implementation in class ZBGPFBP_R_InventoryTP_006 unique;
アクションの定義
action reCalculateInventory;
イベントの定義
event QuantityUpdated for side effects;
Side effectの定義
QuantityUpdated
のイベントが発生したらQuantityを更新するというSide effectです。
side effects
{
event QuantityUpdated affects field ( Quantity );
}
Behavior Projection
Side effectを使用
projection;
strict ( 2 );
use draft;
use side effects;
アクションとイベントを利用
use action reCalculateInventory;
use event QuantityUpdated;
Behavior Implementation
アクション
BgpfStatus
の項目を設定することにより、後続処理(save_modified)にバックグラウンド処理が必要なことを通知します。
METHOD reCalculateInventory.
READ ENTITIES OF ZBGPFR_InventoryTP_006 IN LOCAL MODE
ENTITY Inventory
FIELDS ( BgpfStatus )
WITH CORRESPONDING #( keys )
RESULT DATA(inventories).
LOOP AT inventories ASSIGNING FIELD-SYMBOL(<inventory>) .
CASE <inventory>-BgpfStatus.
WHEN zbgpfcl_calc_inventory_006=>bgpf_state-unknown .
<inventory>-BgpfStatus = zbgpfcl_calc_inventory_006=>bgpf_state-started_from_bo.
WHEN zbgpfcl_calc_inventory_006=>bgpf_state-successful .
<inventory>-BgpfStatus = zbgpfcl_calc_inventory_006=>bgpf_state-started_from_bo.
WHEN OTHERS.
"do nothing
ENDCASE.
ENDLOOP.
MODIFY ENTITIES OF ZBGPFR_InventoryTP_006 IN LOCAL MODE
ENTITY Inventory
UPDATE FIELDS ( BgpfStatus )
WITH CORRESPONDING #( inventories ).
ENDMETHOD.
save_modifiedメソッド
アクションでBgpfStatus
が設定された場合、BGPFによるバックグラウンド処理を起動します。バックグラウンド処理ではEMLを使用してRAP BOを更新するので、その際にもsave_modified
が呼ばれます。そこで数量が更新されていればQuantityUpdated
のイベントを発生させます。
METHOD save_modified.
DATA : inventories TYPE STANDARD TABLE OF zbgpf_inven_006,
inventory TYPE zbgpf_inven_006,
events_to_be_raised TYPE TABLE FOR EVENT ZBGPFR_InventoryTP_006~QuantityUpdated.
DATA update_inventory_2 TYPE STRUCTURE FOR CHANGE zbgpfr_inventorytp_006\\inventory.
DATA update_inventories_2 TYPE TABLE FOR CHANGE ZBGPFR_InventoryTP_006\\Inventory.
IF update-inventory IS NOT INITIAL.
LOOP AT update-inventory ASSIGNING FIELD-SYMBOL(<update_inventory>).
"check if a process via bgpf shall be started
IF <update_inventory>-BgpfStatus = zbgpfcl_calc_inventory_006=>bgpf_state-started_from_bo
AND <update_inventory>-%control-BgpfStatus = if_abap_behv=>mk-on.
TRY.
"BGPFでバックグラウンド処理を開始
DATA(bgpf_process_name) = zbgpfcl_calc_inventory_006=>run_via_bgpf( i_rap_bo_entity_key = <update_inventory>-%key ).
update_inventory_2-%control-BgpgProcessName = if_abap_behv=>mk-on.
update_inventory_2-bgpgprocessname = bgpf_process_name.
update_inventory_2-%key = <update_inventory>-%key.
APPEND update_inventory_2 TO update_inventories_2.
CATCH cx_bgmc INTO DATA(bgpf_exception).
"handle exception
update_inventory_2-%control-remark = if_abap_behv=>mk-on.
update_inventory_2-remark = bgpf_exception->get_text( ).
update_inventory_2-%key = <update_inventory>-%key.
APPEND update_inventory_2 TO update_inventories_2.
ENDTRY.
ENDIF.
"BGPFによって数量が更新されたらイベントを発生させる
IF <update_inventory>-%control-Quantity = if_abap_behv=>mk-on.
CLEAR events_to_be_raised.
APPEND INITIAL LINE TO events_to_be_raised.
events_to_be_raised[ 1 ] = CORRESPONDING #( <update_inventory> ).
RAISE ENTITY EVENT zbgpfr_inventorytp_006~QuantityUpdated FROM events_to_be_raised.
ENDIF.
ENDLOOP.
ENDIF.
"以下、テーブル更新のための処理
ENDMETHOD.
BGPF用のクラス
run_via_bgpf
save_modied
から呼ばれる処理です。バックグラウンド処理を起動します。
METHOD run_via_bgpf.
TRY.
DATA(process_monitor) = cl_bgmc_process_factory=>get_default( )->create(
)->set_name( |Calculate inventory data|
)->set_operation( NEW zbgpfcl_calc_inventory_006( i_rap_bo_entity_key = i_rap_bo_entity_key )
)->save_for_execution( ).
r_process_monitor_string = process_monitor->to_string( ).
CATCH cx_bgmc INTO DATA(lx_bgmc).
ENDTRY.
ENDMETHOD.
if_bgmc_op_single~execute
バックグラウンドで実行される処理です。処理対象となった製品の在庫を+10してBOを更新します。
METHOD if_bgmc_op_single~execute.
DATA update TYPE TABLE FOR UPDATE ZBGPFR_InventoryTP_006\\Inventory.
DATA update_line TYPE STRUCTURE FOR UPDATE ZBGPFR_InventoryTP_006\\Inventory .
WAIT UP TO wait_time_in_seconds SECONDS.
READ ENTITIES OF ZBGPFR_InventoryTP_006
ENTITY Inventory
ALL FIELDS
WITH VALUE #( ( %is_draft = if_abap_behv=>mk-off
%key-uuid = inventory_uuid
) )
RESULT DATA(entities)
FAILED DATA(failed).
IF entities IS NOT INITIAL.
LOOP AT entities INTO DATA(entity).
update_line-%is_draft = if_abap_behv=>mk-off.
update_line-uuid = entity-uuid.
update_line-Quantity = entity-Quantity + 10.
APPEND update_line TO update.
ENDLOOP.
MODIFY ENTITIES OF ZBGPFR_InventoryTP_006
ENTITY Inventory
UPDATE FIELDS ( Quantity )
WITH update
REPORTED DATA(reported_ready)
FAILED DATA(failed_ready).
ENDIF.
ENDMETHOD.
まとめ
非同期な処理の結果を画面に返したいとき、Event driven side effectを使うことができます。BGPFを使えばバックグラウンドの処理も比較的簡単に実装できます。画面が非同期に更新されると「おおっ」となって楽しいのでもしよければ試してみてください。