My Learning Journal On BTP (Series 1) – Using Six Questions And One Example To Learn ABAP RESTful Application Programming Model (RAP)


1. Introduction:

In this blog post, I will firstly use 6 questions which I meet before to do an introduction on ABAP RESTful Application Programming Model. After that, I will create a Fiori application by ABAP RESTful Application Programming Model to practice the things introduced in the first part.

The Fiori application will be created for a restaurant and it includes the functions below:

  1. The Fiori application can list the Food Orders with key elements like Process Status, Processor, etc.
  2. Each ‘Food Order’ may has multiple line items.
  3. Users can filter the Food Orders by ‘Order Status’ and ‘Processor’.
  4. One processor can only process one food order at one time.

2. Questions 

The questions below came from my practice on ABAP RESTful Applicaiton Programming Model.

2.1 What is ABAP RESTful Programming Model?

As introduced in SAP Help Portal and SAP Developer Center, ABAP RESTful Application Programming Model can be used ‘for efficient end-to-end development of intrinsically SAP HANA-optimized OData services (such as SAP Fiori apps) in the ABAP environment.It supports the development of all types of Fiori applications as well as publishing Web APIs.‘ .

ABAP RESTful Application Programming Model has three main layers:

  • Data Modeling & Behavior
  • Business Services Provisioning
  • Service Consumption

From the introductions, we can see ABAP RESTful Application Programming Model can use the technologies like CDS View to create Fiori applications and OData Services (Web API).

2.2 What does ‘REST’ mean?

‘REST’ stands for Representational State Transfer. You can find the detial introduction on it from SAP Help Portal.

Two key points:

a. The Server side will not store the state information of the Client side, this restriction can be called as ‘Stateless‘. Each request send from the Client side to the Server side must includes all the information required by the Server side.

b. The Client side will store and handle the session related information.

For example, we open a web page (consider the web page is Client side) has two fields, Filed1 and Field2 and a button ‘Save’. We edit the value of Field1 from ‘A’ to ‘B’. The value change will not be sent to the backend Server to update the database table until when we click the ‘Save’ button on the web page.

Open question to the reader:

When we click the ‘Save’ button on the web page, which session information on the Client side will be sent to the Server side? Field1, or Filed2, or both Field1 and Filed2? I welcome you to write your answer in the comment.

One thing to note is OData is RESTful, it means the OData UI services and OData Web APIs created by ABAP RESTful Programming Model will fully complies with the desgin principles of Representational State Transfer (REST).

2.3 Which CDS View types should be used?

In ABAP RESTful Application Programming Model, we will define CDS Views for ‘BUSINESS OBEJCTS’, ‘QUERIES’ and ‘BUSINESS OBEJCT PROJECTION’.

One thing to note is use VDM annotation in CDS view has some limitations like system version.

Basic Interface View:

A Basic Interface View is directly deployed on top of a database table, we can think it is a mirror of the database table.

When we define an Interface View, we should use VDM view type ‘#BASIC’ in the annotation part of the CDS view entity.

@VDM.viewType: #BASIC

Composite Interface View:

We can build Composite Interface View based on Basic Interface View or other Composite Interface View.

The main purpose of this view is to combinations data from multiple data sources.

When we define an Composite Interface View, we may use VDM view type ‘#COMPOSITE’ in the annotation part of the CDS view entity. But if the View is for transaction object models (if actions like Create, Update, Delete needed) then we should use VDM view type ‘#TRANSACTIONAL’ in the annotation part of the CDS View.

@VDM.viewType: #COMPOSITE

@VDM.viewType: #TRANSACTIONAL -> We will use this view type in the example later

Consumption View:

Consumption View is the top layer view of VDM views. We should built them based on the Basic Interface View and Composite Interface View. We can define the special anotations in this view to control the proprities of a certain Fiori application or Web API.

For example, we may have two Consumption Views based on one Composite View for two different Fiori applicaitons. The specific requirements of each Fiori applicaiton can be achieved by design specific anotations in each Consumption View.

When we define an Consumption View, we may use VDM view type ‘#CONSUMPTION’ in the annotation part of the CDS view entity.

@VDM.viewType: #CONSUMPTION

2.4 What is Business Object in ABAP RESTful Programming Model?

From the SAP Help Portal, we can see the introduction of Business Object is ‘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. ‘ .

Business Object in ABAP RESTful Programming Model can be charaterized into three parts:

2.4.1 Structure

The introduction from SAP Help Portal: ‘ From structural aspect, a business object consists of a tree of nodes (SalesOrderItemsScheduleLines) 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.’

In this blog post, I will create an example Fiori application for a restaurant and the key element of the applicaiton is Food Order. One Food Order may links to multiple Food Order Items.

In this example, the Business Object consists of a hierarchical tree of two nodes: Food Order and Food Order Items. As a Food Order Line Item cannot exist without link to a Food Order, so Food Order is the root entity and the Food Order Item is the child entity links to the root entity.

As introduced in 2.3 (Which CDS View types will be used in the blog post?), we have Composite Interface View to combinations data from multiple data sources. So we should consider use this view type to create a CDS View for the root entity.

We can find more information on this point in the example later.

2.4.2 Behavior of a Business Object

As introduced in the SAP Help Portal, Behavior of a Business Object can be characted into two parts:

a. Behavior Characteristic

This part defines the general properties of a an enetity.

I will introdue ‘Later Numbering’ and ‘ETag’ later in the example.

b. Operations

This part defines the operations of each entity in a Business Object. ABAP RESTful Application Programming Model has provided the standard operations for Creat, Update and Delete. We can also add customizing operations in the Behavior Defination.

We can define the Behavior Charateristic and Operations in Behavior Definition, the related logic will be add to the Bhevior Implementation links to the Behavior Definition.

Key Points:

  • Behavior Defination will be created at toot entity level. For example, we should only define the Behavior Defination for Food Order, but not for Food Order Item, as Food Order is the root entity and Food Order Item is the Child Entity.
  • Each entity can define seperate Operations. For example, we can define Create and Update operation for root entity Food Order, and only use Update operation for child entity Food Order Item.
  • When we define the Behavior Defination for the root entity, then the Behaviors of the child entities linked to the root entity will be created in the same Behavior Definition.

More information can be found in the example later.

2.4.3 Business Object’s Runtime

As introduced, one key characteristic of ABAP RESTful Application Programming Model is Stateless. It means each time the Client side send a request to the Server side, the request should includes all the necessary information.In the meantime, the Client side should keep the changed transaction information in buffer.

In our example, the enduser can change the Food Order information (like Processor of the Food Order) in the Fiori application, the value change actions can be considered as the ‘Interaction phase‘. Afer the value changed, the chang will be store to the ‘Transactional Buffer‘ (not saved to Server side yet). After the enduser click the ‘Save’ button in the Fiori application, the ‘Save Sequence‘ will be triggerd and if there is nothing worng then the related OData service will be called to update the value in the Server side.

2.5 What are Managed Scenario and Unmanaged Scenario in Behavior Defination?

When we create a new Behavior Defination, the first thing we should consider is which implementation type we should use? Managed Scenario or Unmanaged Scenario?

In short, Managed Scenario is for Greenfield implementation, and Unmanaged Scenario is for Brownfield implementation.

If we choose Managed Scenario as the implementation type of the Behavior Definition, then ABAP RESTful Application Programming Model infrastructure will take care the CRUD (Create, Read, Update, Delete) operations and buffer handling (like store the buffer into the PERSISTENT TABLE) for us.

The main reason to choose Unmanaged Scenario is we can reuse the existing business logic into the operations. But the CRUD operations and Buffer handling should be implemented in the related  Behavior Implementation.

One thing to note is in Managed Scenario, we still have chance to call our own update task. To achieve this, we should use syntax ‘WITH UNMANAGED SAVE’ to intstead of ‘PERSISTENT TABLE’ in the Behavior Defination. After that, we can add our own update logic in the Behavior Implementation of the Behavior Defination.

2.6  What is Service Defination and Service Bidning? What is the relationship between them?

After we define the objects like Data Model and Business Object, we should use the Service Definition and Service Binding to expose the objects to the external environments.

We can define the scope of a service by Service Definition and define the Binding Type (UI or Web API) by Service Binding. One Service Definition can links to multiple Service Bindings if it is needed.

One thing to note is if we create a Service Definition for a Composite Interface View which links root entity and child entity, then we should expost both of the root entity and child eneity in the Service Definition.

3. Example 

Now let’s start to use ABAP RESTful Application Programming Model to build up a Fiori applicaiton.

I will use ADT (ABAP Development Tool) to do the development, you can link your BTP trial account ABAP Cloud Environemnt to your ADT tool. Please make sure you have applied the BTP trial account and installed the tool.

3.1 Data Modeling

We will first create the data modeling objects which will be used later.

3.1.1  Data Elements

  • ‘ZORDER_ID_E’ (Order ID) Type CHAR10
  • ‘ZORDER_ITEM_E’ (Order Item) Type CHAR10
  • ‘ZORDER_STATUS_E’ (Deliver Address) Type CHAR1
  • ‘ZORDER_ADDR’ (Order Status) Type CHAR30
  • ‘ZORDER_COM’ (Order Comment) Type CHAR50
  • ‘ZORDER_PAYMENTM_E’ (Order Payment Method) Type CHAR10
  • ‘ZPROCESSOR_ID_E’ (Order processor ID) Type CHAR10
  • ‘ZPROCESSOR_NAME_E’ (Order processor name) Type CHAR10
  • ‘ZLAST_CHANGEDBY_E’ (Last Changed By) Type CHAR12

3.1.2 Database Tables

a. Processor table: ‘ZORD_PRO_T’

This table will be used as the data source table of the Food Order processors.

@EndUserText.label : 'Order processor table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zord_pro_t { key client : abap.clnt not null; key processorid : zprocessor_id_e not null; name : zprocessor_name_e;
}

b. Food Order table: ‘ZFOOD_ORDERS_T’

This table will be used to store the header information of the food orders.

@EndUserText.label : 'Table for food ordres'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zfood_orders_t { key client : abap.clnt not null; key orderid : zorder_id_e not null; status : zorder_status_e; receivedtime : timestampl; processorid : zprocessor_id_e; last_changed_by : zlast_changedby_e; last_changed_at : timestampl; }

c. Food Order Item Table: ‘ZFOOD_ORD_ITEM_T’

This table will be used to store the main information of the food orders.

@EndUserText.label : 'Food order item table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zfood_ord_item_t { key client : abap.clnt not null; key orderid : zorder_id_e not null; key orderitem : zorder_item_e not null; deliver_addr : zorder_addr; ord_comment : zorder_com; payment_method : zorder_payment_m; }

d. Order Status table: ‘ZFOOD_ORDSTA_T’

This table will be used as the data source of the serach help for the ‘status’ field.

@EndUserText.label : 'Food order status'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zfood_ordsta_t { key client : abap.clnt not null; key status : zorder_status_e not null; description : abap.char(20);
}

3.1.3 Create Classes to Insert Values to the Database Tables

For testing purpose, we should add some values to the database tables by a class.

Interface ‘if_oo_adt_classrun’ should be used in the class.

Class ‘ZCL_ORDER_TABLE’:

Insert values to database table ZFOOD_ORDERS_T.

CLASS zcl_order_table DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION.
ENDCLASS. CLASS ZCL_ORDER_TABLE IMPLEMENTATION. METHOD if_oo_adt_classrun~main. DATA: lt_orders TYPE STANDARD TABLE OF zfood_orders_t. lt_orders = VALUE #( ( orderid = 'OR00000001' status = '5' receivedtime = '20210810093940.5961061' processorid = 'PR00000001' last_changed_by = 'JasonLu' last_changed_at = '20210810095040.5961061' ) ( orderid = 'OR00000002' status = '2' receivedtime = '20210810094040.5961061' processorid = 'PR00000002' last_changed_by = 'JasonLu' last_changed_at = '20210810095540.5961061' ) ( orderid = 'OR00000003' status = '3' receivedtime = '20210810095040.5961061' processorid = 'PR00000003' last_changed_by = 'JasonLu' last_changed_at = '20210810095640.5961061' ) ). DELETE FROM zfood_orders_t. INSERT zfood_orders_t FROM TABLE @lt_orders. CLEAR lt_orders. SELECT * FROM zfood_orders_t INTO TABLE @lt_orders. IF sy-subrc = 0. out->write( 'data instered to the table!' ). out->write( lt_orders ). ENDIF. ENDMETHOD.
ENDCLASS.

Class ‘ZCL_ORDER_ITEM_TABLE’:

Insert value to database table ZFOOD_ORD_ITEM_T.

CLASS zcl_order_item_table DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION.
ENDCLASS. CLASS ZCL_ORDER_ITEM_TABLE IMPLEMENTATION. METHOD if_oo_adt_classrun~main. DATA: lt_order_item TYPE STANDARD TABLE OF zfood_ord_item_t. lt_order_item = VALUE #( ( orderid = 'OR00000001' orderitem = 1 deliver_addr = 'test addr' ord_comment = 'asap' payment_method = 'Cash' ) ( orderid = 'OR00000001' orderitem = 2 deliver_addr = 'test addr' ord_comment = 'asap' payment_method = 'Cash' ) ( orderid = 'OR00000002' orderitem = 1 deliver_addr = 'test addr' ord_comment = 'less salt' payment_method = 'Cash' ) ( orderid = 'OR00000003' orderitem = 1 deliver_addr = 'test addr' ord_comment = 'more suger' payment_method = 'Cash' ) ). DELETE FROM zfood_ord_item_t. INSERT zfood_ord_item_t FROM TABLE @lt_order_item. CLEAR lt_order_item. SELECT * FROM zfood_ord_item_t INTO TABLE @lt_order_item. IF sy-subrc = 0. out->write( 'data instered to the table!' ). out->write( lt_order_item ). ENDIF. ENDMETHOD.
ENDCLASS.

Class ‘ZCL_PROCESSOR_TABLE’:

Insert value to database table ZORD_PRO_T.

CLASS zcl_processor_table DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_oo_adt_classrun . PROTECTED SECTION. PRIVATE SECTION.
ENDCLASS. CLASS ZCL_PROCESSOR_TABLE IMPLEMENTATION. METHOD if_oo_adt_classrun~main. DATA lt_processor TYPE STANDARD TABLE OF ZORD_PRO_T. lt_processor = value #( ( processorid = 'PR00000001' name = 'PROCESSOR1' ) ( processorid = 'PR00000002' name = 'PROCESSOR2' ) ( processorid = 'PR00000003' name = 'PROCESSOR3' ) ). DELETE FROM ZORD_PRO_T. INSERT ZORD_PRO_T FROM TABLE @lt_processor. CLEAR lt_processor. SELECT * FROM ZORD_PRO_T INTO TABLE @lt_processor. IF sy-subrc = 0. out->write( 'data instered to the table!' ). out->write( lt_processor ). ENDIF.
ENDMETHOD.
ENDCLASS.

Class ‘ZCL_FOOD_ORDER_STATUS_TABLE’:

Insert value to database table ZFOOD_ORDSTA_T.

CLASS zcl_food_order_status_table DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION.
ENDCLASS. CLASS ZCL_FOOD_ORDER_STATUS_TABLE IMPLEMENTATION. METHOD if_oo_adt_classrun~main. DATA: lt_status TYPE STANDARD TABLE OF zfood_ordsta_t. lt_status = VALUE #( ( status = '1' description = 'Order Received') ( status = '2' description = 'Order processing') ( status = '3' description = 'Out of Deliver') ( status = '4' description = 'Order Delivered') ( status = '5' description = 'Order Finish') ). DELETE FROM zfood_ordsta_t. INSERT zfood_ordsta_t FROM TABLE @lt_status. CLEAR lt_status. SELECT * FROM zfood_ordsta_t INTO TABLE @lt_status. IF sy-subrc = 0. out->write( 'data instered to the table!' ). out->write( lt_status ). ENDIF. ENDMETHOD.
ENDCLASS.

After create the classes, you can run them as ABAP Application (Console).

3.2 Create Basic Interface CDS Views

After the persistent database tables are created, we should create the ‘Basic Interface’ CDS views on the database tables.

a. ‘ZI_FOODORD_PROCESSOR’

As I want to show the Processor’s name but not the Processor ID in the Fiori application, so I used annotation ‘@ObjectModel.text.element’ to establishes the link between the annotated element (processorid) and its descriptive language-independent text (name).

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Basic CDS View for Food Order Processor table'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel: { usageType: { serviceQuality: #B, sizeCategory: #XL, dataClass: #TRANSACTIONAL }
}
define view entity ZI_FOODORD_PROCESSOR as select from zord_pro_t { key processorid as Processorid, name as Name
}

b. ‘ZI_FOOD_ORDSTA’

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Basic Interface View for Food Order Status'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel: { usageType: { serviceQuality: #B, sizeCategory: #XL, dataClass: #TRANSACTIONAL }
}
define view entity ZI_FOOD_ORDSTA as select from zfood_ordsta_t { @ObjectModel.text.element: ['description'] key status as Status, description as Description
}

c. ‘ZI_FORDER_ITEMS’

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Basic interface view for food order items'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{ serviceQuality: #X, sizeCategory: #S, dataClass: #MIXED
}
define view entity ZI_FORDER_ITEMS as select from zfood_ord_item_t
association [1..1] to ZI_FOOD_ORDERS as _FoodOrder on $projection.Orderid = _FoodOrder.Orderid { key orderid as Orderid, key orderitem as OrderItem, deliver_addr as DeliverAddr, ord_comment as OrdComment, payment_method as PaymentMethod, //Associationa _FoodOrder }

d. ‘ZI_FOOD_ORDERS’

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Basic interface view for food orders'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel: { representativeKey: 'Orderid', usageType: { dataClass: #TRANSACTIONAL, serviceQuality: #B, sizeCategory: #L }
}
define view entity ZI_FOOD_ORDERS as select from zfood_orders_t association [0..1] to ZI_FOODORD_PROCESSOR as _Processor on $projection.Processorid = _Processor.Processorid
association [0..1] to ZI_FOOD_ORDSTA as _Status on $projection.Status = _Status.Status
association [1..*] to ZI_FORDER_ITEMS as _Items on $projection.Orderid = _Items.Orderid
{ key orderid as Orderid, status as Status, _Status.Description as StatusDesc, receivedtime as Receivedtime, processorid as Processorid, _Processor.Name as ProcessorName, @Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy, @Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt, //Association _Processor, _Status, _Items
}

3.3 Create Composite Interface View

Now let’s start to create the Composite Interface views for Food Order and Food Order Items.

‘ZR_FORDER_ITEMS’

Firstly, I will defind the Child node.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Composite Interface view for Food Orders Items'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel: { representativeKey: 'Orderid', usageType: { dataClass: #TRANSACTIONAL, serviceQuality: #B, sizeCategory: #XL }
}
define view entity ZR_FORDER_ITEMS as select from ZI_FORDER_ITEMS association to parent ZR_FOOD_ORDERS as _FoodOrder on $projection.Orderid = _FoodOrder.Orderid
{ key Orderid, key OrderItem, DeliverAddr, OrdComment, PaymentMethod, /* Associations */ _FoodOrder
}

Now, let’s define the Root one.

@AbapCatalog: { sqlViewName: 'ZFOODORDERS_C', preserveKey: true, compiler.compareFilter: true
}
@ObjectModel: { usageType: { serviceQuality: #B, sizeCategory: #XL, dataClass: #TRANSACTIONAL }, representativeKey: 'Orderid'
}
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Composite Interface view for Food Orders'
define root view ZR_FOOD_ORDERS as select from ZI_FOOD_ORDERS composition [1..*] of ZR_FORDER_ITEMS as _Items { key Orderid, Status, StatusDesc, Receivedtime, Processorid, ProcessorName, LastChangedBy, LastChangedAt, /* Associations */ _Items, _Processor, _Status
}

3.4 Create Behavior Definiation

After create the Compisite Interface View, I will create the related Behavior Definition.

In this example, we do not need to create Behavior Definitions for both of the Composite Interface views. Insteadly,we should only create it for the Root view.

I will use Managed scenario in this example.

managed implementation in class zbp_r_food_orders unique;
//strict; //Comment this line in to enable strict mode. The strict mode is prerequisite to be future proof regarding syntax and to be able to release your BO. define behavior for ZR_FOOD_ORDERS alias food_order
//persistent table zfood_orders_t
with unmanaged save
lock master
authorization master ( instance )
etag master LastChangedAt
{ create; update; delete; association _Items { create; } mapping for zfood_orders_t { LastChangedBy = last_changed_by; LastChangedAt = last_changed_at; } // validations validation validateOrderProcessor on save { field Processorid; }
} define behavior for ZR_FORDER_ITEMS alias food_orderitem
//persistent table ZFOOD_ORD_ITEM_T
with unmanaged save
lock dependent by _FoodOrder
authorization dependent by _FoodOrder
//etag master <field_name>
{ update; delete; field ( readonly ) Orderid; association _FoodOrder; mapping for ZFOOD_ORD_ITEM_T { DeliverAddr = deliver_addr; }
}

One character of ABAP RESTful Application Programming Model is stateless. But this may cause the consistency question, as different users may edit the same field in the Fiori application and submit the changes at the almost same time. So we should use ‘ETatg’ to consistently identiy if the entity is updated/changed.

As introduced, ABAP RESTful Application Programming Model will help us to handle the ‘Save’ ‘Delete’ and ‘Update’ actions under Managed scenario. So we should assign the data change should  be performed on which database table (‘persistent table’). But in this example, I will use syntax ‘WITH UNMANAGED SAVE‘ but not ‘Persistent Table‘.

Sometimes, same field may has different name in the database table and the CDS views. So we should add the mapping bettewn the database table and the CDS view. I add ‘Mapping For’ in the Behavior Definition as an example.

3.4.1 Create Behavior Definition Implementation Class

As introduced, the Managed scenario in Behavior Deinition will help us to handle the CRUD performs. So if we still need to add the Behavior Definition Implementation Class in this example? The answer is Yes.

Let’s come back to the reqirements of the example, one of the requirements is ‘One processor can only process one food order at one time’. So, we should do a check when we create a Food Order or update a Food Order to check if the processor is busy with another order.

You may have a question on this, as we know the Managed scenario will help us to do the ‘Save’ perform when we create or update a record, so we may do not have chance to do the check when save happens. But as introduced, we can consider use syntax ‘WITH UNMANAGED SAVE‘ in Managed scenario to handle this kind of requirement. One thing to note is if we cannot use ‘WITH UNMANAGED SAVE’ and ‘PERSISTENT TABLE’ at same time in a Behavior Definition.

Below is the save sequence description from SAP Help Portal.

We also can use the ‘Validation’ method in Behavior Definition to hanle the requirement.

The code below is an example to use ‘With Unmanaged Save‘ and ‘Validation‘.

CLASS lsc_zr_food_orders DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS save_modified REDEFINITION. ENDCLASS. CLASS lsc_zr_food_orders IMPLEMENTATION. METHOD save_modified. DATA lv_check TYPE ABAP_BOOL. IF CREATE-food_order IS NOT INITIAL. DATA lt_foodorder TYPE STANDARD TABLE OF ZFOOD_ORDERS_T. lt_foodorder = CORRESPONDING #( create-food_order ). "In Create scenario, we should check if the assigned Processor is "linked to any Food Order with status '2'(Order Processing) SELECT processorid FROM ZFOOD_ORDERS_T WHERE status = '2' INTO TABLE @DATA(lt_processors). LOOP AT lt_processors INTO DATA(ls_processor). READ TABLE lt_foodorder WITH KEY processorid = ls_processor-processorid TRANSPORTING NO FIELDS. IF sy-subrc = 0. "I do not think we need to double check the buffer of the business object by EML at here, "but I welcome you to leave your thinking in comment in case you have different ideas lv_check = abap_true. ENDIF. ENDLOOP. IF lv_check IS INITIAL. " If the Processor is free then save the record to the database table INSERT ZFOOD_ORDERS_T FROM TABLE @lt_foodorder. ELSE. "Add error message to parameter FAILD. ENDIF. ENDIF. ENDMETHOD. ENDCLASS. CLASS lhc_ZR_FOOD_ORDERS DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION IMPORTING keys REQUEST requested_authorizations FOR zr_food_orders RESULT result. METHODS validateorderprocessor FOR VALIDATE ON SAVE IMPORTING keys FOR food_order~validateorderprocessor. ENDCLASS. CLASS lhc_ZR_FOOD_ORDERS IMPLEMENTATION. METHOD get_instance_authorizations. ENDMETHOD. METHOD validateOrderProcessor. READ ENTITY ZR_FOOD_ORDERS FROM VALUE #( FOR <root_key> IN keys ( %key-orderid = <root_key>-orderid %control = VALUE #( Processorid = if_abap_behv=>mk-on ) ) ) RESULT DATA(lt_orders). READ TABLE lt_orders INTO DATA(ls_order) INDEX 1. CHECK ls_order IS NOT INITIAL. SELECT SINGLE * FROM ZFOOD_ORDERS_T WHERE status = '2' AND processorid = @ls_order-Processorid INTO @DATA(ls_order_c). IF sy-subrc = 0. "The processor has linked to other Food Order APPEND VALUE #( %key = ls_order-%key Orderid = ls_order-Orderid ) TO failed-food_order. "food_order = Business Object ZR_FOOD_ORDERS "Create message class ZFOOD_ORDER_MESSAGES with the error message you want to use APPEND VALUE #( %key = ls_order-%key %msg = new_message( id = 'ZFOOD_ORDER_MESSAGES' number = 001 v1 = ls_order-processorid severity = if_abap_behv_message=>severity-error ) %element-processorid = if_abap_behv=>mk-on ) TO reported-food_order. ENDIF. ENDMETHOD. ENDCLASS.

3.4 Create Consumption View

As mentioned, Consumption View is the top layer view of the VDM views. We should create the views as the Projection Views in ABAP RESTful Programming Application Model.

If the related Business Object will be used for UI purpose (like create a Fiori application), then we should also add the UI element definition in the Projection View.

For the example, we should create a new CDS view refer to the Composite Interface view.

Select ‘Define Projection View’.

Below is an example:

@EndUserText.label: 'Projection view for Food Orders'
//@AccessControl.authorizationCheck: #CHECK
@UI: { headerInfo: { typeName: 'Food Orders', typeNamePlural: 'Orders', title: { type: #STANDARD, value: 'Orderid' } } }
@Search.searchable: true
define root view entity ZC_FOOD_ORDERS
provider contract transactional_query
as projection on ZR_FOOD_ORDERS { @UI.facet: [ { id: 'Orders', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: 'Order', position: 10 } ] @UI: { lineItem: [ { position: 10, importance: #HIGH } ], identification: [ { position: 10, label: 'Order IDs' } ] } key Orderid, @UI: { lineItem: [ { position: 20, importance: #HIGH } ], identification: [ { position: 20, label: 'Order Status' } ], selectionField: [ { position: 20 } ] } @Consumption.valueHelpDefinition: [{ entity : {name: 'zi_food_ordsta', element: 'Status' } }] @ObjectModel.text.element: ['Description'] @Search.defaultSearchElement: true Status as Status, _Status.Description as Description, @UI: { lineItem: [ { position: 30, importance: #HIGH } ], identification: [ { position: 30, label: 'Order Received Time' } ] } Receivedtime, @UI: { lineItem: [ { position: 40, importance: #HIGH } ], identification: [ { position: 40, label: 'Order Processor' } ], selectionField: [ { position: 40 } ] } @Consumption.valueHelpDefinition: [{ entity : {name: 'zi_foodord_processor', element: 'Processorid' } }] @ObjectModel.text.element: ['ProcessorName'] @Search.defaultSearchElement: true Processorid as Processorid, _Processor.Name as ProcessorName, @UI: { lineItem: [ { position: 60, importance: #HIGH } ], identification: [ { position: 60, label: 'Last Changed By' } ] } LastChangedBy, @UI.hidden: true LastChangedAt, /* Associations */ _Items, _Processor, _Status
}

3.5 Create Service Definition and Service Binding

Right click the just created Projection view and then create the Service Definition.

Below is an example:

@EndUserText.label: 'Service definition on Food Orders'
define service ZSRV_C_FOOD_ORDERS { expose ZC_FOOD_ORDERS; }

After that, we can create a Service Binding based on the Service Definition.

In this example, I will use ‘OData V4 – UI’ as the Binding Type.

After the Service Binding is created, we should Active it and Publish it.

Click the ‘Preview’ button, a new web page will be opened as the preview of the Fiori application.

Click the ‘Go’ button, the test data which we assigned to the database tables will be displayed.

4. Conclusion

In this blog post, I tried to use the questions which I met in my learing processing and one example to introduce ABAP RESTful Programming Model.

Now we should have some high level ideas on what is ABAP RESTful Application Programming Model and the key elements/objects in it. I recommend you read the related topics in SAP Help Portal and SAP Developer Center to have a deeper understanding of the new and interesting topic.

Jason Lu