Technical prerequisites: S/4 Hana On premise or S/4 Hana private cloud edition environment.
In this blog, the aim is to create a Fiori application using the RAP framework, based on an existing BAPI, while complying with SAP guidelines to achieve the clean core objective.
Use case: For this presentation, we’ll be looking to see whether deliveries have existing HUs (Handling Units). If so, we need to be able to delete these HUs.
In legacy ABAP, we can use the HU_DELETE_FROM_DEL BAPI.
We’ll now look at how to integrate this into a RAP object, following the SAP recommendations for a clean core.
I’ll also come back to the use of the RAP framework with the use of ABAP Standard, without the objective of clean core or cloud compliance, to show you some of the differences.
Let’s take a very brief look at the ABAP RAP framework and its structure to provide the main foundations for this blog, all in the context of ABAP Cloud development.
However, I recommend that you read the SAPPRESS book “ABAP RESTful Application Programming Model – A comprehensive guide” (1), which will explain all the details of this framework perfectly.
Before delving into the structure of the framework, let’s return to the notion of LUW in the cloud, as this will have a major impact on our development.
As mentioned in detail in this SAP Blog (2), while it used to be recommended to use IN UPDATE TASK (which, among other things, avoided writing to databases when there were implicit commits and increased performance, but I invite you to read the article cited above) when it was desired to write to the SAP database using the ABAP standard. This is no longer the case with ABAP cloud, since the performance of the HANA database means that it can now be dispensed with. However, this implies that database writes only take place at the end of the SAP LUW (during the save sequence, see later in the explanation of the RAP framework), since in the event of an implicit commit, database modifications would be made and we would no longer respect ACID rules (again, see the article cited above).
This is where the structure of the RAP framework comes in:
The RAP framework can be broken down into 2 main phases:
– Interaction phase
– Save sequence
During the interaction phase, data is placed in a buffer, where it can be enriched and verified.
During the save sequence, the data in the buffer can no longer be modified and is saved in the database (this is the end of the SAP LUW).
These are the rules followed by the RAP framework in the case of ABAP cloud development:
The following violations will always lead to a runtime error in a RAP context (Even if ABAP CLOUD is used or STANDARD ABAP is used) :
– Explicit use of COMMIT WORK or ROLLBACK WORK statements, as the transaction handling and database commit is exclusively handled by RAP.
– Calling the update or background task (bgPF) in the interaction phase or early-save phase.
Some other operations only lead to runtime errors, if the RAP behavior definition is implemented in ABAP for Cloud Development and the strict mode is applied:
– Invoking authorization checks in the early-save or late-save phase.
Modifying database operations in the interaction phase or early-save phase.
– Raising business events in the interaction phase or early-save phase.
(Implicit or explicit) database commits for the primary database connection in the late-save phase.
NOTE FOR STANDARD ABAP USAGE : Looking at this rules, if you’re not in the optic of clean core, you can develop in STANDARD ABAP, and perform implicit commit during late phase for example, or use IN UPDATE TASK statement, but in my case I prefer following the more stricter rules of ABAP RAP FRAMEWORK even if I’m developing in STANDARD ABAP.
There are 2 types of scenarios for implementing the process: managed or unmanaged.
With the managed scenario, the SAP system automatically generates the methods and the business object. With the unmanaged scenario, everything must be developed by hand.
This is not the case here, as we want to store/modify our data in standard tables using legacy objects (a BAPI).
In this case, we’d have to develop everything from the phase interaction to the save sequence. We’re not interested in this implementation, as we don’t have an API to manage buffering for our needs, and would like to use the SAP standard for the interaction part.
Note : it exists also Managed scenario with additional save : Adding further update logic to the save sequence (e.g., in the application log) that doesn’t affect the actual business object. But we will not use it.
SAP provides the objects it wishes to expose for cloud development by Whitelisting them. If these objects are not exposed, SAP can also propose their successors (e.g. in the case of BAPIs, if not exposed, a RAP behavior can be proposed instead).
This can be verified either directly in ADT (3), or on GitHub (4).
In our case, the desired BAPI is neither exposed nor replaced.
Following SAP guidelines (5) (6), we will create a wrapper around this BAPI, and whitelist this wrapper. When SAP proposes a successor to this BAPI, we can remove the wrapper.
To create a wrapper around a BAPI, follow this tutorial (7).
Here, we’ve created the ZCL_WRAP_DEL_HU_FAC ( and ZCL_WRAP_DEL_HU using ZIF_WRAP_BAP_HU_DEL) class, which will be used to delete HUs.
INTERFACE zif_wrap_bapi_hu_del
PUBLIC .
"Delivery Number
TYPES de_number TYPE BAPIDELICIOUSDELIVERY-DELIVERY_NUMBER.
"HU
TYPES de_hu TYPE BAPIHUKEY-HU_EXID.
"Return table
TYPES de_returns TYPE bapirettab.
METHODS delete
IMPORTING iv_number type de_number
iv_hu type de_hu
RETURNING VALUE(rt_results) TYPE de_returns.
ENDINTERFACE.
CLASS zcl_wrap_del_hu DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zif_wrap_bapi_hu_del.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_wrap_del_hu IMPLEMENTATION.
METHOD zif_wrap_bapi_hu_del~delete.
CALL FUNCTION 'BAPI_HU_DELETE_FROM_DEL'
EXPORTING
delivery = iv_number
hukey = iv_hu
TABLES
return = rt_results.
.
ENDMETHOD.
ENDCLASS.
CLASS zcl_wrap_del_hu_fac DEFINITION
PUBLIC
FINAL
CREATE PRIVATE .
PUBLIC SECTION.
CLASS-METHODS create_instance
RETURNING VALUE(result) TYPE REF TO zif_wrap_bapi_hu_del.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS constructor.
ENDCLASS.
CLASS zcl_wrap_del_hu_fac IMPLEMENTATION.
METHOD create_instance.
result = NEW zcl_wrap_del_hu( ).
ENDMETHOD.
METHOD constructor.
ENDMETHOD.
ENDCLASS.
And I have released the wrapper and its interface as with listed APIs
So, it can be used now in ABAP for cloud development.
Now that we have our whitelisted object for use in our rap object, we’re going to start creating our business object.
I have created a package using ABAP for cloud development.
1) CDS creation
– We create a CDS Root view entity which will store the delivery number, and whether at least one HU is linked to this delivery. (I have create the private CDS view ZP_HU to find the first line of eah Handler Unit for a delivery and then I join this view in the CDS Root Entity to check if at list one HU exists).
Note here that I’m using the CDS released by sap, which replaces the standard vekp-type tables).
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'CDS internal use HU'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}
define view entity ZP_HU
as select from I_DeliveryDocument
left outer join I_HandlingUnitHeader on I_HandlingUnitHeader.HandlingUnitPackingObjectKey = I_DeliveryDocument.DeliveryDocument
{
key I_DeliveryDocument.DeliveryDocument as deliveryDocument,
min(I_HandlingUnitHeader.HandlingUnitInternalID) as hu,
min(I_HandlingUnitHeader.HandlingUnitPackingObjectKey) as objectkey
}
group by
I_DeliveryDocument.DeliveryDocument
having
count(*) = 1
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Handling Units for Delivery'
define root view entity ZI_DELIV_HU as select from I_DeliveryDocument
left outer join ZP_HU on ZP_HU.deliveryDocument = I_DeliveryDocument.DeliveryDocument
{
key I_DeliveryDocument.DeliveryDocument as DeliveryDocument,
case
when $projection.DeliveryDocument = ZP_HU.objectkey
then cast ( 'X' as boole_d )
else cast ( ' ' as boole_d )
end as HUAssigned
}
– We create the associated projection view, which will be used to create the web service. The projection view reduces the business object to the simple use it will be put to in the Fiori application, whereas the ZI_DELIV_HU cds could be reused by other applications.
(Here there are no differences between the 2 cds, but there could have been).
@EndUserText.label: 'Projection view ZI_DELIV_HU'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZC_DELIV_HU as projection on ZI_DELIV_HU
{
key DeliveryDocument,
HUAssigned
}
Note: we could have created cds for additional value helps
2) We create the behavior definition
– We specify first the strict mode for all the necessary syntax checks, and the authorization method used (authorization check).
– Then we specify the operations that can be used: in this case, we only wish to update our business object to remove the HU assignment flag, so we specify “update”. (we’ll come back to internal).
– Next, we enrich the automatically-generated standard by adding a validation in which we use the BAPI in test mode to check that everything’s running smoothly.
– We also enrich the standard with an “unassign” action that will remove the HUs flag assigned to the CDS. This action will use the UPDATE operation internally to modify the entity, and user should not be able to modify by himself the flag : He has to use the unassign button (here the explanation why internal is specified in the definition : UPDATE could only be used internally, in the RAP business object implementation).
– finally, we specify Unmanaged save to use the BAPI in the save sequence and save the data in the database.
managed with unmanaged save implementation in class zbp_i_deliv_hu unique;
strict ( 2 );
define behavior for ZI_DELIV_HU //alias <alias_name>
//persistent table <???>
lock master
authorization master ( instance )
//etag master <field_name>
{
// create;
internal update;
// delete;
action unassign;
validation validateDelivery on save { create; update; }
}
3) Create the implementation
– Creating the action
EML is used to update the object by deleting the flag. If the user attempts to remove the flag from an object already without a flag (without HUs), an error message is displayed.
– Validation creation (at save time)
Execute the BAPI in test mode to check that everything’s ok. If not, an error message is displayed.
– Create save sequence :
The BAPI is called in the last phase, at the end of the SAP LUW. Once the save sequence is complete, the RAP framework takes care of committing it.
CLASS lhc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR zi_deliv_hu RESULT result.
METHODS unassign FOR MODIFY
IMPORTING keys FOR ACTION zi_deliv_hu~unassign.
METHODS validateDelivery FOR VALIDATE ON SAVE
IMPORTING keys FOR zi_deliv_hu~validateDelivery.
ENDCLASS.
CLASS lhc_ZI_DELIV_HU IMPLEMENTATION.
METHOD get_instance_authorizations.
* Here you check authorizations
ENDMETHOD.
METHOD unassign.
* We read the current value of the key instances
READ ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_sh_for_unassign).
* We loop at the finding values to check if instances have HUs qssigned or no, If yes we report an error
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign_err>) WHERE huassigned = ' '.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %tky = <ls_sh_for_unassign_err>-%tky
%msg = me->new_message( severity = if_abap_behv_message=>severity-error
id = 'ZHU_MSG'
number = '002'
v2 = |{ <ls_sh_for_unassign_err>-deliverydocument ALPHA = OUT }| ) ) ).
ENDLOOP.
DELETE lt_sh_for_unassign WHERE huassigned = ' '.
* We modify entities to delete the flag and un-assign the HUs linked to the delivery
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign>).
MODIFY ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
UPDATE FROM
VALUE #( FOR sh IN lt_sh_for_unassign (
%key = sh-%key
deliverydocument = sh-deliverydocument
huassigned = ' '
%control-huassigned = if_abap_behv=>mk-on ) ).
ENDLOOP.
ENDMETHOD.
METHOD validateDelivery.
* Validate here the data
* For example, if the BAPI have a test mode it is be possible to execute it in test mode and in case of error report errors
* However here it's not the case for BAPI_HU_DELETE_FROM_DEL
* It's still possible to add our own validation
ENDMETHOD.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS save_modified REDEFINITION.
METHODS cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU IMPLEMENTATION.
METHOD save_modified.
* We loop at the updated instances with deleted flag
LOOP AT update-zi_deliv_hu INTO DATA(ls_update).
* In this loop we find all the HU linked to the delivery
SELECT HandlingUnitExternalID
FROM I_HandlingUnitHeader
WHERE HandlingUnitPackingObjectKey = @ls_update-DeliveryDocument
INTO TABLE @DATA(lt_hu_to_delete).
* Loop to all HUs assigned to delivery
LOOP AT lt_hu_to_delete INTO DATA(ls_hu_to_delete).
* Call the bapi to delete the HU using the wrapper for the cloud
DATA(lt_hu_delete) = zcl_wrap_del_hu_fac=>create_instance( )->delete(
EXPORTING
iv_number = ls_update-DeliveryDocument
iv_hu = ls_hu_to_delete-HandlingUnitExternalID ).
* At this stage we can't report errors, it should has been verify that everything is fine before the save sequence
* But in case of error we still give an information message
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
FOR r IN lt_hu_delete WHERE ( type = 'E' )
( deliverydocument = ls_update-deliverydocument
%msg = me->new_message(
id = r-id
number = r-number
severity = if_abap_behv_message=>severity-information
v1 = r-message_v1
v2 = r-message_v2
v3 = r-message_v3
v4 = r-message_v4 ) ) ).
IF reported IS INITIAL.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %key = ls_update-%key
%msg = me->new_message( severity = if_abap_behv_message=>severity-success
id = 'ZHU_MSG'
number = '002'
v1 = |{ ls_update-deliverydocument ALPHA = OUT }| ) ) ).
ENDIF.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
METHOD cleanup_finalize.
* Clean after save
ENDMETHOD.
ENDCLASS.
CLASS lhc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR zi_deliv_hu RESULT result.
METHODS unassign FOR MODIFY
IMPORTING keys FOR ACTION zi_deliv_hu~unassign.
METHODS validateDelivery FOR VALIDATE ON SAVE
IMPORTING keys FOR zi_deliv_hu~validateDelivery.
ENDCLASS.
CLASS lhc_ZI_DELIV_HU IMPLEMENTATION.
METHOD get_instance_authorizations.
* Here you check authorizations
ENDMETHOD.
METHOD unassign.
* We read the current value of the key instances
READ ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_sh_for_unassign).
* We loop at the finding values to check if instances have HUs qssigned or no, If yes we report an error
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign_err>) WHERE huassigned = ' '.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %tky = <ls_sh_for_unassign_err>-%tky
%msg = me->new_message( severity = if_abap_behv_message=>severity-error
id = 'ZHU_MSG'
number = '002'
v2 = |{ <ls_sh_for_unassign_err>-deliverydocument ALPHA = OUT }| ) ) ).
ENDLOOP.
DELETE lt_sh_for_unassign WHERE huassigned = ' '.
* We modify entities to delete the flag and un-assign the HUs linked to the delivery
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign>).
MODIFY ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
UPDATE FROM
VALUE #( FOR sh IN lt_sh_for_unassign (
%key = sh-%key
deliverydocument = sh-deliverydocument
huassigned = ' '
%control-huassigned = if_abap_behv=>mk-on ) ).
ENDLOOP.
ENDMETHOD.
METHOD validateDelivery.
* Validate here the data
* For example, if the BAPI have a test mode it is be possible to execute it in test mode and in case of error report errors
* However here it's not the case for BAPI_HU_DELETE_FROM_DEL
* It's still possible to add our own validation
ENDMETHOD.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS save_modified REDEFINITION.
METHODS cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU IMPLEMENTATION.
METHOD save_modified.
* We loop at the updated instances with deleted flag
LOOP AT update-zi_deliv_hu INTO DATA(ls_update).
* In this loop we find all the HU linked to the delivery
SELECT HandlingUnitExternalID
FROM I_HandlingUnitHeader
WHERE HandlingUnitPackingObjectKey = @ls_update-DeliveryDocument
INTO TABLE @DATA(lt_hu_to_delete).
* Loop to all HUs assigned to delivery
LOOP AT lt_hu_to_delete INTO DATA(ls_hu_to_delete).
* Call the bapi to delete the HU using the wrapper for the cloud
DATA(lt_hu_delete) = zcl_wrap_del_hu_fac=>create_instance( )->delete(
EXPORTING
iv_number = ls_update-DeliveryDocument
iv_hu = ls_hu_to_delete-HandlingUnitExternalID ).
* At this stage we can't report errors, it should has been verify that everything is fine before the save sequence
* But in case of error we still give an information message
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
FOR r IN lt_hu_delete WHERE ( type = 'E' )
( deliverydocument = ls_update-deliverydocument
%msg = me->new_message(
id = r-id
number = r-number
severity = if_abap_behv_message=>severity-information
v1 = r-message_v1
v2 = r-message_v2
v3 = r-message_v3
v4 = r-message_v4 ) ) ).
IF reported IS INITIAL.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %key = ls_update-%key
%msg = me->new_message( severity = if_abap_behv_message=>severity-success
id = 'ZHU_MSG'
number = '002'
v1 = |{ ls_update-deliverydocument ALPHA = OUT }| ) ) ).
ENDIF.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
METHOD cleanup_finalize.
* Clean after save
ENDMETHOD.
ENDCLASS.
4) We create a behavior consumption definition to reduce CRUD operations to the application if necessary (here I create the consumption but it is identical to ZI_DELIV_HU )
projection;
strict ( 2 );
define behavior for ZC_DELIV_HU //alias <alias_name>
{
use action unassign;
}
5) We create the metadatas, adding the extra button for Unassigned HUs.
@Metadata.layer: #CORE
annotate entity ZC_DELIV_HU
with
{
@UI: { lineItem: [{ position: 10, importance: #HIGH },
{ type: #FOR_ACTION, dataAction: 'unassign', label: 'Unassign' }],
selectionField: [{ position: 10 }] }
DeliveryDocument;
@UI.lineItem: [{ position: 20 },
{ label : 'HU(s) assigned ?' }]
@UI.selectionField: [{ position: 20 }]
@UI.identification:[ { label: 'HU(s) assigned ?' } ]
@EndUserText.label: 'HU(s) assigned ?'
HUAssigned;
}
6) Create the definition service
@EndUserText.label: 'Service def deliv hu'
define service ZUI_DELIV_HU {
expose ZC_DELIV_HU;
}
7) create the binding service
8) All that’s left to do is create the Fiori application with VS Code or SAP BAS (8) using SAP Fiori elements
This is how I create Fiori applications using the Rap framework, taking into account the SAP guidelines for the cloud.
For more similar content, follow the ABAP Development environment Topic page (https://community.sap.com/topics/abap), post and answer questions (https://answers.sap.com/tags/833755570260738661924709785639136), and read other posts on the topic (https://blogs.sap.com/tags/833755570260738661924709785639136/)
You can also follow my profile for similar content.
(1) Learn the ABAP RESTful Application Programming Model | – by SAP PRESS (sap-press.com)
(2) https://blogs.sap.com/2022/12/05/the-sap-luw-in-abap-cloud/
(4) https://raw.githubusercontent.com/SAP/abap-atc-cr-cv-s4hc/main/src/objectReleaseInfo_PCE2022.json
(5) https://www.sap.com/documents/2023/05/b0bd8ae6-747e-0010-bca6-c68f7e60039b.html
(7) https://developers.sap.com/tutorials/abap-s4hanacloud-purchasereq-create-wrapper.html
(8) https://blogs.sap.com/2022/10/31/fiori-development-with-vscode-and-nodejs/