Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
Showing results for 
Search instead for 
Did you mean: 

RestFul ABAP programming model for S4HANA

Since 1909 release SAP came up with new innovation in the programming model of SAP for Services.

That is RESTFul programming Model ( RAP),  this is huge improvement over current BOPF framework and has lots of advantages. There is some blogs explaining more about RAP some of them are listed below.

In above link the concept is very well explained. Please go through these blogs to get deeper knowledge about the this exciting concept

The purpose of this document is to show the capabilities of RAP with on Premise setup and see how exactly this is different than BOPF.

Basically there are 3 implementation scenarios possible for RAP

  • Managed ( Green field development )

    1. Managed Scenarios are more like BOPF where you define what tables will be updated at end of the process.

    2. We have options for implementing actions, determination and validations

    3. Only available on S4HANA cloud

  • Managed with save ( Self implemented )

    1. These are more sophisticated implementation types where data is not directly updated into tables

    2. BAPIs, update function modules can be called in the implementation to save the data

    3. As of now it is for SAP internal purpose and not delivered to Customers

  • Unmanaged

    1. In this scenario the implementation decides business logic

    2. In the implementation developer can process the input data and update the respective tables or also call BAPIs, functions etc.

    3. Available for S4HANA 1909 OnPremise customers as well

    4. We cannot use validation and determination on field levels.

As you can see that RAP is still in its evaluation state but its worth already start working with it and exploring it.

The major benefit of RAP is first it now follows global development paradigm for Web development i.e. REST services.  This makes it uniform with all other programming languages and also makes integration with system much easy.

Another benefit is, it does not create bunch of objects like BOPF. The implantation is very LEAN.

It does not create new classes with every implementation, does not create lots of structures etc. also SEWG project is not created for RAP.

It also cuts the annotation assignments to CDS views (Hence we have to remember less annotation keywords).

As we proceed with the example I will try to highlight the difference between current (BOPF) and RAP

In this document we will consider Unmanaged Scenario as this is also available for OnPrem (S4HANA1909) customers.

Our example scenario is as follows

We would like to create, update, delete, display the Service entry for the Auto Service industry (Please note this is taken only for example)

P.S Since covering all these actions in this single blog will be to much information, I will only cover Create and Display actions in this blog and publish another one with other actions and options.

Basic setup


We would need 2 tables (header and item) to store the Service order and also a basic CDS view.

Header Table
@EndUserText.label : 'Service order header table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zserv_ordhd {
key client : abap.clnt not null;
key sord : char10 not null;
partner : bu_partner;
status : char1;
priority : char1;
@Semantics.amount.currencyCode : 'zserv_ordhd.currkey'
estcost : abap.curr(5,2);
currkey : abap.cuky;
include /bobf/s_lib_admin_data;

Item Table

@EndUserText.label : 'Service Order item table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zserv_orditm {
key client : abap.clnt not null;
key sord : char10 not null;
key sitm : numc3 not null;
servcode : char10;
servdescr : char50;
itemstatus : char1;
@Semantics.amount.currencyCode : 'zserv_ordhd.currkey'
partcost : abap.curr(5,2);
labourcode : char10;
labourcat : char1;
labstarttime : timestampl;
labendtime : timestampl;

CDS view for Header table
@AbapCatalog.sqlViewName: 'ZISERVORDHDR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Service order header interface view'
@VDM.viewType: #BASIC
define view ZI_SERV_ORDHRD as select from zserv_ordhd
association [0..*] to ZI_SERV_ORDITM as _Item on $projection.ServiceOrd = _Item.ServiceOrd{
key sord as ServiceOrd,
partner as Customer,
status as Status,
priority as Priority,
@Semantics.amount.currencyCode: 'Currency'
estcost as EstimatedCost,
currkey as Currency,
/*--Admin data */
@Semantics.systemDate.createdAt: true
crea_date_time as CreatedOn,
@Semantics.user.createdBy: true
crea_uname as CreatedBy,
@Semantics.systemDate.lastChangedAt: true
lchg_date_time as ChangedOn,
@Semantics.user.lastChangedBy: true
lchg_uname as ChangedBy,

CDS view for Item table
@AbapCatalog.sqlViewName: 'ZISERVORDITEM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Service order item interface view'
@VDM.viewType: #BASIC
define view ZI_SERV_ORDITM
as select from zserv_orditm
association [1..1] to ZI_SERV_ORDHRD as _Header on $projection.ServiceOrd = _Header.ServiceOrd
key sord as ServiceOrd,
key sitm as ServiceItem,
servcode as ServiceCode,
servdescr as ServiceDescription,
itemstatus as ItemStatus,
@Semantics.amount.currencyCode: '_Header.Currency'
partcost as PartCost,
labourcode as LabourCode,
labourcat as LabourCategory,
labstarttime as LabourStartTime,
labendtime as LabourEndTime,


Until this step there is no difference with our usual procedure and RAP.

Next step for us is to create the Consumption views.


Consumption Views

While doing this we should define which View is Parent View ( Root View ) and connect all Children to this view.

Let’s create the Root Consumption view for our Header table
@AbapCatalog.sqlViewName: 'ZCSERVORDHDRR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Service Order Cons View with RAP'
@Search.searchable: true
@UI.headerInfo: { typeName: 'Service Order', typeNamePlural: 'Service Orders', title: { type: #STANDARD, value: 'ServiceOrd' } }
define root view ZC_SERV_ORDHDR_REST
as select from ZI_SERV_ORDHRD
composition [0..*] of ZC_SERV_ORDITEM_REST as _Item
@UI.facet: [
id: 'ServiceHeader',
position: 10,
label: 'Service Orders'
position: 10,
targetQualifier: 'GeneralData1',
parentId: 'ServiceHeader',
isSummary: true,
isPartOfPreview: true
position: 20,
targetQualifier: 'GeneralData2',
parentId: 'ServiceHeader',
isSummary: true,
isPartOfPreview: true
id: '_Item',
purpose: #STANDARD,
label: 'Item details',
position: 10,
targetElement: '_Item'

@UI.lineItem: [{position: 10, importance: #HIGH, label: 'Service Order' }]
@UI.fieldGroup: [{qualifier: 'GeneralData1',position: 10,importance: #HIGH, label: 'Service Order' }]
@UI.selectionField: [{position: 10 }]
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.9
@UI.hidden: #(CreateAction)
key ServiceOrd,
case when ServiceOrd is initial then cast ( 'X' as bool )
else cast( ' ' as bool ) end as CreateAction,
@UI.lineItem: [{position: 20, importance: #HIGH, label: 'Customer' }]
@UI.fieldGroup: [{qualifier: 'GeneralData1',position: 20,importance: #HIGH, label: 'Customer' }]
@UI.selectionField: [{position: 20 }]
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.9
@ObjectModel.text.element: ['StatusTxt']
@UI.lineItem: [{position: 30, importance: #HIGH, label: 'Status', criticality: 'StatusCriticality' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 10,importance: #HIGH, criticality: 'StatusCriticality', label: 'Status' }]
@UI.selectionField: [{position: 30 }]
@UI.hidden: #(CreateAction)
@Semantics.text: true
case when Status = '1' then 'Open'
when Status = '2' then 'Approval'
when Status = '3' then 'In Progress'
when Status = '4' then 'Completed' else '' end as StatusTxt,
case when Status = '1' then 1
when Status = '2' then 2
when Status = '3' then 2
when Status = '4' then 3 else 1 end as StatusCriticality,
@ObjectModel.text.element: ['PriorityTxt']
@UI.lineItem: [{position: 40, importance: #HIGH, label: 'Priority' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 20,importance: #HIGH, label: 'Priority' }]
@UI.selectionField: [{position: 40 }]
@Semantics.text: true
case when Priority = '1' then 'Urgent'
when Priority = '2' then 'Major'
when Priority = '3' then 'Medium'
when Priority = '4' then 'Minor' else 'Minor' end as PriorityTxt,
@UI.lineItem: [{position: 50, importance: #HIGH, label: 'Estimated Cost', criticality: 'StatusCriticality' }]
@UI.fieldGroup: [{qualifier: 'GeneralData1',position: 30,importance: #HIGH, label: 'Estimated Cost', criticality: 'StatusCriticality' }]
case when EstimatedCost > 500 then 'X'
else '' end as approvalNeeded,

@UI.lineItem: [{position: 60, importance: #HIGH, label: 'Actual Cost' }]
@UI.fieldGroup: [{qualifier: 'GeneralData1',position: 40,importance: #HIGH, label: 'Actual Cost' }]
@Semantics.amount.currencyCode: 'Currency'
@UI.hidden: #(CreateAction)
cast(0 as abap.curr(5,2) ) as ActualCost,
@UI.lineItem: [{position: 70, importance: #MEDIUM, label: 'Currency' }]
@UI.fieldGroup: [{qualifier: 'GeneralData1',position: 50,importance: #MEDIUM, label: 'Currency' }]
@UI.lineItem: [{position: 80, importance: #MEDIUM, label: 'Created On' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 30,importance: #MEDIUM,label: 'Created On' }]
@UI.selectionField: [{position: 50 }]
@UI.hidden: #(CreateAction)
@UI.lineItem: [{position: 90, importance: #MEDIUM, label: 'Created By' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 40,importance: #MEDIUM,label: 'Created By'}]
@UI.selectionField: [{position: 60 }]
@UI.hidden: #(CreateAction)
@UI.lineItem: [{position: 100, importance: #LOW, label: 'Changed On' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 50,importance: #LOW,label: 'Changed On' }]
@UI.hidden: #(CreateAction)
@UI.lineItem: [{position: 110, importance: #LOW, label: 'Changed By' }]
@UI.fieldGroup: [{qualifier: 'GeneralData2',position: 60,importance: #LOW,label: 'Changed By' }]
@UI.hidden: #(CreateAction)
/* Associations */

Now same way create Item consumption view
@AbapCatalog.sqlViewName: 'ZCSERORDITR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Service order Item cons view RAP'
@UI.headerInfo: { typeName: 'Service Order Item', typeNamePlural: 'Service Order Items', title: { type: #STANDARD, value: 'ServiceOrd' } }

as select from ZI_SERV_ORDITM
association to parent ZC_SERV_ORDHDR_REST as _Header on $projection.ServiceOrd = _Header.ServiceOrd
@UI.facet: [
{type: #COLLECTION, position: 10, id: '_Item', label: 'Idoc details'},
{type: #FIELDGROUP_REFERENCE, position: 10, targetQualifier: 'Item1',parentId: '_Item', isSummary: true, isPartOfPreview: true},
{type: #FIELDGROUP_REFERENCE, position: 20, targetQualifier: 'Item2',parentId: '_Item', isSummary: true, isPartOfPreview: true}
@UI.lineItem: [{position:10,importance: #HIGH, label:'Service Order' }]
@UI.fieldGroup: [{qualifier: 'Item1', label:'Service Order', position:10,importance: #HIGH}]
key ServiceOrd,
@UI.lineItem: [{position:20,importance: #HIGH, label: 'Order Item' }]
@UI.fieldGroup: [{qualifier: 'Item1', position:20, label: 'Order Item',importance: #HIGH}]
key ServiceItem,
@UI.lineItem: [{position:30,importance: #MEDIUM , label: 'Service code'}]
@UI.fieldGroup: [{qualifier: 'Item1', position:30, label: 'Service code',importance: #HIGH}]
@UI.lineItem: [{position:40,importance: #MEDIUM , label:'Service description'}]
@UI.fieldGroup: [{qualifier: 'Item1', position:40, label:'Service description',importance: #HIGH}]
@UI.lineItem: [{position:50,importance: #HIGH, label: 'Item status', criticality: 'ItemCriticality' }]
@UI.fieldGroup: [{qualifier: 'Item2', position:10,importance: #HIGH, label: 'Item status', criticality: 'ItemCriticality'}]
@ObjectModel.text.element: ['ItemStatusTxt']
@Semantics.text: true
case when ItemStatus = '1' then 'Open'
when ItemStatus = '2' then 'In Progress'
when ItemStatus = '3' then 'Complete'
else '' end as ItemStatusTxt,
case when ItemStatus = '1' then 1
when ItemStatus = '2' then 2
when ItemStatus = '3' then 3
else 1 end as ItemCriticality,
@UI.lineItem: [{position:60,importance: #MEDIUM, label:'Labour code' }]
@UI.fieldGroup: [{qualifier: 'Item1', position:50, label:'Labour code',importance: #HIGH}]
@UI.lineItem: [{position:70,importance: #MEDIUM, label:'Labour Category' }]
@UI.fieldGroup: [{qualifier: 'Item1', position:60, label:'Labour Category',importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'Item1', position:70, label:'Part cost',importance: #MEDIUM}]
@Semantics.amount.currencyCode: '_Header.Currency'
//_Header.Currency as Currency,
@UI.fieldGroup: [{qualifier: 'Item1', position:80, label:'Labour starttime',importance: #MEDIUM}]
@UI.fieldGroup: [{qualifier: 'Item1', position:90, label:'Labour Endtime',importance: #MEDIUM}]
/* Associations */

The Notable difference with this consumption views are, we cut lots of Annotations.

In older CDS view for BOPF we have to define many ObjectModel Annotations in the Interface and Consumptions Views.


The Parent and Child relationship in BOPF was defined at the bottom of the CDS view where we declare the Associations

All of these annotations are not required now as the RAP does not create the BOPF like framework in the background out of these Annotations.

The Difference we see here is how we define the view and how we write the select statement.

To declare the view as Parent we have to use DEFINE ROOT keyword, with this statement the RAP consider this view ad Parent and automatically create the Relationships for all the Views declared with COMPOSITION statement

Other than these 2 differences we don’t need any other special declaration to set up RAP.

While creating Child view we have to use keyword association to parent. This is sufficient for RAP to link these views and create Relationship.


That’s it. The basic setup for creating the RAP is completed.

We have usual UI Annotations in these CDS view for UI Representation.

Now let’s go further and create Behavior Definition


Behavior Definition

Behavior Definition is used to (name suggest )  define the Behavior of our service. Which means what kind of database activities are allowed, what actions this service should do, Validations etc?

To create the Behavior Definition right click on the Cons View and select “Create Behavior Definition”

Please note: Create Behavior Definition only for the Root View.

In the next screen select the implementation type Unmanaged as this is the only possibility for OnPrem S4HANA1909 to create RAP.

unmanaged implementation in class ZCL_C_I_SERVORD_REST unique;

define behavior for ZC_SERV_ORDHDR_REST alias ServOrd
//late numbering
//lock master
//authorization master
etag ChangedOn
field ( read only ) ServiceOrd, ActualCost, Status, CreatedOn, CreatedBy, ChangedOn, ChangedBy;
field ( mandatory ) Customer;
association _Item { create; }
// action (features : instance) Approved result [1] $self;

define behavior for ZC_SERV_ORDITEM_REST alias ServItem
//late numbering
//lock dependent( <local_field_name> = <target_field_name> )
//authorization dependent( <local_field_name> = <target_field_name> )
//etag <field_name>
field ( read only ) ServiceOrd, ServiceItem, ItemStatus;
field ( mandatory ) LabourCode, LabourCategory;

In the above code we wrote that some fields are read only, some are mandatory. What database actions can be performed. We can also define custom actions( will be explained in details in next Blog )

We also have to create the Class defined in the Behavior with following Signature
class ZCL_C_I_SERVORD_REST definition
public abstract final for behavior of ZC_SERV_ORDHDR_REST.

class ZCL_C_I_SERVORD_REST implementation.


Don’t worry for the implementation of the class methods for now.  We will look at this later.

Now lets create Service Definition.

Service Definition

In Service Definition, we define what View are exposed in this service.

@EndUserText.label: 'Service Definition for Service Order'
define service ZUI_ZC_SERV_ORDHDR_REST {
expose ZC_SERV_ORDHDR_REST as ServHeader;
expose ZC_SERV_ORDITEM_REST as ServItem;

Next step is to create the Service Binding


Service Binding

In Service Binding we define what kind of service we want to create. The service can be simple Odata Service which can be consumed by other services or external systems using REST calls or it could be UI service which then creates FIORI element UI5 service.

Let’s create UI service as this is more fun.

Now let’s click on the Activate Service.

And after this the magic begins.

Our service is activated with automatically create Associations Navigation for the Views exposed in the previous step.

Also we can now already see how our UI Service will look like when implemented in WebIDE.

Click on the Preview button and we can see the preview of our FIORI UI5 application already.

In older frameworks, this was not possible we first have to create UI5 application, consume this service and then check the output but here we can Preview it much earlier and make adjustments if needed.

Not only this, you can navigate to next page to check the Item Association.


By this point our service with FIORI element is ready, now we have to implement the logic for database activities and any external actions which we defined in Behavior Definition.

So let’s go back to our class ZCL_C_I_SERVORD_REST and add the necessary methods.

We declare and implement these methods in Local Types (don’t ask me exactly why, probably as we have to inherit from 2 classes and ABAP do not support this )

In this class we have to declare local class inheriting from CL_ABAP_BEHAVIOR_HANDLER and declare and implement all the database activity methods as below
*"* use this source file for the definition and implementation of
*"* local helper classes, interface definitions and type
*"* declarations
class LCL_BUFFER definition.

public section.

types: begin of TY_BUFFER_HDR.
include type ZSERV_ORDHD as DATA.
types: FLAG type C length 1,

types: begin of TY_BUFFER_ITM.
include type ZSERV_ORDITM as DATA.
types: FLAG type C length 1,
types: tt_SERVORD type sorted table of TY_BUFFER_HDR with unique key SORD.
types: tt_SERVORD_ITM type sorted table of TY_BUFFER_ITM with unique key SORD SITM.

class-data MT_BUFFER_HDR type tt_SERVORD.
class-data MT_BUFFER_ITM type tt_SERVORD_ITM.

class LHC_SERVORD definition inheriting from CL_ABAP_BEHAVIOR_HANDLER.
private section.
methods CREATE_HEADER for modify importing ENTITIES for create ServOrd.
methods UPDATE_HEADER for modify importing ENTITIES for update ServOrd.
methods DELETE_HEADER for modify importing ENTITIES for delete ServOrd.
* methods LOCK_HEADER for lock importing KEYS for lock ServOrd.
methods READ_HEADER for read importing KEYS for read ServOrd result RESULT.
methods CREATE_ITEM for modify importing ENTITIES for create ServItem.
methods UPDATE_ITEM for modify importing ENTITIES for update ServItem.
methods DELETE_ITEM for modify importing ENTITIES for delete ServItem.
* methods LOCK_ITEM for lock importing KEYS for lock ServItem.
methods READ_ITEM for read importing KEYS for read ServItem result RESULT.


class LHC_SERVORD implementation.

get time stamp field data(LV_TSL).
loop at ENTITIES into data(LS_CREATE).
select max( SORD ) from ZSERV_ORDHD into @data(LV_MAX_SORD). "get last servord num
LS_CREATE-%DATA-ServiceOrd = LV_MAX_SORD. "calculate field

LS_CREATE-%DATA-Status = 1. "set default values
append initial line to lcl_buffer=>mt_buffer_hdr assigning field-symbol(<buffer>).
<buffer>-data = corresponding #( ls_create-%DATA ).
<buffer>-data-sord = ls_create-%DATA-ServiceOrd.
<buffer>-data-Partner = ls_create-%DATA-Customer.
if LS_CREATE-%CID is not initial.
insert value #( %CID = LS_CREATE-%CID ServiceOrd = LS_CREATE-ServiceOrd ) into table MAPPED-SERVORD.



"nothing to do here


"nothing to do in this method

get time stamp field data(LV_TSL).
loop at ENTITIES into data(ls_itemCreate).
ls_itemCreate-%DATA-ItemStatus = 1.
ls_itemCreate-%DATA-LabourStartTime = LV_TSL.
insert value #( FLAG = 'C' DATA = corresponding #( ls_itemCreate-%DATA ) ) into table LCL_BUFFER=>MT_BUFFER_ITM.
if ls_itemCreate-%CID is not initial.
insert value #( %CID = ls_itemCreate-%CID ServiceOrd = ls_itemCreate-ServiceOrd ServiceItem = ls_itemCreate-ServiceItem ) into table MAPPED-SERVITEM.



method READ_ITEM.





( In this blog only Create methods are implemented, others will follow in next Blog )

In the method implementation, you can add normal ABAP code to perform actions (isn’t this cool).

You can calculate and populate certain fields, add default values etc..

After this we have to declare and implement another local class inheriting from CL_ABAP_BEHAVIOR_SAVER.

This class has methods

CHECK_BEFORE_SAVE – For before save validations

FINALIZE – Final adjustments

SAVE – Actual commit.

In this class we actual write ABAP code to perform the database action.

You can call standard BAPIs or function modules to commit the transactions or also direct update to your custom tables or any other action like creating file on application server etc..
class lsc_ZI_SERV_ORDHDR_TP definition inheriting from CL_ABAP_BEHAVIOR_SAVER.
protected section.
methods CHECK_BEFORE_SAVE redefinition.
methods FINALIZE redefinition.
methods SAVE redefinition.

class LSC_ZI_SERV_ORDHDR_TP implementation.



method FINALIZE.


method SAVE.
data: LT_DATA_HDR type standard table of ZSERV_ORDHD.
data: LT_DATA_ITM type standard table of ZSERV_ORDITM.

LT_DATA_HDR = value #( for ROW in LCL_BUFFER=>MT_BUFFER_HDR where ( FLAG = 'C' ) ( ROW-DATA ) ).
if LT_DATA_HDR is not initial.
insert ZSERV_ORDHD from table @LT_DATA_HDR.
LT_DATA_ITM = value #( for ROW_ITM in LCL_BUFFER=>MT_BUFFER_ITM where ( FLAG = 'C' ) ( ROW_ITM-DATA ) ).
if LT_DATA_ITM is not initial.
insert ZSERV_ORDITM from table @LT_DATA_ITM.




Last step is to consume this newly created service in our SAP WebIDE and created FIORI element application.

Create FIORI Element application


Follow same steps like we consume other services



At the end of this our service is ready with read and write action along with custom logic for writing the data back to database. You can now enhance the class implementation of the Behavior definition and add further logic. Also we have all the possibilities to enhance the FIORI element app like any other FIORI element development.



RAP ( RestFul ABAP Programming ) Model is very promising, it is very flexible but at the same time reliable. There will be more development and changes coming in this direction in future releases. It leaves less database footprint as it does not create lots of artifacts for every application. We will keep looking for the updates on this topic.
Labels in this area