Hello Guys,
In this blog we will learn new programming model RAP in detail with an example, I would recommend to go through the theory as well before you start coding to understand RAP basics.I would like to thank my colleagues in SAP specially Kailash Satapathy and Gaurav Kumar for helping me in writing this blog.
Prerequisite:
Concepts of Core Data Services(CDS) and annotations.
Lets Start
🙂
RESTful ABAP Programming Model (RAP): The term
RESTful ABAP Programming Model (
RAP) is chosen to reflect its orientation towards a “stateless” REST architecture.
We will start with ABAP evolution over the years: -
Evolution of the ABAP Programming Model
We kick started our ABAP WORLD with classical ABAP programming using reports and module pool programs, then we moved to BSP/Webdynpro with FPM (for UI harmonization), then slowly shifted from ERP style developments to S/4 HANA model developments where our major programming consists of artifacts like CDS, BOPF, GW and Fiori.
ABAP Programming model for SAP Fiori (Current best practice) : -
This development model usually consists of CDS view, BOPF and GW where CDS is tightly coupled with BOPF and GW (using RDS Mapping or using @OData.Publish annotation).
Note: Issue with this model is flow is not well defined
Future Direction - New Programming Model (ABAP Restful Programming Model)
RESTful ABAP Programming model in detail: -
The ABAP RESTful programming model defines the architecture for efficient end-to-end development of intrinsically SAP HANA-optimized OData services (such as Fiori apps) in
SAP Cloud Platform
ABAP Environment. It supports the development of all types of Fiori applications as well as A2X services. It is based on technologies and frameworks such as Core Data Services (CDS) for defining semantically rich data models and a service model infrastructure for creating OData services with bindings to an OData protocol and ABAP-based application services for custom logic and SAPUI5-based user interfaces – as shown in the figure below
Understanding Concepts
- Business Object
A
business object (BO) is a common term to represent a real-world artifact in enterprise application development such as the Product, the Travel, or the SalesOrder. In general, a business object contains several nodes such as Items and and common transactional operations such as for creating, updating and deleting data and additional application-specific operations, such as
Approve in a
SalesOrder business object.
From a formal point of view, a business object is characterized by
- a structure,
- a behavior and
- the corresponding runtime implementation.
See the figure for more details.
1.1 Structure of a Business Object
From structural aspect, a business object consists of a tree of nodes (
SalesOrder,
Items,
ScheduleLines) where the nodes are linked by means of a special kind of associations, the compositions. A composition is specialized association that defines a whole-part relationship. A composite part only exists together with its parent entity (whole).
Each node of this composition tree is an element that is modelled with a
CDS entity. The
root entity is of particular importance: This is considered in the source code of the CDS data definition with the keyword
ROOT. The root entity serves as a representation of the business object and defines the top node in a business object's structure.
1.2 Behavior of a Business Object
Each entity of the business object can offer the standard CUD operations create(), update() and delete(). In addition, each entity can also offer specific operations with a dedicated input and output structure which are called actions. The offered CUD operations, actions as well some behavior-relevant properties, such as lock dependencies between the parent and child entities are defined in the behavior definition artifact.
A behavior definition always refers to a
CDS data model. As shown in the figure below, a behavior definition relies directly on the
CDS root entity. One behavior definition refers exactly to one root entity and one CDS root entity has at most one behavior definition (a 0..1 relationship), which also handles all included child entities.
1.3 Business Object’s Runtime Implementation
The business object runtime mainly consists of two parts: The first part is the
interaction phase where a consumer calls the business object operations to change data and read instances with or without the transactional changes. The business object runtime keeps the changes in its internal
transactional buffer which represents the state of the instance data. This transactional buffer is always required for a business object. After all changes were performed, the data can be persisted. This is realized with the
save sequence.
Business object runtime in detail
Interaction phase have different steps which we segregated as Modify, Read and Lock. Save sequence phase will have steps like Finalise, check_before_save, adjust_numbers and save.
Implementations can be classified as
Unmanaged, Managed and Managed with save.
Currently supported implementation is Unmanaged. Managed and Managed with save implementations will come in future releases.
- Business Service
The ABAP development platform can act in the roles of
service provider and
service consumer, such as SAP Fiori UI client.
In the context of the ABAP RESTful programming model, a business service is RESTful service which can be called by a consumer. It is defined by exposing data models and behavior models. It consists of a
Service definition and a
Service binding which are illustrated in the figure below: -
The
Service Definition is a projection of the data model and the related behavior to be exposed, whereas the
Service Binding implements a specific protocol and the kind of service to be offered for a consumer. This separation allows the data models and service definitions to be integrated into various protocols without any re-implementation.
2.1 Service Definition
A service definition represents the service model that is generically derived from the underlying CDS-based data model.
Example :-
DEFINE SERVICE service_definition_name
{
EXPOSE cds_entity_1 [AS alias_1];
EXPOSE cds_entity_2 [AS alias_2];
EXPOSE ...
EXPOSE cds_entity_m [AS alias_m];
}
2.2 Service Binding
The business service binding (short form: service binding) is an ABAP Repository object used to bind a service definition to a client-server communication protocol such as OData.
Relationship between the Data Model, the Service Definition and Service Binding
Let's learn RAP with a CRUD Example.
Here we will go with the
Sales Order CRUD Example.
For testing purpose, I have created Sales Order Header table – ZSSK_VBAK and Sales Order Item table – ZSSK_VBAP
ZSSK_VBAK
ZSSK_VBAP
We have 3 steps to implement here.
- Provide the CDS Data Model with Business Object Structure
- Defining and Implementing Behavior of the Business Object.
- Defining Business Service for Fiori UI.
- Provide the CDS Data Model with Business Object Structure
Here we will create CDS view for Business object using Composition and associations to sub nodes.
Code snippet for Sales Order Header CDS view: -
@AbapCatalog.sqlViewName: 'ZSSKSOHDR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Header'
@UI: {
headerInfo: {
typeName: 'Sales Order',
typeNamePlural: 'Sales Orders',
title: { type: #STANDARD, value: 'vbeln' }
}
}
@ObjectModel.semanticKey: ['vbeln']
@ObjectModel.representativeKey: 'vbeln'
define root view Zssk_SoHeader
as select from zssk_vbak
as SOHeader
composition
[0
..*] of Zssk_SoItem
as _SOItem
{
@UI.facet: [
{
id: 'GeneralData',
type: #COLLECTION,
position: 10,
label: 'Sales Order Header'
},
{
type: #FIELDGROUP_REFERENCE,
position: 10,
targetQualifier: 'GeneralData1',
parentId: 'GeneralData',
isSummary: true,
isPartOfPreview: true
},
{
type: #FIELDGROUP_REFERENCE,
position: 20,
targetQualifier: 'GeneralData2',
parentId: 'GeneralData',
isSummary: true,
isPartOfPreview: true
},
{
id: 'SOItem',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Sales Order Item',
position: 10,
targetElement: '_SOItem'
}
]
@UI: {
lineItem: [ { position: 10, importance: #HIGH } ],
selectionField: [{position: 10 }],
fieldGroup: [{qualifier: 'GeneralData1',position: 10,importance: #HIGH }]
}
key SOHeader
.vbeln
,
@UI: {
lineItem: [ { position: 20, importance: #HIGH } ],
selectionField: [{position: 20 }],
fieldGroup: [{qualifier: 'GeneralData2',position: 10,importance: #HIGH }]
}
SOHeader
.erdat
,
@UI: {
lineItem: [ { position: 30, importance: #HIGH } ],
fieldGroup: [{qualifier: 'GeneralData2',position: 20,importance: #HIGH }]
}
SOHeader
.ernam
,
@UI: {
lineItem: [ { position: 40, importance: #HIGH } ],
fieldGroup: [{qualifier: 'GeneralData1',position: 20,importance: #HIGH }]
}
SOHeader
.vkorg
,
@UI: {
lineItem: [ { position: 50, importance: #HIGH } ],
fieldGroup: [{qualifier: 'GeneralData1',position: 30,importance: #HIGH }]
}
SOHeader
.vtweg
,
@UI: {
lineItem: [ { position: 60, importance: #HIGH } ],
fieldGroup: [{qualifier: 'GeneralData1',position: 40,importance: #HIGH }]
}
SOHeader
.spart
,
//For action Set Favourite in UI
@UI.lineItem: [{ type: #FOR_ACTION,dataAction: 'set_favourite',label: 'Set Favourite' },
{position: 15, importance: #HIGH}]
@EndUserText.label: 'Favourite'
SOHeader
.favourite
,
SOHeader
.lastchangedby
,
SOHeader
.lastchangedat
,
/*Association*/
_SOItem
}
Code Snipper for Sales Order Item CDS view :-
@AbapCatalog.sqlViewName: 'ZSSKSOITM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Item'
@ObjectModel.representativeKey: ['vbeln','posnr']
define view Zssk_SoItem
as select from zssk_vbap
as SOItem
association to parent Zssk_SoHeader
as _SOHeader
on(
$projection.vbeln
= _SOHeader
.vbeln
)
{
@UI.facet: [
{type: #COLLECTION, position: 10, id: 'SoItem', label: 'Sales Order Item'},
{type: #FIELDGROUP_REFERENCE, position: 10, targetQualifier: 'SoItem1',parentId: 'SoItem', isSummary: true, isPartOfPreview: true},
{type: #FIELDGROUP_REFERENCE, position: 20, targetQualifier: 'SoItem2',parentId: 'SoItem', isSummary: true, isPartOfPreview: true}
]
@UI: {
lineItem: [ { position: 10, importance: #HIGH } ],
fieldGroup: [{qualifier: 'SoItem1',position: 10,importance: #HIGH }]
}
key SOItem
.vbeln
,
@UI: {
lineItem: [ { position: 20, importance: #HIGH } ],
fieldGroup: [{qualifier: 'SoItem1',position: 20,importance: #HIGH }]
}
key SOItem
.posnr
,
@UI: {
lineItem: [ { position: 30, importance: #HIGH } ],
fieldGroup: [{qualifier: 'SoItem2',position: 10,importance: #HIGH }]
}
SOItem
.matnr
,
@UI: {
lineItem: [ { position: 40, importance: #HIGH } ],
fieldGroup: [{qualifier: 'SoItem2',position: 20,importance: #HIGH }]
}
SOItem
.zmeng
,
SOItem
.meins
,
/*Association*/
_SOHeader
}
- Defining and Implementing Behavior of the Business Object
Here we will create Behavior definition and implementation for BO.
Right click on CDS view Zssk_SoHeader and create New Behavior definition
Code Snippet for Behavior Definition: -
implementation unmanaged;
define behavior for Zssk_SoHeader alias SOHeader
{
create;
update;
delete;
action set_favourite result [1] $self;
association _SOItem { create; }
}
define behavior for Zssk_SoItem alias SOItem
{
create;
update;
delete;
}
We can implement e-tag and locking also in behavior definition: -
After this step, we will create Behavior Implementation, right click Behavior definition and opt New Behavior Implementation.
Note: The moment you create a new Behavior implementation, local classes will get generated with Behavior class definition for
Interaction Phase and
Save Sequence Phase (Please refer topic
Business object runtime in detail)
We will write our logic in respective class, to make it simple I am putting code only in Modify Method in lcl_handler class.
Note : Ideally scenario will be like entries from the UI will be putting into the transaction buffer by
Interaction phase( modify(), read() and lock() methods) with a proper locking mechanism and
Save Sequence phase( finalize(),check_before_save(),adjust_numbers() and save() methods) will take care of database commit and rollback part with proper validation.
Code Snippet for Modify method to update Sales Order Header and Item tables.
Here I am using INSERT,UPDATE and DELETE statements for CRUD operations to make it simple, but in ideal scenarios we will be using BAPI/APIs for CRUD operations.
CLASS lcl_handler DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
DATA : lt_soheader TYPE TABLE OF Zssk_vbak,
lt_soitem TYPE TABLE OF Zssk_vbap,
ls_vbap TYPE Zssk_vbap,
lt_fields TYPE TABLE OF dfies.
METHODS modify FOR BEHAVIOR IMPORTING
"
For Header table
it_set_fav FOR ACTION soheader~set_favourite RESULT et_soheader
it_soheader_create FOR CREATE soheader
it_soheader_update FOR UPDATE soheader
it_soheader_delete FOR DELETE soheader
" For Item table
it_soheader_soitem_create FOR CREATE soheader\_soitem
it_soitem_create FOR CREATE soitem
it_soitem_update FOR UPDATE soitem
it_soitem_delete FOR DELETE soitem.
ENDCLASS.
CLASS lcl_handler IMPLEMENTATION.
METHOD modify.
/**************************
Action Set Favourite************************/
IF it_set_fav IS NOT INITIAL.
DATA(ls_fav_so) = it_set_fav[ 1 ].
UPDATE zssk_vbak SET favourite = 'X' WHERE vbeln = ls_fav_so-vbeln.
IF sy-subrc EQ 0.
SELECT * FROM zssk_vbak INTO CORRESPONDING FIELDS OF TABLE et_soheader WHERE vbeln = ls_fav_so-vbeln.
ENDIF.
ENDIF.
/***************************************************************************/
/* CRUD Operations for Header table */
/***************************
Create****************************************/
IF it_soheader_create IS NOT INITIAL.
MOVE-CORRESPONDING it_soheader_create TO lt_soheader.
INSERT zssk_vbak FROM TABLE lt_soheader.
ENDIF.
/*************************************************************************/
/***************************
Update*************************************/
IF it_soheader_update IS NOT INITIAL.
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname = 'ZSSK_VBAK'
TABLES
dfies_tab = lt_fields
EXCEPTIONS
not_found = 1
internal_error = 2
OTHERS = 3.
IF sy-subrc <> 0.
ENDIF.
DATA(ls_hdr) = it_soheader_update[ 1 ].
SELECT SINGLE *
FROM zssk_vbak
INTO
@DATA(ls_vbak)
WHERE vbeln =
@LieneS_hdr-vbeln.
LOOP AT lt_fields ASSIGNING FIELD-SYMBOL(<fs_field>).
ASSIGN COMPONENT <fs_field>-fieldname OF STRUCTURE ls_hdr TO FIELD-SYMBOL(<fs_target>).
IF <fs_target> IS ASSIGNED AND <fs_target> IS NOT INITIAL.
ASSIGN COMPONENT <fs_field>-fieldname OF STRUCTURE ls_vbak TO FIELD-SYMBOL(<fs_source>).
<fs_source> = <fs_target>.
ENDIF.
ENDLOOP.
APPEND ls_vbak TO lt_soheader.
UPDATE zssk_vbak FROM TABLE lt_soheader.
ENDIF.
/*************************************************************************/
/****************************
Delete*************************************/
IF it_soheader_delete IS NOT INITIAL.
MOVE-CORRESPONDING it_soheader_delete TO lt_soheader.
DELETE zssk_vbak FROM TABLE lt_soheader.
IF sy-subrc EQ 0.
DATA(lv_vbeln) = lt_soheader[ 1 ]-vbeln.
IF lv_vbeln IS NOT INITIAL.
DELETE FROM zssk_vbap WHERE vbeln EQ lv_vbeln.
ENDIF.
ENDIF.
ENDIF.
/*************************************************************************/
/* CRUD Operations for Item table */
/***************************
Create****************************************/
IF it_soheader_soitem_create IS NOT INITIAL.
READ TABLE it_soheader_soitem_create INTO DATA(ls_item) INDEX 1.
IF sy-subrc EQ 0.
MOVE-CORRESPONDING ls_item-%target TO lt_soitem.
ENDIF.
IF lt_soitem IS NOT INITIAL.
INSERT zssk_vbap FROM TABLE lt_soitem.
ENDIF.
ENDIF.
IF it_soitem_create IS NOT INITIAL.
MOVE-CORRESPONDING it_soitem_create TO lt_soitem.
INSERT zssk_vbap FROM TABLE lt_soitem.
ENDIF.
/*************************************************************************/
/***************************
Update*************************************/
IF it_soitem_update IS NOT INITIAL.
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname = 'ZSSK_VBAP'
TABLES
dfies_tab = lt_fields
EXCEPTIONS
not_found = 1
internal_error = 2
OTHERS = 3.
IF sy-subrc <> 0.
ENDIF.
DATA(ls_itm) = it_soitem_update[ 1 ].
SELECT *
FROM zssk_vbap
INTO TABLE
@DATA(lt_vbap)
WHERE vbeln =
@LieneS_itm-vbeln.
CLEAR ls_itm.
LOOP AT it_soitem_update INTO ls_itm.
READ TABLE lt_vbap INTO DATA(ls_vbap) WITH KEY vbeln = ls_itm-vbeln posnr = ls_itm-posnr.
IF sy-subrc EQ 0.
LOOP AT lt_fields ASSIGNING FIELD-SYMBOL(<fs_itm_field>).
ASSIGN COMPONENT <fs_itm_field>-fieldname OF STRUCTURE ls_itm TO FIELD-SYMBOL(<fs_itm_target>).
IF <fs_itm_target> IS ASSIGNED AND <fs_itm_target> IS NOT INITIAL.
ASSIGN COMPONENT <fs_itm_field>-fieldname OF STRUCTURE ls_vbap TO FIELD-SYMBOL(<fs_itm_source>).
<fs_itm_source> = <fs_itm_target>.
ENDIF.
ENDLOOP.
ENDIF.
APPEND ls_vbap TO lt_soitem.
CLEAR: ls_vbap,
ls_itm.
ENDLOOP.
UPDATE zssk_vbap FROM TABLE lt_soitem.
ENDIF.
/*************************************************************************/
/****************************
Delete*************************************/
IF it_soitem_delete IS NOT INITIAL.
MOVE-CORRESPONDING it_soitem_delete TO lt_soitem.
DELETE zssk_vbap FROM TABLE lt_soitem.
ENDIF.
/*************************************************************************/
ENDMETHOD.
ENDCLASS.
CLASS lcl_saver DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS finalize REDEFINITION.
METHODS check_before_save REDEFINITION.
METHODS save REDEFINITION.
ENDCLASS.
CLASS lcl_saver IMPLEMENTATION.
METHOD finalize.
ENDMETHOD.
METHOD check_before_save.
ENDMETHOD.
METHOD save.
ENDMETHOD.
ENDCLASS.
- Defining Business Service for Fiori UI
Here we will create Service definition and Service Binding.
Service Definition.
The service definition is a projection of the data model and the related behavior to be exposed.
Right click your Package->New->other ABAP Repository Object->Business Service->Service Definition
Code Snippet for Service Definition: -
Service Binding
The service binding implements a specific protocol and the kind of service to be offered for a consumer.
Right click your Package->New->other ABAP Repository Object->Business Service->Service Binding.
Give name of Service Binding and service definition name for which binding needs to be created and click on Finish
Click on the button Publish as shown below to publish Odata service locally which makes service ready for consumption
Once we published, we can see published Information on right side.
Click on Service Url to see the Metadata
Right click on SOHeader/SoItem to see the UI Preview
APP Preview
This is just a preview and it will be useful to check how the UI looks with annotations implemented in CDS.
Note: - We can create proper UI project from Web IDE and map our Service Binding to test this application. Launch Web IDE -> Create Project from template->List Report->Map Service Binding
Our App will look like this :- It will show a list of sales orders.
Once we select the line item, Action
'Set Favourite' and
CUD operations will be enabled:-
Once we click on the line item, It will go to the respective Sales order item page.
The above steps will guide you on how to create a RAP CRUD application.
Overview: ABAP Restful Programming Model: Development Flow
Steps to remember:-
- Model CDS view with proper annotations.( For simplicity I have created only one CDS view for root and then one for Item, but ideally, we will follow like we will create basic views for SO header and Item, then transactional( BO views ) for SO header and Item and then Projection views for SO Header and Item ).
- Create a Behavior definition for CDS views. ( Behavior definition for root transactional view and Behavior projection for Projection view )
- Create Behavior implementation for Behavior definition.
- Create a Service definition.
- Create Service Binding and add Service definition name for which binding needs to be created and Publish.
- Create WebIde project and map Service binding to it.
Conclusion: This programming model will be the future of ABAP which will be protocol agnostic and the good news is SAP will continue to support the ABAP Programming Model for SAP Fiori beside the ABAP RESTful Programming Model in the future for upwards compatibility reasons.
Hope you find this blog helpful.