ABAP RESTful Application Programming Model
Salvation after 5+ years and variety of technological marriages, ABAP RAP is here finally. Fiori and WebAPI development can take a deep breath. This post is about common and uncommon snippets for ABAP RAP. This post is updated on regular manner including some facts about RAP as well.
CDS Data Modeling
Administrative Data
At the beginning, we had to manually update admin fields of changes within BOPF determinations manually. Alternatively we created reusable base determination classes combined with custom Z annotations and reuse it within CDS views and generated BOPF objects freely. We can relax a bit more, RAP supports this out of the box, you just need to annotate your timestamp and username fields, system does the rest.
"Database table fields created_by : syuname; created_at : timestampl; last_changed_by : syuname; last_changed_at : timestampl; local_last_changed_at : timestampl;
--CDS: Data Modelling @Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt, @Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy, @Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt, @Semantics.systemDateTime.localInstanceLastChangedAt: true local_last_changed_at as LocalLastChangedAt,
Admin fields populated automatically
Inheriting authorization from lower level CDS views
Let’s say You wrap a standard CDS view into a custom one (Z/Y). Doing this the CDS Access control is lost, due it is not inherited when wrapping a CDS Entity. You like to provide authorization check for READ access, which is actually the same as in the standard CDS View. Instead of redoing, You have the possibility to inherit the CDS Access control attached to the view from which Your custom view is selecting. This is possible until You include all those properties used in the Access Control in Your CDS entity as well. In our case the custom CDS Entity ZI_PurchaseOrder_Simple
select from standard CDS I_PurchaseOrderItemAPI01
, which has all the required access control in place by SAP already.
@EndUserText.label: 'Inheriting access control' @MappingRole: true define role ZI_RAP_PurchaseOrder_Simple { grant select on ZI_PurchaseOrder_Simple where inheriting conditions from entity I_PurchaseOrderItemAPI01; }
In case You wrap the ZI_PurchaseOrder_Simple
into another CDS Entity, You need to repeat this step, but in that case You continue the inheritance chain, inheriting conditions from
. ZI_PurchaseOrder_Simple
@EndUserText.label: 'Inheriting access control' @MappingRole: true define role ZC_RAP_PurchaseOrder_Simple { grant select on ZC_RAP_PurchaseOrder_Simple where inheriting conditions from entity ZI_RAP_PurchaseOrder_Simple; }
Behaviour Defintion
Mapping
Mapping in behavior definition is not only used for mapping fields of DB table fields for persistency.
Let’s say You have a managed scenario with unmanaged save, where the RAP framework is responsible for the interaction phase, and You are responsible for the peristency. An example could be to trigger an API class or function module. In this case the mapping serves not as matching the DB table fields, but to map the fields of the CDS Entity rather to the importing structure of the API.
An optional and really nice feature is that the X structure of a BAPI is also supported:
mapping for bapimepoheader control bapimepoheaderx corresponding { PurchaseOrder = po_number; PurchasingOrganization = purch_org; PurchasingGroup = pur_group; Supplier = vendor; }
So far so good, but what is the benefit ? The benefit is that You can do the mapping in data object constructor expressions within the behavior implementation. I find this really cool, we neither need to do mapping at multiple places nor to implement the mapping logic by ourselves.
DATA: ls_po_header TYPE bapimepoheader
, po_entity type ZI_PurchaseOrder_Simple.
ls_po_header = CORRESPONDING #( po_entity MAPPING FROM ENTITY ).
Behavior Implementation
Reporting Messages
Using the helper method NEW_MESSAGE
The easy way of reporting messages in validations is to use the generic helper method of the base class of Your behavior handler class ( CL_ABAP_BEHAVIOR_HANDLER / CL_ABAP_BEHV ). You can mark the field what caused the issue exactly providing together with the Message Class and ID.
IF material-active NE abap_true. reported-purchaseorder = VALUE #( BASE reported-purchaseorder ( %tky = po-%tky %element-material = if_abap_behv=>mk-on %msg = me->new_message( severity = if_abap_behv_message=>severity-error id = 'ZPO' number = '001' v1 = |{ po-Material ALPHA = OUT }| ) ) ). ENDIF.
Using custom message class
If You like to find examples for custom message classes, Open the ABAP Type Hierarchy for the interface IF_ABAP_BEHV_MESSAGE
.
APPEND VALUE #( %tky = po-%tky %state_area = 'VALIDATE_PO' %msg = NEW zcm_po_messages( po_id = po-DocumentNr textid = zcm_po_messages=>po_unkown severity = if_abap_behv_message=>severity-error ) %element-DocumentNr = if_abap_behv=>mk-on ) TO reported-purchaseorder.
Execution sequence of determinations
You are in charge of executing dependent operations in the right order. The order in Behavior Definition (BDEF) does not count!
This like with kernel driven BAdI implementations, RAP determination execution sequence is non-deterministic. History: BOPF is a pure ABAP solution w/o kernel, there You have the possibility to define a relative order (before/after aka. predecessor/successor).
What You can do, is that for an entity in the BO hierarchy You encapsulate dependent determinations into same handler method, where You are in control for the order of execution.
Point of no return
This is about save sequence and Your save handler class in the behavior pool inheriting from cl_abap_behavior_saver
, in case You have such.
When the method adjust_numbers
is called in the save sequence that is the point of no return, which means returning/reporting messages has no effect on the steps of the save execution, the RAP framework will not stop. In case any error occurs during the save phase, You have to raise a short dump, like:RAISE SHORTDUMP NEW zcx_po( textid = VALUE #( msgid = <msgid> msgno = <msgno> ) ).
That is why You need to cover as many case You can in the interaction phase with the validations, so that You do not reach that state with the dump.
Share this content: