【RAP】Early Numbering


はじめに

この記事では、RAP(ABAP RESTful Application Programming Model)でキーの採番(Numbering)をする方法について紹介します。

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

RAPでの採番方法

RAPでの採番方法には、以下のようなパターンがあります。
参考:Numbering


まず大きくEarly NumberingLate Numberingがあり、Early Numberingはさらに枝分かれしています。これらの方法はBOのタイプ(Managed, Unmanaged)、ドラフトの有無にかかわらず使うことができます(※)。
Numberingの種類を説明するManaged、Unmagaedは、BOの種類とは関係なく、番号を自分で(Unmanaged)採番するか、フレームワークが(Managed)採番するかの違いを表しています。

※Late NumberingについてはUnmanagd BOでしか使えないという情報もあるので次回確認します。ドキュメントを見ると、どちらでも使えそうですが…

2021/1/15 更新
BTPのABAP環境では、Managed BOでもLate Numberingを使うことができました。

Early Numbering, Late Numberingとは

Early、Lateはキーの採番タイミングを指します。Early Numberingでは、エンティティの登録リクエストがあると即座に採番を行います。このあとでチェックやDBへの保存が行われるため、登録がキャンセルされた場合はその番号は不使用となり、番号に抜けが発生する可能性があります。Late NumberingではDBへの保存の直前に採番が行われ、この後にエラーは基本起こりえないため、番号に抜けが発生しません。

Early Numberingの分類

Early Numberingはさらに枝分かれします。External Early Numberingとは、キーをユーザの入力など外部から受け取ることを指します。これに対してInternal Early Numberingとは、自動で採番を行うことを指します。Managed Internal Early Numberingはフレームワークに番号の採番を任せる方法で、この場合キーに使えるのはUUIDのみです。Unmanaged Internal Early Numberingは自分で番号を採番する方法です。

Unmanaged Internal Early Numberingを実装

この記事では、Unmanaged Internal Early Numberingを実装してみます。この方法は、テーブルがUUIDでないキーを持ち、キーの採番を自動で行いたい場合に使用します。
ステップは以下のようになります。

  1. UUIDでないキーを持つテーブルを登録
  2. RAP GeneratorでBOを生成
  3. Early Numberingの実装

1. UUIDでないキーを持つテーブルを登録

以下のテーブルを定義します。

@EndUserText.label : 'Managed Person with Numeric Key'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zperson_num_m {
  key client            : abap.clnt not null;
  key person_id         : ze_person_id not null;
  first_name            : ze_firstname;
  last_name             : ze_lastname;
  email                 : ze_email120;
  birthday              : ze_birthday;
  status                : ze_status;
  created_by            : abp_creation_user;
  created_at            : abp_creation_tstmpl;
  last_changed_by       : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

キーのために以下のデータエレメントを定義しました。

2. RAP GeneratorでBOを生成

こちらの記事の要領でBOを生成します。

Behavior Definitionを見てみると、UUIDをキーにしていたときとは以下の違いがあります。

  • キー項目がreadonlyになっていない
  • numberingに関する指定がない

UUIDがキーのとき

{
  field ( readonly )
   PersonUUID,
   CreatedAt,
   CreatedBy,
   LastChangedAt,
   LocalLastChangedAt,
   LastChangedBy;

  field ( numbering : managed )
   PersonUUID;

UUIDがキーでないとき

{
  field ( readonly )
   CreatedAt,
   CreatedBy,
   LastChangedAt,
   LocalLastChangedAt,
   LastChangedBy;

すなわちデフォルトでは、キーを外部から受け取るExternal Early Numberingになっているということです。

3. Early Numberingの実装

3.1. Behavior Definitionの設定

Behavior Definitionの先頭にearly numberingの指定を追加します。また、キー項目であるPersonIDをreadonlyに設定します。

define behavior for ZI_PERSON_NUM_M alias Person
persistent table zperson_num_m
draft table zperson_num_m_d
etag master LocalLastChangedAt
lock master total etag LastChangedAt
authorization master( global )
early numbering //追加

{
  field ( readonly )
   CreatedAt,
   CreatedBy,
   LastChangedAt,
   LocalLastChangedAt,
   LastChangedBy,
   PersonID; //追加

createのところに警告が出るので、Quick Fix(Ctrl + 1)でメソッドを実装します。

Behavior InplementationクラスのLocal Typeにメソッドearlynumbering_createが追加されます。

CLASS LCL_HANDLER DEFINITION INHERITING FROM CL_ABAP_BEHAVIOR_HANDLER.
  PRIVATE SECTION.
    METHODS:
      GET_GLOBAL_AUTHORIZATIONS FOR GLOBAL AUTHORIZATION
        IMPORTING
           REQUEST requested_authorizations FOR Person
        RESULT result,
      earlynumbering_create FOR NUMBERING
            IMPORTING entities FOR CREATE Person.
ENDCLASS.

CLASS LCL_HANDLER IMPLEMENTATION.
  METHOD GET_GLOBAL_AUTHORIZATIONS.
  ENDMETHOD.
  METHOD earlynumbering_create.
  ENDMETHOD.

ENDCLASS.

3.2. Behavior Implementationの実装

メソッドearlynumbering_createを以下のように実装します。メソッドのリターンパラメータはfailedreportedmappedという3つのテーブルです。failed、reportedはエラーの場合に返すもので、今回重要なのがmappedです。mappedにはフレームワークで採番される仮のキー(%cid)と自分で採番したキーのマッピングを返します。
デバッグで見ると、mapped-personは以下の構造でした。つまり、ドラフトかどうかもキーに含まれるということです。

最初の考え
最初は以下のような実装を考えました。簡単にするためDBから最大のキーを取得してそれをインクリメントします。
参考:BTPで番号範囲を使うには、以下の方法があります。
https://blogs.sap.com/2020/09/03/number-ranges-sap-cloud-platform/

  METHOD earlynumbering_create.
    "Get max person id from db
    select max( person_id ) from zperson_num_m into @data(new_id).

    loop at entities ASSIGNING FIELD-SYMBOL(<person>).
      new_id += 1.
      append value #(
        %cid = <person>-%cid
        %is_draft = <person>-%is_draft
        personid = new_id
       ) to mapped-person.
    endloop.

  ENDMETHOD.

しかし、このメソッドはドラフトを登録したときと保存したときの2回動くことがわかりました。ドラフト状態のオブジェクトが複数作られるとキーがかぶってしまう可能性があります。そこで、以下のようにドラフトテーブルと本物のテーブルの両方を見に行くように変更しました。
また、保存時にドラフトと同じようにキーを採番すると、ドラフトのときのキーを上書きしてキーが変わってしまうこともわかったので、キーがすでに設定されている場合はその値をそのままmappedに渡すこととしました。

修正後

  METHOD earlynumbering_create.
    DATA new_id TYPE zperson_num_m-person_id.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<person>).
      if <person>-PersonID is not initial.
        new_id = <person>-PersonID.
      else.
        new_id = get_newid(  ).
      endif.

      APPEND VALUE #(
        %cid = <person>-%cid
        %is_draft = <person>-%is_draft
        personid = new_id
       ) TO mapped-person.

    ENDLOOP.

  ENDMETHOD.

  METHOD get_newid.
    "Get max person id from db and draft
    SELECT MAX( person_id ) FROM zperson_num_m INTO @DATA(max_id).
    SELECT MAX( personid ) FROM zperson_num_m_d INTO @DATA(max_id_draft).

    new_id = COND #( WHEN max_id > max_id_draft
                       THEN max_id
                       ELSE max_id_draft  ).
    new_id += 1.

  ENDMETHOD.

4. 動作確認

"Create"を押して新規データを登録します。

ドラフトの状態でキーが採番されます。

保存せずに戻り、もう一つデータを登録します。正しくキーがインクリメントされました。

参考

How to use Early Numbering with Semantic Keys – RAP Managed BO