LoginSignup
1
0

More than 1 year has passed since last update.

【RAP】Managed BOでUnmanaged Saveを実装

Last updated at Posted at 2022-01-09

はじめに

この記事では、RAP(ABAP RESTful Application Programming Model)のManaged BOを使用した、Unmanaged Saveを実装します。

シリーズの先頭はこちら☞ 【RAP】ADTのRAP Generatorを使ってみる

Managed / Unmanaged BOとは

RAPでビジネスオブジェクト(BO)を扱う流れは、保存前までのInteraction Phaseと、ユーザが保存をトリガした後のSave Sequenceに分けられます。Interaction Phaseでは、ユーザがBOを呼んでデータを読み込んだり変更をしたりします。BOへの変更はTransactional bufferにためられます。保存がトリガされた後はSave Sequenceによってデータがデータベースに保存されます。

Managed BOとは、Interaction PhaseおよびSave Sequenceの処理がフレームワークによって提供されるタイプのBOです。これは新規の開発で使用されます。
これに対しUnmanaged BOとは、Interaction Phase、およびSave Sequenceの中の処理を自前で実装するタイプのBOです。これは、再利用できるコードが既にあり、RAPにつなぎたい場合に使用されます。
image.png

Unmanaged Saveとは

さて、Unmanaged BOで何が一番手間かというと、それはInteraction PhaseでのTransactional bufferとのやり取りだと思います。CRUD処理は再利用できるクラスや汎用モジュールがあるとしても、Transactional bufferはRAP固有の考え方なので既存のコードはない場合がほとんどでしょう。そこで登場したのがmanaged BO with unmanaged save(以下、Unmanagd Save)という方法です。
Unmanaged Saveを使うと、Save Sequenceのsave(実際にDBとやりとりする部分)だけを自前で実装し、その他の部分はフレームワークに任せることができます。

Unmanaged Saveの実装

RAP Generatorで生成したビジネスオブジェクトをUnmanaged Saveに変えます。
ステップは以下です。

  1. CUD用クラスの登録
  2. Behavior Definitionの設定
  3. Saverクラスの実装

1. CUD用クラスの登録

PersonエンティティのCUD(Create, Update, Delete)用クラスを作成します。
事前にメソッドの受け渡しに使うテーブルタイプを更新対象のテーブルと同じ型で登録しておきます。
image.png

以下がクラスの実装です。受け取ったデータを使ってデータを更新するだけのごくシンプルな処理です。

CLASS zc_save_person DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS create
      IMPORTING persons TYPE ztt_person.
    METHODS update
      IMPORTING persons TYPE ztt_person.
    METHODS delete
      IMPORTING persons TYPE ztt_person.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zc_save_person IMPLEMENTATION.
  METHOD create.
    INSERT zperson_m FROM TABLE @persons.
  ENDMETHOD.

  METHOD update.
    UPDATE zperson_m FROM TABLE @persons.
  ENDMETHOD.

  METHOD delete.
    DELETE zperson_m FROM TABLE @persons.
  ENDMETHOD.
ENDCLASS.

2. Behavior Definitionの設定

Unmanaged Saveに変えるために、Behavior Definitionを以下のように変更します。
保存の方法としてのpersistent table ...をコメントアウトし、代わりにwith unmanaged saveを指定します。

managed implementation in class zbp_person_m unique;
strict;
with draft;

define behavior for ZI_PERSON_M alias Person
//persistent table zperson_m コメントアウト
with unmanaged save

unmanagedの部分に警告が出るのでCtrl + 1でQuick Fixを開き、"Add required method save_modified..."をダブルクリックします。
image.png

Behavior ImplementationクラスのLocal Typesに以下の定義が追加されます。

CLASS lsc_zi_person_m DEFINITION INHERITING FROM cl_abap_behavior_saver.

  PROTECTED SECTION.

    METHODS save_modified REDEFINITION.

ENDCLASS.

CLASS lsc_zi_person_m IMPLEMENTATION.

  METHOD save_modified.
  ENDMETHOD.

ENDCLASS.

3. Saverクラスの実装

Saver クラスのメソッドsave_modifiedを実装します。処理の大枠は以下のようになります。

  METHOD save_modified.
    IF create-person IS NOT INITIAL.
      "登録処理
    ENDIF.

    IF update-person IS NOT INITIAL.
      "更新処理
    ENDIF.

    IF delete-person IS NOT INITIAL.
      "削除処理
    ENDIF.

  ENDMETHOD.

3.1. 登録処理

登録時は、DB登録用の構造にcreate-personで入ってきた構造をマッピングし、create用のメソッドに渡します。
このケースのように、DBの項目名とCDS Viewの項目名が違う場合はマッピングが必要になります。

    DATA person_db TYPE STANDARD TABLE OF zperson_m.
    DATA(lo_save_person) = NEW zc_save_person(  ).

    IF create-person IS NOT INITIAL.
      "項目マッピング
      person_db = CORRESPONDING #( create-person mapping
                                     person_uuid = PersonUUID
                                     first_name  = FirstName
                                     last_name   = LastName
                                     email       = Email
                                     birthday    = Birthday
                                     status      = Status
                                     created_by  = CreatedBy
                                     created_at  = CreatedAt
                                     last_changed_by       = LastChangedBy
                                     local_last_changed_at = LocalLastChangedAt
                                     last_changed_at       = LastChangedAt ).

      lo_save_person->create( person_db ).

    ENDIF.

3.2. 更新処理

更新時はupdate-person-%controlの構造で、更新された項目に01が設定されます。以下はデバッグで見たupdate-personの中身です。
image.png
処理の流れは以下のようになります。
①update-personからキーを取得
②データベースから変更前の全項目を取得
③各項目について%controlの値をチェックし、更新されていたらupdate-entityの項目で上書き
④update用メソッドを呼ぶ

上の画面の通り、update-personには更新された項目も更新されていない項目も入ってきているので、%controlと比較せず全項目update-personから上書きしてもよいのではとも思いますが、Helpのサンプルソースのやり方に従いました。

    IF update-person IS NOT INITIAL.
      person_db = CORRESPONDING #( update-person mapping person_uuid = PersonUUID ).

      " データベースから全項目を取得
      SELECT * FROM zperson_m
        FOR ALL ENTRIES IN @person_db
        WHERE person_uuid = @person_db-person_uuid
        INTO TABLE @person_db.

      "変更された項目で上書きする
      LOOP AT update-person ASSIGNING FIELD-SYMBOL(<u_person>).
        ASSIGN person_db[ person_uuid = <u_person>-PersonUUID ]
        TO FIELD-SYMBOL(<person_db>).

        IF <u_person>-%control-FirstName = if_abap_behv=>mk-on.
          <person_db>-first_name = <u_person>-FirstName.
        ENDIF.

        IF <u_person>-%control-LastName = if_abap_behv=>mk-on.
          <person_db>-last_name = <u_person>-LastName.
        ENDIF.

        IF <u_person>-%control-Email = if_abap_behv=>mk-on.
          <person_db>-email = <u_person>-Email.
        ENDIF.

        IF <u_person>-%control-Birthday = if_abap_behv=>mk-on.
          <person_db>-birthday = <u_person>-Birthday.
        ENDIF.

        IF <u_person>-%control-Status = if_abap_behv=>mk-on.
          <person_db>-status = <u_person>-Status.
        ENDIF.

        IF <u_person>-%control-LastChangedBy = if_abap_behv=>mk-on.
          <person_db>-last_changed_by = <u_person>-LastChangedBy.
        ENDIF.

        IF <u_person>-%control-LocalLastChangedAt = if_abap_behv=>mk-on.
          <person_db>-local_last_changed_at = <u_person>-LocalLastChangedAt.
        ENDIF.

        IF <u_person>-%control-LastChangedAt = if_abap_behv=>mk-on.
          <person_db>-last_changed_at = <u_person>-LastChangedAt.
        ENDIF.
      ENDLOOP.

      lo_save_person->update( person_db ).

    ENDIF.

3.3. 削除処理

削除処理はシンプルに、delete-personからキーのみをマッピングし、delete用のメソッドに渡します。

    IF delete-person IS NOT INITIAL.
      "項目マッピング(keyのみ)
      person_db = CORRESPONDING #( delete-person mapping person_uuid = PersonUUID ).

      lo_save_person->delete( person_db ).
    ENDIF.

動作確認

CUD処理

登録、更新、削除は以前と同じように動きます。以下は更新前後でのテーブルの内容を確認した結果です。

更新前
image.png

Last Nameを更新した後
image.png

変更した項目と、タイムスタンプ項目が更新されました。

アクション

アクションを使って項目(ステータス)を更新したあとはどうでしょうか?
image.png
image.png
アクションで更新した項目がDBに反映され、タイムスタンプも更新されました。
image.png

エラーのときは?

保存処理でエラーが起こった場合はどうすればよいでしょうか。なお、Unmanaged SaveでもValidationで定義したロジックは動くので、ここでのエラーはDBが更新できないなど特殊なケースに限られます。
image.png
SAP Help: Integrating Unmanaged Save in Managed Business Objectsより引用

Saverクラスのスーパークラスであるcl_abap_behavior_saverを見てみると、メソッドsave_modifiedはreportedとfailedをchangingパラメータに持っているのでValidationと同じように処理すればよさそうです。

  methods SAVE_MODIFIED
    importing
      !CREATE type DATA
      !UPDATE type DATA
      !DELETE type DATA
    changing
      !REPORTED type DATA optional
      !FAILED type DATA optional .

ところが、コンパイラで「"failed"というパラメータがない」というエラーになってしまいました。
image.png

そこでfailedの設定はあきらめ、reportedだけ設定します。

    IF update-person IS NOT INITIAL.
      "エラーにする
*        "Set failed keys
*        APPEND VALUE #( %is_draft = if_abap_behv=>mk-off
*                        personuuid = update-person[ 1 ]-PersonUUID )
*               TO failed-person.    

      "Set message
      APPEND VALUE #( %key = update-person[ 1 ]-%key
                      %is_draft = if_abap_behv=>mk-off
                      personuuid = update-person[ 1 ]-PersonUUID
                      %element-FirstName = if_abap_behv=>mk-on
                      %msg = new_message(
                               id       = 'ZRAP_MSG_YASU2122_2'
                               number   = 002
                               severity = if_abap_behv_message=>severity-error
                             ) )
             TO reported-person.

      RETURN.

実行するとエラーメッセージは出ますが、保存が成功したようなMessage Toastも出るという状態です。
image.png

ここまで試した限りでは、Saverクラスの中でカスタムのエラーを設定するのは難しいという結論です。もともとビジネスロジックによるチェックはValidationで行うべきものなので、Saverクラスの中でエラーを出すことは基本的にないのかもしれません。

しかし、そうだとすると既存の保存ロジックって本当に使い回せるの?という疑問がわきます。既存ロジック(たとえば汎用モジュール)には、チェック処理も入っているものが多いのではないでしょうか。
シンプルにDBに保存するだけの既存ロジックがある場合にしかUnmanaged Saveを使えないのか、あるいはチェックの部分だけ切り出してValidationで事前にかけておくのか(保存時にもチェックはかかるがそこは100%パスする状態にしておく)。ちょっとモヤっとしてこの回は終わります。

2022/1/11更新
SAP Communityに質問したところ、以下の回答をいただきました。

Naveen Kumar Chikkanna

  • save_modifiedは失敗してはならない
  • トランザクションを止める必要があるときはショートダンプを発生させるしかない

Andre Fischer

  • 入力されたデータが正しいかどうかはVaidationまたはCheckBeforeSaveを使い、ショートダンプの発生を防止する必要がある
  • REPORTEDはInfo/Warningメッセージを返すために使う
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