SAP S/4HANA, ABAP RESTful Application Programming Model

S/4Hana RAP Unmanaged framework in a complex Transactional environment using micro services

Introduction

In this article we will explore how complex transactional processing can be implemented using the ABAP Restful Application Processing framework. The article will demonstrate how the RAP unmanaged scenario supports the reuse of existing code and how best OO practices can be maintained in a complicated transactional environment. The central design concept is that the methods of the unmanaged behavior class are managed by a factory class and the factory class will delegate its processing to micro services.

The use case in this article resolves around transactional processing for receivables grouped by a debt set. The concept of the debt set and transactional processing using the one order framework can be explored in earlier blogs:

Description of the Use Case

Let us assume that we want to create a business process which will apply a charge to a group of receivables managed by an existing debt set. The requirement is that the relevant details of the charge should be validated and captured. The transactional details of the charge will be captured in a custom specific table and there will be a financial impact to the processing. A financial document will be created and the items of financial document will be assigned to the debt set.

In the diagram below(diagram 1) the landscape of the technical artefacts are shown prior to creating the new charge.

The diagram shows that receivable items are grouped into a debt set of type ZDS and when the debt set has was created a corresponding enforcement action of type ZEA was created. The two entities are linked together by table ZI_DS_EALINK, which is essentially an entry in the physical table CRMS4D_EXT_REF.

While as a general principle a debt set can be linked to many different enforcement actions. In this use case a debt set of type ZDS will be linked to just one enforcement action of type ZEA. This design principle explains the 1:1 associations in Diagram 1.

Diagram 1

In the diagram below(diagram 2) the landscape of the technical artefacts are shown after the creation of a charge.

The persistent table that contains the details of the charge is ZT_DS_CHARGE. When recording the transactional details of the charge, a one order transaction type of ZCHG is also created and the details of the charge and the transaction will be linked together by the ZCHG transaction guid which is stored on the charge record. When creating the ZCHG activity, the activity is linked to the ZEA enforcement action using a doc flow relationship. Importantly when the financial details of the charge are recorded in the PSCD system the document number will link the transactional charge details(ZI_DS_CHARGE) to the financial document(DFKKKO).

New associations that will realised on the creation of the charge are:

  • _chrgACTV (charge to an activity)
  • _chrgDOC (charge to a fica document in PSCD)
  • _chrgDS (charge to a debt set in DCM)
  • _actvDOCFLOW (activity to enforcement action via docflow)
  • _dsCHRG (debt set to a charge)
Diagram 2

Custom Charge Table

The diagram below(diagram 3) shows the structure of the custom charge table ZT_DS_CHARGE. As a best practice the structure has been segmented into various components i.e. context, data and admin.

@EndUserText.label : 'Custom Charge'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zt_ds_charge {
  key client : abap.clnt not null;
  key guid   : /bobf/conf_key not null;
  gr_cntxt   : include zs_charge_cntxt;
  gr_data    : include zs_charge_data;
  gr_admin   : include zs_charge_admin;

}

@EndUserText.label : 'Charge Context'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
define structure zs_charge_cntxt {
  company_code     : bukrs;
  debt_set_number  : dcm_debt_set_number_kk;
  partner_id       : bu_partner;
  transaction_guid : crmt_object_guid;
  transaction_id   : crmt_object_id;

}

@EndUserText.label : 'Charge Data'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
define structure zs_charge_data {
  @Semantics.amount.currencyCode : 'zt_ds_charge.charge_curr'
  charge_amount      : betrw_kk;
  charge_curr        : blwae_kk;
  charge_type          : char3; 
  charge_reason_code   : char3;
  charge_date          : dats;
  charge_document      : opbel_kk;
  reverse_document     : opbel_kk;
  reverse_date         : dats;
  reverse_reason       : char3;
  status_code          : char3;

}

@EndUserText.label : 'Admin fields for charge'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
define structure zs_charge_admin {
  created_on : timestampl;
  created_by : syuname;
  changed_on : timestampl;
  changed_by : syuname;

}

Diagram 3

RAP Framework and the Use Case

Now that we have an understanding of the use case and the relationships of the data we can embark on constructing artifacts using the ABAP Restful Application Processing framework.

A key feature of the RAP framework is its runtime process flow. There are two main phases in the process flow, namely, the interaction phase and the save sequence. The details of the two phases can be seen below in diagram 4. When the behaviour class is auto generated there will be two local classes created, one class to control the interaction phase and the other to control the save phase.

Diagram 4

There are three main steps that will be required in order to implement the required changes for the RAP framework:

  1. Define a CDS root entity for the non-standard table (ZR_DS_CHARGE_ROOT)
  2. Create a Behaviour Definition for the root entity (ZR_DS_CHARGE_ROOT )
  3. Create a Behaviour Class(ZBP_R_DS_CHARGE_ROOT)
Diagram 5

In the above diagram( ie diagram 5), the methods that belong to the interaction and save sequence will delegate their processing to a factory class, namely, zcl_bo_ds_charge. The factory concept is a key element in ensuring that no unsightly technical debt is created in the local classes. The factory class will simply delegate its processing to smaller micro services which have been built using sound OO and clean ABAP concepts.

Define the CDS root view for Custom Charge

The details of the custom charge entity are depicted below by the root view in diagram 6.

define root view entity ZR_DS_CHARGE_ROOT
  as select from ZI_DS_CHARGE
{
  key Guid,
      CompanyCode,
      DebtSetNumber,
      PartnerId,
      TransactionGuid,
      TransactionId,
      ChargeAmount,
      ChargeCurr,
      ChargeType,
      ChargeReasonCode,
      ChargeDocument,
      ChargeDate,
      ReverseDocument,
      ReverseDate,
      ReverseReason,
      StatusCode,
      CreatedOn,
      CreatedBy,
      ChangedOn,
      ChangedBy

}

Diagram 6

Define the behaviour Definition

The next phase of utilising the RAP framework is to create the behaviour definition. The details below in diagram 7 show that only create, update and read operations are allowed, however, there is one action, namely, reverseCHRG that is also defined. The details in diagram 7 also show the mapping between the physical attributes of table ZT_DS_CHARGE and the root entity ZR_DS_CHARGE_ROOT.

implementation unmanaged in class zbp_r_ds_charge_root unique;
define behavior for ZR_DS_CHARGE_ROOT alias DcmCharge
late numbering in place
etag master ChangedOn

{
  create;
  update;
  action reverseCHRG parameter zrk_ds_charge_reverse_action result [1] $self;
  mapping for ZT_DS_CHARGE control zs_ds_charge_control
  {
    Guid = guid;
    CompanyCode = company_code;
    DebtSetNumber = debt_set_number;
    PartnerId = partner_id;
    TransactionGuid = transaction_guid;
    TransactionId = transaction_id;
    ChargeAmount = charge_amount;
    ChargeCurr = charge_curr;
    ChargeType = charge_type;
    ChargeReasonCode = charge_reason_code;
    ChargeDocument = charge_document;
    ChargeDate = charge_date;
    ReverseDocument = reverse_document;
    ReverseDate = reverse_date;
    ReverseReason = reverse_reason;
    StatusCode = status_code;
    CreatedOn = created_on;
    CreatedBy = created_by;
    ChangedOn = changed_on;
    ChangedBy = changed_by;

  }

  //Define fields that can only be set internally
  field ( readonly ) guid, createdby, createdon, changedby, changedon;
}

@EndUserText.label: 'Reverse Charge action'
define abstract entity zrk_ds_charge_reverse_action 
//  with parameters parameter_name : parameter_type 
  {
    reverseReason : char3;
    
}

@EndUserText.label : 'Control structure for Unamanged RAP'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
define structure zs_ds_charge_control {
  guid                 : xsdboolean;
  company_code         : xsdboolean;
  debt_set_number      : xsdboolean;
  partner_id           : xsdboolean;
  transaction_guid     : xsdboolean;
  transaction_id       : xsdboolean;
  charge_amount        : xsdboolean;
  charge_curr          : xsdboolean;
  charge_type          : xsdboolean;
  charge_reason_code   : xsdboolean;
  charge_document      : xsdboolean;
  charge_date          : xsdboolean;
  restart_document     : xsdboolean;
  reverse_date         : xsdboolean;
  reverse_reason       : xsdboolean;
  status_code          : xsdboolean;
  created_on           : xsdboolean;
  created_by           : xsdboolean;
  changed_on           : xsdboolean;
  changed_by           : xsdboolean;

}

Diagram 7

Define the behaviour Class

The details of the behaviour class will be found in the local class section. There will be two local classes that are created. One class will control the interaction phase and the other will control the save phase.

  • cl_abap_behavior_handler (Interaction phase)
  • cl_abap_behavior_save (save phase)
CLASS zbp_r_ds_charge_root DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zr_ds_charge_root.
ENDCLASS.

CLASS zbp_r_ds_charge_root IMPLEMENTATION.
ENDCLASS.

Interaction Phase Local definition

In the details below(diagram 8) only the create and read action have been implemented along with the action of reverse charge.

The implementation of the local interaction phase is relatively simple due to the use of a factory class which delegates its processing to other micro services. In the create method of the factory will simply perform validation and if validation are successful then the entity will be cached for later processing in the save phase.

CLASS lhc_DcmCharge DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS create FOR MODIFY
      IMPORTING entities FOR CREATE DcmCharge.

    METHODS update FOR MODIFY
      IMPORTING entities FOR UPDATE DcmCharge.

    METHODS read FOR READ
      IMPORTING keys FOR READ DcmCharge RESULT result.

    METHODS reverse_charge FOR MODIFY
      IMPORTING entities  FOR ACTION DcmCharge~reverseChrg
      RESULT    et_return.

ENDCLASS.

CLASS lhc_DcmCharge IMPLEMENTATION.

  METHOD create.
    DATA lo_bo_ds_charge    TYPE REF TO zif_bo_ds_charge.
    lo_bo_ds_charge = zcl_bo_ds_charge=>get_factory(  ).

    lo_bo_ds_charge->create(  EXPORTING it_entities = entities
                            CHANGING  ct_mapped   = mapped-dcmcharge
                                      ct_failed   = failed-dcmcharge
                                      ct_reported = reported-dcmcharge  ).

  ENDMETHOD.

  METHOD update.
  ENDMETHOD.

  METHOD read.
    DATA lo_bo_ds_charge    TYPE REF TO zif_bo_ds_charge.
    lo_bo_ds_charge = zcl_bo_ds_charge=>get_factory(  ).

    lo_bo_ds_charge->read(  EXPORTING it_keys = keys
                          CHANGING  ct_result   = result
                                    ct_failed   = failed-dcmcharge ).

  ENDMETHOD.

  METHOD reverse_charge.
    DATA lo_bo_ds_charge    TYPE REF TO zif_bo_ds_charge.
    lo_bo_ds_charge = zcl_bo_ds_charge=>get_factory(  ).
    lo_bo_ds_charge->reverse_charge(  EXPORTING it_entities = entities
                                      CHANGING  ct_mapped   = mapped-dcmcharge
                                                ct_failed   = failed-dcmcharge
                                                ct_reported = reported-dcmcharge  ).
  ENDMETHOD.

ENDCLASS.

Diagram 8

Save Phase Local definition

The diagram below shows that the save method on the factory class is invoked. This save method on the factory class will process the details from cache and ensure that processing is delegated to other micro services..

CLASS lsc_ZR_DS_CHARGE_ROOT DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.

    METHODS check_before_save REDEFINITION.

    METHODS finalize          REDEFINITION.

    METHODS save              REDEFINITION.

    METHODS adjust_numbers    REDEFINITION.

ENDCLASS.

CLASS lsc_ZR_DS_CHARGE_ROOT IMPLEMENTATION.

  METHOD check_before_save.
  ENDMETHOD.

  METHOD finalize.
  ENDMETHOD.

  METHOD save.
    DATA lo_bo_ds_charge    TYPE REF TO zif_bo_ds_charge.
    lo_bo_ds_charge = zcl_bo_ds_charge=>get_factory(  ).

    lo_bo_ds_charge->save( ).

  ENDMETHOD.

  METHOD adjust_numbers.
  ENDMETHOD.

ENDCLASS.

Diagram 8

Factory Class

The key design decision in ensuring sound OO practices are adhered to is the decision to use a factory class. The factory class means that the implementation of the methods for the interaction and save phases is simplified to a corresponding method call to the factory class.

One of the main features of the factory class is to maintain a buffer, once all the entities have passed validation, the buffer will be processed by the save sequence. The factory essentially functions as an interface or adapter between the RAP business object entities and the actual DAOs required to perform the processing. The details of the factory class and how the factory class delegates to other micro services can be seen in diagram 9 and 9.1. The factory class has a dependency on three other interfaces zif_ds_charge, zif_ds_charge_reverse and zif_ds_charge_dao.

Diagram 9

The table below outlines the methods of the factory class and where processing is delegated to. For example the read method of the factory class will delegate its processing to zif_ds_charge~read.

Method Delegate To 
read  zif_ds_charge_dao~read
create   zif_ds_charge~create(simulation_mode) 
reverse_charge   zif_ds_charge_reverse~create(simulation_mode) 
save  zif_ds_charge~create; zif_ds_charge_reverse~create 

Diagram 9.1

Factory Interface

An important aspect in the unmanaged use case is the creation and processing of a buffer. The interface for the factory ZIF_BO_DS_CHARGE contains the definition for the buffer(diagram 10) and the buffer will be created during the execution of the create method on the factory class. The save method of the factory class will process the buffer and perform the actual updates as outlined in diagram 2.

TYPES: BEGIN OF ty_s_chrg_buffer,
           debt_set_number   TYPE dcm_debt_set_number_kk,
           entity_data       TYpe zt_ds_charge,
         END OF   ty_s_chrg_buffer.

  TYPES:
     ty_t_chrg_buffer         TYPE TABLE OF ty_s_chrg_buffer.

Diagram 10

The factory interface also contain other important type definitions

TYPES:
    ty_t_ent_keys            TYPE TABLE FOR READ IMPORT zr_ds_charge_root .
  TYPES:
    ty_t_ent_upd             TYPE TABLE FOR UPDATE zr_ds_charge_root.
  TYPES:
    ty_t_ent_add             TYPE TABLE FOR CREATE zr_ds_charge_root .
  TYPES:
    ty_t_chrg_map             TYPE TABLE FOR MAPPED zr_ds_charge_root .
  TYPES:
    ty_t_chrg_mapped          TYPE TABLE FOR MAPPED LATE zr_ds_charge_root .
  TYPES:
    ty_t_chrg_failed          TYPE TABLE FOR FAILED zr_ds_charge_root .
  TYPES:
    ty_t_chrg_reported        TYPE TABLE FOR REPORTED zr_ds_charge_root .
  TYPES:
    ty_t_chrg_read_result     TYPE TABLE FOR READ RESULT zr_ds_charge_root .
  TYPES:
    ty_t_reverse_charge    TYPE TABLE FOR ACTION IMPORT zr_ds_charge_root~reversechrg .

Diagram 11

The definitions of the create, read, update, check_before_save, save and reverse_charge methods can be seen in diagram 12.

METHODS create
    IMPORTING
      !it_entities TYPE ty_t_ent_add
    CHANGING
      !ct_mapped   TYPE ty_t_chrg_map
      !ct_failed   TYPE ty_t_chrg_failed
      !ct_reported TYPE ty_t_chrg_reported .

  METHODS read
    IMPORTING
      !it_keys   TYPE ty_t_ent_keys
    CHANGING
      !ct_failed TYPE ty_t_chrg_failed
      !ct_result TYPE ty_t_chrg_read_result.

  METHODS update .
  METHODS check_before_save .
  METHODS save .
  METHODS reverse_charge
    IMPORTING
      !it_entities TYPE ty_t_reverse_charge
    CHANGING
      !ct_mapped   TYPE ty_t_chrg_map
      !ct_failed   TYPE ty_t_chrg_failed

Diagram 12

Factory Realisation

In the following section will will see how the methods from the factory interface have been implemented and how processing is delegated to other micro services. In order that the factory class methods can be used and instance of the factory must be instantiated using the get_factory method.

A summary of the methods of the class are listed below with a summary of the basic purpose

Method Delegate To 
get_factory create an instance of the factory class
add_message convert a message of type bapiret2 to a rap message
create delegate processing to micro service zcl_ds_charge and build buffer
read delegate processing to micro service zcl_ds_charge_dao
reverse_charge delegate processing to micro service zcl_ds_charge_reverse and build buffer
save delegate processing to zcl_ds_charge or zcl_ds_charge_reverse depending on buffer.

Get_Factory method

METHOD get_factory.
    IF factory IS INITIAL.
      factory = NEW zcl_bo_ds_charge( ).
    ENDIF.
    rr_instance = factory.
  ENDMETHOD.

Add_Message method

The delegation classes that perform the actual validations will return an error structure of type bapiret2_tab. If an error is encountered then we require a means to map this message structure to a RAP message behavior.

The constructor will call cl_rap_plmi_msg_convert to create reference to mapping classes(refer diagram 13)

  • mo_bapi_message_convert
  • mo_sy_message_convert

If an error is encountered in the interaction phase then ct_reported will be populated with an appropriate RAP message.

<ls_reported>-%msg will be populated with behaviour message type.

DATA mo_bapi_message_convert TYPE REF TO if_rap_plmi_bapi_msg_convert .
    DATA mo_sy_message_convert TYPE REF TO if_rap_plmi_sy_msg_convert .

     cl_rap_plmi_msg_convert=>get_mapper(
  IMPORTING
    eo_mapper_symsg = mo_sy_message_convert
    eo_mapper_bapi  = mo_bapi_message_convert ).

Diagram 13

The snipet below(diagram 14) shows how the mapping between a bapiret message and and a rap message.

METHODS add_message
      IMPORTING
        !iv_cid        TYPE string OPTIONAL
        !iv_set_failed TYPE abap_bool DEFAULT abap_true
        !is_bapiret    TYPE bapiret2 OPTIONAL
      CHANGING
        !ct_failed     TYPE zif_bo_ds_charge=>ty_t_chrg_failed
        !ct_reported   TYPE zif_bo_ds_charge=>ty_t_chrg_reported .


  METHOD add_message.
    DATA(ls_message) = CORRESPONDING symsg( sy ).

    APPEND INITIAL LINE TO ct_reported ASSIGNING FIELD-SYMBOL(<ls_reported>).

    <ls_reported>-%cid            = iv_cid.
    <ls_reported>-%msg            = COND #(
      WHEN is_bapiret IS INITIAL
      THEN mo_sy_message_convert->map_symsg_to_behv_message( ls_message )
      ELSE mo_bapi_message_convert->map_bapi_to_behv_message( is_bapiret ) ).

    IF iv_set_failed = abap_true.
      APPEND INITIAL LINE TO ct_failed ASSIGNING FIELD-SYMBOL(<ls_failed>).
      <ls_failed>-%cid            = iv_cid.
    ENDIF.

  ENDMETHOD.

Diagram 14

Create method

The create method forms part of the interaction phase which means there shouldn’t be any database updates during this phase of the RAP framework. For this reason the class zcl_ds_charge is invoked in simulation mode. In simulation mode, the delegated class will only perform validation checks.

If no validation errors are encountered then an entry is added to the charge buffer.

  • buffer i.e. mt_chrg_buffer type zif_bo_ds_charge~ty_t_chrg_buffer.
  • delegation class i.e. mo_ds_charge = NEW zcl_ds_charge( ).
METHOD zif_bo_ds_charge~create.
    DATA lt_message      TYPE bapiret2_tab.
    DATA ls_chrg_buffer  TYPE zif_bo_ds_charge~ty_s_chrg_buffer.

    REFRESH me->mt_chrg_buffer.
    LOOP AT it_entities ASSIGNING FIELD-SYMBOL( <fs_chrg> ).
      CLEAR ls_chrg_buffer.
      DATA(ls_chrg) =  <fs_chrg>-%data.
      lt_message = me->mo_ds_charge->create( iv_debt_set_number = ls_chrg-debtsetnumber
                                                iv_reason          = CONV #( ls_chrg-chargereasoncode )
                                                iv_charge_type     = ls_chrg-chargetype  
                                                iv_simulation      = abap_true ).
      READ TABLE lt_message WITH KEY type = 'E' TRANSPORTING NO FIELDS.
      IF sy-subrc EQ 0.
        me->add_message( EXPORTING iv_cid      =  <fs_chrg>-%cid
                                   is_bapiret  = lt_message[ 1 ]
                         CHANGING  ct_failed   = ct_failed
                                   ct_reported = ct_reported ).
      ELSE.
        APPEND VALUE #( %cid  = <fs_chrg>-%cid ) TO ct_mapped. 
        ls_chrg_buffer-debt_set_number = ls_chrg-debtsetnumber. 
        ls_chrg_buffer-entity_data = CORRESPONDING #( <fs_chrg> MAPPING FROM ENTITY USING CONTROL ). 
        APPEND ls_chrg_buffer TO me->mt_chrg_buffer.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

Read method

The technical key of the charge entity ZR_DS_CHARGE_ROOT is a GUID which is supplied to the delegated dao class zcl_ds_charge_dao. The dao will perform an actual database read of the physical table zt_ds_charge. The details returned by the DAO which will need to be mapped back to the root entity zr_ds_charge_root using the MAPPING to ENTITY feature.

METHOD zif_bo_ds_charge~read.

    LOOP AT it_keys INTO DATA(key).
      DATA(ls_chrg) = mo_ds_charge_dao->get_chrg_by_guid( key-guid ).
      IF ls_chrg IS NOT INITIAL.
        INSERT CORRESPONDING #( ls_chrg MAPPING TO ENTITY )
              INTO TABLE ct_result.
      ELSE.
        APPEND VALUE #( guid = key-guid
                       %fail-cause = if_abap_behv=>cause-not_found )
                       TO ct_failed.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

Reverse_Charge method

The reverse charge method is similar in nature to the create process in that the details of the request need to be validated and the request details stored in buffer until the save phase is invoked. The reverse_charge action has a parameter that will be passed in, namely, the reverseReason.

If no validation errors are encountered then an entry is added to the reverse charge buffer.
Note: Another approach could be to utilise the same buffer and not to have a separate reverse_charge buffer. If the same buffer is used then the buffer would need to be amended to include an action type of say, either “ADD” or “REV”. The action type would drive the processing in the SAVE method and direct processing to the appropraite micro service.

  • buffer i.e. mt_reverse_chrg_buffer type zif_bo_ds_charge~ty_t_reverse_chrg_buffer.
  • delegation class i.e. mo_ds_charge_reverse = NEW zcl_ds_charge_reverse( ).

If validation error are encountered then ct_failed and ct_reported are populated.

METHOD zif_bo_ds_charge~reverse_charge.
    DATA lt_message              TYPE bapiret2_tab.
    DATA ls_reverse_chrg_buffer   TYPE zif_bo_ds_charge~ty_s_reverse_chrg_buffer.
    DATA lv_guid                 TYPE /bobf/conf_key.

    REFRESH me->mt_reverse_chrg_buffer.
    LOOP AT it_entities ASSIGNING FIELD-SYMBOL(<fs_chrg>).
      CLEAR ls_reverse_chrg_buffer.
      lv_guid = <fs_chrg>-guid.
      DATA(ls_chrg)  = me->mo_ds_charge_dao->>get_chrg_by_guid(  lv_guid  ).
      DATA(lv_reverse_reason) = <fs_chrg>-%param-reversereason.
      lt_message = me->mo_ds_charge_reverse->create( iv_debt_set_number       = ls_chrg-debt_set_number
                                                      iv_reverse_reason        = CONV #( lv_reverse_reason )
                                                      iv_simulation            = abap_true ).
      READ TABLE lt_message WITH KEY type = 'E' TRANSPORTING NO FIELDS.
      IF sy-subrc EQ 0.
        APPEND VALUE #( %tky  = <fs_chrg>-%tky )
                 TO ct_failed.
        APPEND VALUE #( %tky  = <fs_chrg>-%tky
                        %msg  = mo_bapi_message_convert->map_bapi_to_behv_message( lt_message[ 1 ] ) )
                 TO ct_reported.
      ELSE.
        APPEND VALUE #( %tky  = <fs_chrg>-%tky
                        guid  = <fs_chrg>-guid )
                         TO ct_mapped.
        ls_reverse_chrg_buffer-debt_set_number            = ls_chrg-debt_set_number.
        ls_reverse_chrg_buffer-entity_data                = CORRESPONDING #( ls_chrg ).
        ls_reverse_chrg_buffer-entity_data-reverse_reason = lv_reverse_reason.
        APPEND ls_reverse_chrg_buffer TO me->mt_reverse_chrg_buffer.
      ENDIF.
    ENDLOOP.

Save method

The save charge method will process the details for the charge bugger and the reverse_charge buffer. In the diagram below(diagram 15) we can see that the charge buffer is processed and that the micro service zcl_ds_charge is invoked. The micro service has also being built using a create and save methodology hence why both methods are executed during SAVE phase of the RAP framework.

LOOP AT me->mt_chrg_buffer ASSIGNING FIELD-SYMBOL(<fs_chrg_buffer>).
      DATA(ls_chrg) = <fs_chrg_buffer>-entity_data.
      me->mo_ds_charge->create( iv_debt_set_number = ls_chrg-debtsetnumber
                                      iv_reason          = CONV #( ls_chrg-chargereasoncode )
                                      iv_charge_type     = ls_chrg-chargetype  
                                      iv_simulation      = abap_false ).
      me->mo_ds_charge->save( ).
    ENDLOOP.

Diagram 15

The following snipet(diagram 16) shows processing for the reverse_charge buffer and again a micro service is invoked, namely, zcl_ds_charge_reverse.

LOOP AT me->mt_reverse_chrg_buffer ASSIGNING FIELD-SYMBOL(<fs_reverse_chrg_buffer>).
      DATA(ls_chrg) = <fs_reverse_chrg_buffer>-entity_data.
      me->mo_ds_charge_reverse->create( iv_debt_set_number = ls_chrg-debt_set_number
                                              iv_reverse_reason        = CONV #( ls_chrg-reverse_reason )
                                              iv_simulation      = abap_false ).
      me->mo_ds_charge_reverse->save( ).

    ENDLOOP.

Diagram 16

MICRO SERVICES

The micro services is where all the heavy lifting occurs. The micro services are built using the create and save approach. The create will perform validation and store details into buffer and then the save will perform the actual updates. The micro service zcl_ds_charge will essentially:

  • create a new row in table ZT_DS_CHARGE
  • create a new one order transaction ZCHG
  • create a new financial document and items
  • add new items to the debt set

Early blogs, as mentioned in the introduction, demonstrate how an OO approach to one order processing can be utilised to create a ZCHG transaction. The financial document can be created using BAPI_CTRACDOCUMENT_CREATE or FKK_CREATE_DOC.

Testing using EML

In order to test this use case the Entity Manipulation Language(EML) can be used. A test program can be used to populate the charge entity and then the interaction phase can be kicked off using the ‘CREATE’ keyword and the save phase using the ‘COMMIT ENTITIES’ keyword. The debugger in ADT tool can be used to step through the code in the behaviour class ZBP_R_DS_CHARGE_ROOT.

DATA lt_bo_charge        TYPE TABLE FOR CREATE zr_ds_charge_root.

 
    lt_bo_charge = VALUE #(
                        (   %cid               = cl_system_uuid=>create_uuid_c32_static( ).
                            DebtSetNumber      = iv_debt_set_number
                            ChargeType         = iv_charge_type     
                            ChargeReasonCode   = iv_charge_reason
                            CompanyCode        = iv_company_code
                         )
                        ).

    MODIFY ENTITIES OF zr_ds_charge_root
    ENTITY dcmcharge
    CREATE FROM lt_bo_charge
      REPORTED DATA(lt_reported)
      FAILED DATA(lt_failed)
      MAPPED DATA(lt_mapped).

    " When: I commit the changes
    COMMIT ENTITIES RESPONSE OF zr_ds_charge_root
      REPORTED DATA(lt_reported_save)
      FAILED DATA(lt_failed_save).