LoginSignup
1
0

【ABAP】Background Processing Framework (bgPF) でRAPから非同期処理を起動する

Posted at

はじめに

RAPのBehaviorロジックの中から時間のかかる処理を実行するとき、呼び出し元にはすぐに結果を返し、バックグラウンドで非同期に処理を行いたい場合があります。
これまで、RAPから非同期処理を開始する方法としては、Application Job(※1)がありました。これはt-code: SM36で定義するジョブのABAP Cloud版です(※2)。クラス(API)を使用してプログラムからジョブを起動することができます。

Background Processing Framework (bgPF) は昨年登場した新しい方法で、S/4HANA On-Premiseでは2023から使うことができます。bgPFは、従来からある技術のバックグラウンドリモートファンクションコール(bgRFC)を利用しやすくするためのAPIを提供します。技術的な詳細についてはこちらのブログご参照ください。

この記事では、bgPFの概要とRAPへの組み込み方について紹介します。

※1 RAPでApplication Jobを使用した非同期処理の起動方法については、以下のリポジトリを参照
https://github.com/SAP-samples/abap-platform-application-jobs
※2 S/4HANA On-Premiseでも使うことができる

bgPFについて

bgPFは何がうれしいか

Application Jobには、以下のような難点がありました。

  • ジョブカタログエントリ、ジョブテンプレートなどのオブジェクトを事前に作成しておく必要がある
  • ジョブが受け取れるパラメータが、単一項目またはSELECT-OPTION型に限定されている

bgPFの場合、非同期に呼ばれる処理をクラスとして作成しておくだけでよく、Application Jobと比べて簡単に非同期処理を実装できます。また、渡せるパラメータの種類に制限はなく、テーブル型を渡すことも可能です。

bgPFで非同期処理を開始するために必要なもの

bgPFで非同期処理を開始するために必要なものはいたってシンプルで、事前に非同期処理を行うクラスを作成しておき、それをRAPまたはその他のロジックから呼び出すだけです。

image.png

bgPFのシナリオ

bgPFを利用するシナリオには2種類あり、一つがRAPの中で使用するTransactional Controlledシナリオ、もう一つがRAP以外から使用するTransactional Uncontrolledシナリオです。
Transactional Controlledは、RAPのトランザクションコントロールを使用してトランザクションの整合性を担保したいときに使用します。このために、非同期処理を行うクラスの中でトランザクションのフェーズ(modifyフェーズまたはsaveフェーズ)を宣言するという特徴があります。
Transactional Uncontrolledは、RAPを使用しない場合、または厳格なトランザクションの制御が不要な場合に使用します。この場合、非同期処理を行うクラスの中でのフェーズの宣言は不要です。

この記事では、RAPから呼び出すためTransactional Controlledシナリオを使用します。Transactional Uncontrolledでは利用するAPIが変わりますが、基本的な流れは同じです。

bgPFでRAPから非同期処理を起動する

ここからは、実際にRAPのBehaviorロジックの中からどのようにbgPFを呼び出すかについて、実装で確認します。環境はBTPトライアル環境のABAP Environmentを使用しています。

シナリオ

/DMO/I_FlightというCDS Viewは、航空便の最大座席数(MaximumSeats)と予約済み座席数(OccupiedSeats)を持っています。このビューと同じキーを持つカスタムテーブルを作成し、そこに座席の予約率を計算した結果を格納します。予約率の計算を「時間がかかる処理」と仮定し、bgPFを使って実行します。オブジェクトの構成は以下のようになります。

図中の番号は以下の実装ステップと対応します。番号のないオブジェクトについては、bgPFの実装と直接関係ないためAppendixに掲載します。

image.png

実装ステップ

  1. テーブル定義
  2. CDS View定義
  3. Behavior Definition定義
  4. bgPFから呼び出されるクラスの定義
  5. Behavior Implementationクラス実装

1. テーブル定義

以下のテーブルを定義します。このテーブルには座席の使用率、数量単位(%)、計算ステータスを格納します。

zflightoccupancy
@EndUserText.label : 'Flight Occupancy'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zflightoccupancy {

  key client        : abap.clnt not null;
  key carrier_id    : /dmo/carrier_id not null;
  key connection_id : /dmo/connection_id not null;
  key flight_date   : /dmo/flight_date not null;
  @Semantics.quantity.unitOfMeasure : 'zflightoccupancy.occupancy_unit'
  occupancy_rate    : abap.quan(5,2);
  occupancy_unit    : abap.unit(3);
  calc_status       : zde_calc_status;

}

2. CDS View定義

ステップ1で作成したテーブルと/DMO/I_Flightを結合したビューを作成します。計算ステータスに色をつけて表示するため、Criticalityを計算項目として定義しています。(Projection Viewではcase文が使えないため、ベースのViewで項目を作成)

ZI_FlightOccupancy
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Flight Occupancy'
define root view entity ZI_FlightOccupancy as select from /DMO/I_Flight as flight
left outer many to one join zflightoccupancy as occupancy
on flight.AirlineID = occupancy.carrier_id
and flight.ConnectionID = occupancy.connection_id
and flight.FlightDate = occupancy.flight_date
{
    key flight.AirlineID,
    key flight.ConnectionID,
    key flight.FlightDate,
    flight.Price,
    flight.CurrencyCode,
    flight.PlaneType,
    flight.MaximumSeats,
    flight.OccupiedSeats,
    occupancy.calc_status as CalcStatus,
    @EndUserText.label: 'Calculation Status'
    case occupancy.calc_status
      when '1' then 'Processing'
      when '2' then 'Done' 
      when '3' then 'Error' 
      else 'None'
      end as CalcStatusDisp,    
    case occupancy.calc_status
      when '1' then 2 //processing: warning
      when '2' then 3 //done: positive
      when '3' then 1 //error: critical
      else 0
      end as Criticality,
    @Semantics.quantity.unitOfMeasure: 'OccupancyUnit'
    @EndUserText.label: 'Occupancy Rate'
    occupancy.occupancy_rate as OccupancyRate,  
    occupancy.occupancy_unit as OccupancyUnit,    
    /* Associations */
    flight._Airline,
    flight._Connection,
    flight._Currency
}

3. Behavior Definition定義

今回のシナリオではCRUD処理によってテーブルを更新するのではなく、アクションを介して更新するためunmanaged saveを使用します。create, delete処理は使用しないためコメントアウトします。アクションcalculateOccupacyRateで座席の予約率を計算します。

ZI_FlightOccupancy
managed implementation in class zbp_i_flightoccupancy unique;
strict ( 2 );

define behavior for ZI_FlightOccupancy alias FlightOccupacy
with unmanaged save
lock master
authorization master ( instance )
{
  //  create;
  update;
  //  delete;

  action calculateOccupacyRate result [1] $self;
}

4. bgPFから呼び出されるクラスの定義

予約率の計算、テーブル更新を行うクラスを定義します。

クラスの定義部分は以下のようになります。重要な点は、インターフェースif_bgmc_op_singleを使用することです。ADTでクラスを定義するときに、使用するインターフェースとしてif_bgmc_op_singleを指定すると、残りのインターフェースは自動的に入ってきます。
このクラスでは、コンストラクタに計算対象となる航空便のキー(carrier_id, connection_id, flight_date)を受け取り、インスタンス変数(m_flight_keys)に格納します。

zcl_flight_operation_contr
CLASS zcl_flight_operation_contr DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    TYPES: BEGIN OF ty_flight_keys,
             carrier_id    TYPE /dmo/carrier_id,
             connection_id TYPE /dmo/connection_id,
             flight_date   TYPE /dmo/flight_date,
           END OF ty_flight_keys.

    INTERFACES if_serializable_object .
    INTERFACES if_bgmc_operation .
    INTERFACES if_bgmc_op_single .

    METHODS constructor
      IMPORTING i_flight_keys TYPE ty_flight_keys.

  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA m_flight_keys TYPE ty_flight_keys.
ENDCLASS.

クラスの実装部分は以下のようになります。
メソッドif_bgmc_op_single~executeで、非同期に実行される処理を実装します。ポイントは、更新処理を行う前にcl_abap_tx=>save( )を実行することです(この一文がないとMODIFY処理が実行されない)。
cl_abap_tx=>save( )は、以降の処理が更新を伴うものであることを宣言します。これはTransactional Controlledシナリオで、トランザクションの整合性を担保するために必要な宣言です。

CLASS zcl_flight_operation_contr IMPLEMENTATION.
  METHOD constructor.
    m_flight_keys = i_flight_keys.
  ENDMETHOD.


  METHOD if_bgmc_op_single~execute.
    "時間のかかる処理を再現
    WAIT UP TO 10 SECONDS.
    
    cl_abap_tx=>save( ).

    SELECT SINGLE MaximumSeats,
                  OccupiedSeats
    FROM /DMO/I_Flight
    WHERE AirlineID = @m_flight_keys-carrier_id
      AND ConnectionID = @m_flight_keys-connection_id
      AND FlightDate = @m_flight_keys-flight_date
    INTO @DATA(flight).

    IF sy-subrc <> 0 OR flight-MaximumSeats = 0.
      MODIFY zflightoccupancy FROM @( VALUE #( carrier_id = m_flight_keys-carrier_id
                                               connection_id = m_flight_keys-connection_id
                                               flight_date = m_flight_keys-flight_date
                                               occupancy_rate = 0
                                               occupancy_unit = '%'
                                               calc_status = '3' ) ).
      RETURN.
    ENDIF.

    MODIFY zflightoccupancy FROM @( VALUE #( carrier_id = m_flight_keys-carrier_id
                                             connection_id = m_flight_keys-connection_id
                                             flight_date = m_flight_keys-flight_date
                                             occupancy_rate = flight-OccupiedSeats / flight-MaximumSeats * 100
                                             occupancy_unit = '%'
                                             calc_status = '2' ) ).

  ENDMETHOD.
ENDCLASS.

※当初、EMLを使って以下のように更新をしようとしたのですが、処理が終わらずテーブルも更新されないという結果になりました。RAPを使った更新をかけようとしたことで、bgPFが無限に呼び出されることになったのではないかと推測しています。

    modify ENTITIES OF ZI_FlightOccupancy
      ENTITY FlightOccupacy
      UPDATE FIELDS ( OccupancyRate CalcStatus )
      with value #( ( AirlineID = m_flight_keys-carrier_id
                      ConnectionID = m_flight_keys-connection_id
                      FlightDate = m_flight_keys-flight_date
                      OccupancyRate = flight-OccupiedSeats / flight-OccupiedSeats * 100
                      CalcStatus = '2'  ) ).

5. Behavior Implementationクラス実装

Behavior Implementationクラスでは2つの処理を実装します。一つ目はアクションの実行、二つ目は保存処理です。unmanaged saveを使用しているため、保存処理はsave_modifiedというメソッドの中で行います。

アクションの実装は以下のようになります。アクションでは、CalcStatus(計算ステータス)に処理中を表すステータスを設定して結果を返します。この時点ではまだDBは更新されません。

CLASS lhc_FlightOccupacy DEFINITION INHERITING FROM cl_abap_behavior_handler.
 PRIVATE SECTION.

   METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
     IMPORTING keys REQUEST requested_authorizations FOR FlightOccupacy RESULT result.
   METHODS calculateoccupacyrate FOR MODIFY
     IMPORTING keys FOR ACTION flightoccupacy~calculateoccupacyrate RESULT result.



ENDCLASS.

CLASS lhc_FlightOccupacy IMPLEMENTATION.

 METHOD get_instance_authorizations.
 ENDMETHOD.



 METHOD calculateOccupacyRate.
   "set status
   MODIFY ENTITIES OF ZI_FlightOccupancy IN LOCAL MODE
     ENTITY FlightOccupacy
     UPDATE FIELDS ( calcStatus )
     WITH VALUE #( FOR status IN keys ( %tky = status-%tky
                                        CalcStatus = 1 ) ).

   "return result
   READ ENTITIES OF ZI_FlightOccupancy IN LOCAL MODE
     ENTITY FlightOccupacy
     ALL FIELDS WITH CORRESPONDING #( keys )
     RESULT DATA(occupacy).

   result = VALUE #( FOR data IN occupacy ( %tky = data-%tky
                                            %param = data ) ).
 ENDMETHOD.

ENDCLASS.

保存処理の実装は以下のようになります。ここでbgPFが登場します。
はじめに、アクションで更新したステータスと予約率の初期値をDBに保存します。次に、bgPFを使用して計算処理を実行します。lo_process->save_for_execution( )によって処理が起動されます。
なお、bgPFの実行で例外が発生する場合がありますが、ここではキャッチするだけで特に何もしていません。save_modifiedメソッドはエラーを返すことができないため、起こりうるエラーはバリデーションなどであらかじめつぶしておくことが必要です。

CLASS lsc_ZI_FLIGHTOCCUPANCY IMPLEMENTATION.

  METHOD save_modified.
    DATA lo_operation TYPE REF TO if_bgmc_op_single.
    DATA lo_process TYPE REF TO if_bgmc_process_single_op.
    DATA lo_process_monitor TYPE REF TO if_bgmc_process_monitor.

    LOOP AT update-flightoccupacy INTO DATA(occupacy).
      "persist status
      MODIFY zflightoccupancy FROM @( VALUE #( carrier_id = occupacy-AirlineID
                    connection_id = occupacy-ConnectionID
                    flight_date = occupacy-FlightDate
                    calc_status = occupacy-CalcStatus
                    occupancy_rate = 0
                    occupancy_unit = '%' ) ).

      "trigger bgPF
      lo_operation = NEW zcl_flight_operation_contr( i_flight_keys = VALUE #( carrier_id = occupacy-AirlineID
                                                                              connection_id = occupacy-ConnectionID                                                                      
                                                                              flight_date = occupacy-FlightDate ) ).
      TRY.
          lo_process = cl_bgmc_process_factory=>get_default( )->create( ).
          lo_process->set_name( 'Calc Flight Occupancy Test'
                   )->set_operation( lo_operation ).
          lo_process_monitor = lo_process->save_for_execution( ).
        CATCH cx_bgmc INTO DATA(lx_bgmc).
      ENDTRY.

    ENDLOOP.


  ENDMETHOD.

  METHOD cleanup_finalize.
  ENDMETHOD.

ENDCLASS.

処理の実行と監視

bgPFで起動した処理は、ADTのABAP Cross-Tracerというツールによって監視することができます。このために、あらかじめトレースを有効化しておく必要があります。

Window > Show view > ABAP Cross Trace を選択します。
image.png

プロジェクトを右クリックし、"Create Configuration"を選択します。
image.png

"Background Processing"にチェックが入っていることを確認して"OK"をクリックします。
image.png

Service Bindingのプレビューから処理対象の行を選択して、"Calculate"アクションを実行します。
image.png

ステータスが"Processing"になります。
image.png

Trace Resultsタブを見ると、Request Entry Typeが"Remote Function Call"の行ができています。
image.png

その行をダブルクリックすると、処理の詳細が表示されます。3行目に"Excecute completed"の行が出ていることから、処理が完了したことがわかります。
※bgPFから呼び出されるクラスの中で、EMLを使って更新を実行しようとしたときは、"Execute completed"の行が現れませんでした
image.png

"Go"ボタンを押して一覧を更新すると、予約率とステータスが更新されています。
image.png

プロセスを監視するその他の方法については、Help PorlalのMonitoring of the Background Processing Frameworkの章をご参照ください。

おわりに

bgPFを検証してみて、Application Jobと比べて簡単に実装できることを実感しました。今後はRAPから非同期な処理を実行したいという場合は、bgPFが第一の選択肢になると思います。

Appendix

本文で紹介しなかったオブジェクトのソースを掲載します。
ソースコードは以下のリポジトリにも格納しています。
https://github.com/miyasuta/rap-bgpf

Projection View

Zc_Flightoccupancy
@EndUserText.label: 'Flight Occupancy'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity Zc_Flightoccupancy 
provider contract transactional_query
as projection on ZI_FlightOccupancy
{
    key AirlineID,
    key ConnectionID,
    key FlightDate,
    Price,
    CurrencyCode,
    PlaneType,
    MaximumSeats,
    OccupiedSeats,
    Criticality,
    CalcStatus,
    CalcStatusDisp,    
    OccupancyRate,
    OccupancyUnit,
    /* Associations */
//    _Airline, Service Bindingの登録時にエラーになったためコメントアウト
    _Connection,
    _Currency
}

Behavior Projection

Zc_Flightoccupancy
projection;
strict ( 2 );

define behavior for Zc_Flightoccupancy alias FlightOccupancy
{

  use action calculateOccupacyRate;
}

Service Definition

ZFLIGHTOCCUPANCY
@EndUserText.label: 'Flight Occupancy'
define service ZFlightOccupancy {
  expose Zc_Flightoccupancy as FlightOccupancy;
}

Service Binging

image.png

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0