はじめに
この記事では、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につなぎたい場合に使用されます。
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に変えます。
ステップは以下です。
- CUD用クラスの登録
- Behavior Definitionの設定
- Saverクラスの実装
1. CUD用クラスの登録
PersonエンティティのCUD(Create, Update, Delete)用クラスを作成します。
事前にメソッドの受け渡しに使うテーブルタイプを更新対象のテーブルと同じ型で登録しておきます。
以下がクラスの実装です。受け取ったデータを使ってデータを更新するだけのごくシンプルな処理です。
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..."をダブルクリックします。
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の中身です。
処理の流れは以下のようになります。
①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処理
登録、更新、削除は以前と同じように動きます。以下は更新前後でのテーブルの内容を確認した結果です。
変更した項目と、タイムスタンプ項目が更新されました。
アクション
アクションを使って項目(ステータス)を更新したあとはどうでしょうか?
アクションで更新した項目がDBに反映され、タイムスタンプも更新されました。
エラーのときは?
保存処理でエラーが起こった場合はどうすればよいでしょうか。なお、Unmanaged SaveでもValidationで定義したロジックは動くので、ここでのエラーはDBが更新できないなど特殊なケースに限られます。
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"というパラメータがない」というエラーになってしまいました。
そこで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も出るという状態です。
ここまで試した限りでは、Saverクラスの中でカスタムのエラーを設定するのは難しいという結論です。もともとビジネスロジックによるチェックはValidationで行うべきものなので、Saverクラスの中でエラーを出すことは基本的にないのかもしれません。
しかし、そうだとすると既存の保存ロジックって本当に使い回せるの?という疑問がわきます。既存ロジック(たとえば汎用モジュール)には、チェック処理も入っているものが多いのではないでしょうか。
シンプルにDBに保存するだけの既存ロジックがある場合にしかUnmanaged Saveを使えないのか、あるいはチェックの部分だけ切り出してValidationで事前にかけておくのか(保存時にもチェックはかかるがそこは100%パスする状態にしておく)。ちょっとモヤっとしてこの回は終わります。
2022/1/11更新
SAP Communityに質問したところ、以下の回答をいただきました。
-
save_modified
は失敗してはならない - トランザクションを止める必要があるときはショートダンプを発生させるしかない
- 入力されたデータが正しいかどうかはVaidationまたはCheckBeforeSaveを使い、ショートダンプの発生を防止する必要がある
- REPORTEDはInfo/Warningメッセージを返すために使う