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:
- how the one order framework can be applied in an OO landscape
- entity relationships between an enforcement action and a debt set
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.
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)
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.
There are three main steps that will be required in order to implement the required changes for the RAP framework:
- Define a CDS root entity for the non-standard table (ZR_DS_CHARGE_ROOT)
- Create a Behaviour Definition for the root entity (ZR_DS_CHARGE_ROOT )
- Create a Behaviour Class(ZBP_R_DS_CHARGE_ROOT)
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.
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 | Purpose |
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).
Conclusion
The article has shown that the ABAP RAP framework can be utilised effectively without comprising on sound OO concepts. Complex transactional processing can also be accomodated using the RAP framework by delegating processing to micro services. The micro services themselves may defer to other smaller micro services to complete the overall business process.
A successful implementation of a business process has a reliance on a number of factors. From a micro service perspective one of the key elements is to have an understanding of the entities involved and the data flow between these entities. Having a solid understanding of the data flow means that a relevant behaviour definition can be created. The behaviour definition is the key to the ABAP RAP framework so ensure that the foundations and appropriate considerations are are made prior to embarking on an implementation.
Please feel free to supply comments on thoughts on this blog. The following links may also be of benefit to readers.
https://community.sap.com/topics/abap
https://blogs.sap.com/tags/833755570260738661924709785639136/
https://answers.sap.com/tags/833755570260738661924709785639136