How to develop Managed Business Object with Unmanaged Save functionality in ABAP RESTful application programming model

Most of the project development has now started using ABAP RESTful application programming model Capabilities as it provides immense in-built functionalities, which allows the developer to focus on real business logic.

But all the business cases cannot be realized using generic Managed Business Object functionality. Developer might require tweaking the standard OData behavior.

Choosing Unmanaged Business object will overload the developer with same burden of handling all functionalities from the scratch.

There comes the Managed Business Object with Unmanaged Save functionality at rescue.

Advantages

Managed Business Object with Unmanaged Save allows us to utilize the Managed Behavior for all the Business Object Nodes where the business requirement can be achieved using combination of Standard Create/Update/Delete, determination, validation and actions.

For the Business Object Nodes where overriding the Standard Create/Update/Delete (Behavioral change on save operation) is required, we can opt for this option.

Note – Determination, Validation, actions, draft functionality, etc. will continue to work the way it works for normal Managed Business Object Node.

Following example will guide you through the steps required to implement this capability.

Example

Details – 

In following example, I’ve created a Managed Business Object with 2 Nodes –

  • InspectionPlan (Managed Business Object with Unmanaged Save functionality<Parent Node>)
  • InspectionPlanChecks (Managed Business Object with Standard Managed behavior<Child Node>)

We have to perform following changes, in order to implement Managed Business Object with Unmanaged Save capabilities –

Step 1 – Add keyword with unmanaged save for all the nodes, where unmanaged behavior is intended.

Ensure persistence table is not specified, as unmanaged save implementation cannot have persistence table specified to it.

We can still continue using Draft table (in case of Draft enabled Business Object).

Code Snippet for Behavior Definition –

managed implementation in class zbp_i_blg_inspectionplantp unique;
strict;
with draft; define behavior for ZI_BLG_INSPECTIONPLANTP alias InspectionPlan
// persistent table ZBLG_T_INSP_TSP
draft table zblg_d_insp_tsp
with unmanaged save
lock master
total etag LastChangeDateTime
authorization master ( instance )
etag master LocalLastChangedDateTime
{ field ( readonly, numbering : managed ) InspectionPlanUUID; field ( readonly ) InspectionPlanId; field ( mandatory ) InspectionPlanName, InspectionPlanStartdate, InspectionPlanEnddate; create; update; delete; association _InspectionPlanChecks { create; with draft; } determination setInspectionPlanID on save { create; } draft action Edit; draft action Activate; draft action Discard; draft action Resume; draft determine action Prepare; mapping for zblg_t_insp_tsp control ZBLG_X_INSP_TSP corresponding { InspectionPlanUUID = uuid; InspectionPlanId = id; InspectionPlanName = name; InspectionPlanStartdate = startdate; InspectionPlanEnddate = enddate; InspectionPlanText = text; Purchasinggroup = purchasinggroup; MaterialGroup = materialgroup; DueDate = duedate; InspectionPlanIsdeleted = isdeleted; }
} define behavior for ZI_BLG_INSPECTIONPLANCHECKSTP alias InspectionPlanChecks
persistent table zblg_t_insp_chks
draft table zblg_d_insp_chks
lock dependent by _InspectionPlan
authorization dependent by _InspectionPlan
etag dependent by _InspectionPlan
{ field ( readonly, numbering : managed ) InspectionPlanCheckUUID; field ( readonly ) InspectionPlanUUID; update; delete; association _InspectionPlan { with draft; } mapping for ZBLG_T_INSP_CHKS corresponding { InspectionPlanCheckUUID = uuid; InspectionPlanUUID = inspection_uuid; QCategory = q_category; InspectionPlanCheckText = text; InspectionPlanCheckEnabled = enabled; }
}

Step 2 – Redefine method save_modified in Saver Local class implementation level.

Please note, this method contains separate parameters for all 3 – Create / Update / Delete operations.

In case any child node is also having the unmanaged save capability enabled, we can expect/receive that child node details under the same create/update/delete parameter via relevant associations.

Code Snippet for Behavior Class (Local Definition/Implementation) implementation –

CLASS lsc_zi_blg_inspectionplantp DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS save_modified REDEFINITION. ENDCLASS. CLASS lsc_zi_blg_inspectionplantp IMPLEMENTATION. METHOD save_modified. "Data Declaration DATA : lt_inspectionplan TYPE STANDARD TABLE OF zblg_t_insp_tsp. DATA : lr_inspectionplan_uuid TYPE RANGE OF zblg_t_insp_tsp-uuid. DATA : lt_inspectionplan_controlflag TYPE STANDARD TABLE OF zblg_x_insp_tsp. DATA : lt_inspectionplan_upd TYPE STANDARD TABLE OF zblg_t_insp_tsp. "Implement Logic for all possible Save Operations - Create / Update / Delete IF create IS NOT INITIAL. "Populate the values lt_inspectionplan = CORRESPONDING #( create-inspectionplan MAPPING FROM ENTITY ). "Insert record into DB INSERT zblg_t_insp_tsp FROM TABLE lt_inspectionplan. ENDIF. IF update IS NOT INITIAL. "Populate the values lt_inspectionplan_upd = CORRESPONDING #( update-inspectionplan MAPPING FROM ENTITY ). lt_inspectionplan_controlflag = CORRESPONDING #( update-inspectionplan MAPPING FROM ENTITY USING CONTROL ). "Fill range value for all the updated Inspections lr_inspectionplan_uuid = VALUE #( FOR ls_inspectionplan_u IN update-inspectionplan ( sign = 'I' option = 'EQ' low = ls_inspectionplan_u-inspectionplanuuid ) ). IF lr_inspectionplan_uuid IS NOT INITIAL. "Get Old DB Values SELECT * FROM zblg_t_insp_tsp INTO TABLE @DATA(lt_inspectionplan_old) WHERE uuid IN @lr_inspectionplan_uuid. ENDIF. "Prepare DB Table to update lt_inspectionplan = VALUE #( FOR x = 1 WHILE x <= lines( lt_inspectionplan_upd ) LET ls_controlflag = VALUE #( lt_inspectionplan_controlflag[ x ] OPTIONAL ) ls_inspectionplan_upd = VALUE #( lt_inspectionplan_upd[ x ] OPTIONAL ) ls_inspectionplan_old = VALUE #( lt_inspectionplan_old[ uuid = ls_inspectionplan_upd-uuid ] OPTIONAL ) IN ( "Do not update cannonical, semantical keys, administrative fields uuid = ls_inspectionplan_old-uuid id = ls_inspectionplan_old-id createdbyuser = ls_inspectionplan_old-createdbyuser creationdatetime = ls_inspectionplan_old-creationdatetime "Update other columns, if found controlflag as X - else pass DB values name = COND #( WHEN ls_controlflag-name IS NOT INITIAL THEN ls_inspectionplan_upd-name ELSE ls_inspectionplan_old-name ) startdate = COND #( WHEN ls_controlflag-startdate IS NOT INITIAL THEN ls_inspectionplan_upd-startdate ELSE ls_inspectionplan_old-startdate ) enddate = COND #( WHEN ls_controlflag-enddate IS NOT INITIAL THEN ls_inspectionplan_upd-enddate ELSE ls_inspectionplan_old-enddate ) text = COND #( WHEN ls_controlflag-text IS NOT INITIAL THEN ls_inspectionplan_upd-text ELSE ls_inspectionplan_old-text ) materialgroup = COND #( WHEN ls_controlflag-materialgroup IS NOT INITIAL THEN ls_inspectionplan_upd-materialgroup ELSE ls_inspectionplan_old-materialgroup ) duedate = COND #( WHEN ls_controlflag-duedate IS NOT INITIAL THEN ls_inspectionplan_upd-duedate ELSE ls_inspectionplan_old-duedate ) lastchangedatetime = COND #( WHEN ls_controlflag-lastchangedatetime IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedatetime ELSE ls_inspectionplan_old-lastchangedatetime ) lastchangedbyuser = COND #( WHEN ls_controlflag-lastchangedbyuser IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedbyuser ELSE ls_inspectionplan_old-lastchangedbyuser ) lastchangedat = COND #( WHEN ls_controlflag-lastchangedat IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedat ELSE ls_inspectionplan_old-lastchangedat ) ) ). "Update DB table MODIFY zblg_t_insp_tsp FROM TABLE lt_inspectionplan. ENDIF. IF delete IS NOT INITIAL. "Fill range value for all the deleted Inspections lr_inspectionplan_uuid = VALUE #( FOR ls_inspectionplan_d IN delete-inspectionplan ( sign = 'I' option = 'EQ' low = ls_inspectionplan_d-inspectionplanuuid ) ). IF lr_inspectionplan_uuid IS NOT INITIAL. "Delete the record from DB DELETE FROM zblg_t_insp_tsp WHERE uuid IN lr_inspectionplan_uuid. ENDIF. ENDIF. ENDMETHOD. ENDCLASS. CLASS lhc_InspectionPlan DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION IMPORTING keys REQUEST requested_authorizations FOR InspectionPlan RESULT result. METHODS setinspectionplanid FOR DETERMINE ON SAVE IMPORTING keys FOR inspectionplan~setinspectionplanid. ENDCLASS. CLASS lhc_InspectionPlan IMPLEMENTATION. METHOD get_instance_authorizations. ENDMETHOD. METHOD setInspectionPlanID. " DATA : lv_inspectionplanid TYPE zblg_t_insp_tsp-id. "For external Scenario READ ENTITIES OF ZI_BLG_InspectionPlanTP IN LOCAL MODE ENTITY InspectionPlan FIELDS ( InspectionPlanId ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_inspectionplan) FAILED DATA(lt_failed). "Do not generate Id, if already provided IF lt_inspectionplan[ 1 ]-InspectionPlanId IS NOT INITIAL. RETURN. ENDIF. "Get new number from number range TRY. cl_numberrange_runtime=>number_get( EXPORTING nr_range_nr = '01' object = 'ZBLG_INSP' quantity = '1' IMPORTING number = DATA(lv_nr_number) ). CATCH cx_number_ranges INTO DATA(lx_number_ranges). " TODO: variable is assigned but never used (AbapCleaner) " exit ENDTRY. UNPACK lv_nr_number TO lv_inspectionplanid. LOOP AT lt_inspectionplan INTO DATA(ls_inspectionplan). MODIFY ENTITIES OF ZI_BLG_InspectionPlanTP IN LOCAL MODE ENTITY InspectionPlan UPDATE FIELDS ( InspectionPlanId ) WITH VALUE #( ( %tky = ls_inspectionplan-%tky InspectionPlanId = lv_inspectionplanid ) ) REPORTED DATA(lt_update_reported). ENDLOOP. ENDMETHOD. ENDCLASS.

Step 3 – (Optional/Good to have) As a best practice, always try to implement mapping structure with control parameter at individual Node level. This will be very useful while we are trying to read the data from the save_modified method input parameters and populating the internal table for the purpose of updating the DB table.

You can use following keyword in order to refer entity level mapping at ABAP Layer –

Notes –

  1. Never use any Database Commit or rollback keywords inside save_modified method.
  2. Also ensure that the execution of save_modified is not failing, as this method will get executed towards end of save_and_execute phase.
  3. Determinations (if used) gets executed before execution of save_modified method and all the fields will be determined before the control reaches this method. Hence, there is no need to write any such logic twice, e.g., Semantic Key generation, etc.
  4. All the Semantic annotation will continue to behave the way they behave in case of Managed Business Object Node. e.g., CreationLog ( @Semantics.user.createdBy: true / @Semantics.systemDateTime.createdAt: true ), ChangeLog fields ( @Semantics.user.lastChangedBy: true / @Semantics.systemDateTime.lastChangedAt: true ). etc.

Output

During User Input –

After Save –

Summary

In the current blog post, integration of Unmanaged Save functionality as part of Managed Business Object (ABAP RESTful application programming model) is covered.

For more understanding, please use the following references which have helped me in gaining knowledge on this feature and get motivated to write this blog post:

I would encourage you to read through other blog posts on such topics at: https://blogs.sap.com/tags/7e44126e-7b27-471d-a379-df205a12b1ff/

You can post and answer questions on related topics at: https://answers.sap.com/tags/7e44126e-7b27-471d-a379-df205a12b1ff

Please provide your feedback and ask questions in the comments section.

Regards,

Shyam Vasani