SAP BTP, SAP ABAP Development, SAP ABAP Environment

ABAP RESTful Application Programming Model (RAP) – ABAP Cloud

Intro

In this blog I am going to talk about RAP model – RESTful ABAP Programming Model. As described by SAP:-

The ABAP RESTful Application Programming Model (in short RAP) defines the architecture for efficient end-to-end development of intrinsically SAP HANA-optimized OData services (such as Fiori apps). It supports the development of all types of Fiori applications as well as publishing Web APIs. 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.

We will try to understand it technically as we surf…

Pre-requisite

  • Knowledge of CDS
  • Knowledge Fiori elements
  • knowledge of ABAP Cloud syntax
  • Access to BTP account (trial would do)
  • Eclipse Installation

Evolution

I am sure many of you would have seen the below picture. Let’s scan it one more time.

Classic ABAP programming is something we have used since time immemorial and still lives to date but with technology changing every few years there was no way we could stick to the past.

With advent of S/4HANA, Fiori adoption got a big kick start as the future UI technology (though it was available before S/4HANA 1511 was launched) and so did the adoption of ABAP programming model for SAP Fiori. Hence many of you would be aware of this architecture (middle box in the picture above) and would have used in your projects where you would have created a CDS view (basic, interface, Consumption – with Fiori elements) and used that CDS view in a SEGW gateway project and consumed that project in a Fiori App etc.

Well, this works fine but with further technological shift happening towards Cloud, it’s also of importance to have a model which can be cloud ready. This is where the RAP model is findings its place (along with CAP but CAP model consumption is more NodeJS/Java driven whereas RAP is within the realms of ABAP). The core of data modelling for RAP remains CDS and the consumption is defined by a Behavior Definition + Behavior Implementation + Service Definition + Service Binding as shown in the Bigg Picture below.

Service Architecture

Where does the ABAP service resides – SaaS on BTP

Which database it uses – SAP HANA embedded

How the coding is done – Via Eclipse ADT

What type of language – ABAP on Cloud

I am not going to spend more time on these pictures as I feel as an ABAPer it’s better to understand concepts via the code and not slide decks. So, let’s get started with our Employee Data model for which we want to carry out CRUD operations on an Employee.

BTP Account

As a first Step get access to BTP trial account or any BTP account where you could access “Prepare an account for ABAP Trial”

Click on Boosters ->Prepare an Account for ABAP Trial ->Start

Once that is done you will get a service Key which you should download and keep.

Eclipse

You can download the latest Eclipse from https://www.eclipse.org/downloads/packages/

And once done create an ABAP Cloud Project

You can enter your service Key here

Create Package

Create a Package for your developments

Create Table

Once the package is created, create a new table for Employee

Let’s call it ZTEST_EMPL

The structure looks like

@EndUserText.label : 'Employee Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztest_empl {

  key mandt       : abap.clnt not null;
  key id_int      : sysuuid_x16 not null;
  id_ext          : abap.char(10) not null;
  firstname       : abap.char(20);
  lastname        : abap.char(20);
  @Semantics.amount.currencyCode : 'ztest_empl.currency'
  salary          : abap.curr(10,2);
  currency        : abap.cuky;
  exp             : abap.int1;
  role            : abap.char(50);
  active          : abap_boolean;
  created_by      : syuname;
  created_at      : timestampl;
  last_changed_by : syuname;
  last_changed_at : timestampl;

}

The data element of id_int is of significance because it allows auto generation of id_int.

Create Interface CDS

As the core of RAP technologies is a CDS view, lets create one on top of our table

@EndUserText.label: 'Interface view for Employee'
define root view entity ZI_test_empl
  as select from ztest_empl
{

  key id_int,
      id_ext,
      firstname,
      lastname,
      salary,
      currency,
      exp,
      role,
      active,
      @Semantics.user.createdBy: true
      created_by,
      @Semantics.systemDateTime.createdAt: true
      created_at,
      @Semantics.user.lastChangedBy: true
      last_changed_by,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at
}

Note that it’s a view entity hence does not have an SQL view annotation.

Create Consumption CDS

I will now create a root consumption view.

@EndUserText.label: 'Consumption View'
@Search.searchable: true
@Metadata.allowExtensions: true
define root view entity ZC_TEST_EMPL 
as select from ZI_test_empl as Empl 
{
      key id_int,
      id_ext,
      firstname,
      lastname,
      salary,
      currency,
      exp,
      role,
      active,
      @Semantics.user.createdBy: true
      created_by,
      @Semantics.systemDateTime.createdAt: true
      created_at,
      @Semantics.user.lastChangedBy: true
      last_changed_by,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at 
}

Disclaimer:

Please note that I have not created a Projection view although the picture under Evolution section suggests creating a project view. I am trying to see if I could work without it technically. You could create a Projection view and expose whatever is required to be exposed in the corresponding behavior definition. My behavior Implementation is directly on Consumption view and not on interface view. Although Projection view would provide advantages in terms of whatever you want to project, I am trying to see if there is a technical limitation if I don’t follow that path.

Create Metadata extension

Create a metadata extension on the Consumption view so that Fiori annotations could be segregated.

@Metadata.layer: #CORE
@UI: {
  headerInfo: { typeName: 'Employee',
                typeNamePlural: 'Employees',
                title: { type: #STANDARD, label: 'Employee', value: 'id_ext' }  },
                presentationVariant: [{ sortOrder: [{ by: 'firstname', direction:  #ASC }] }] }
annotate view ZC_TEST_EMPL with
{
  @UI.facet: [ { id:              'Employee',
                 purpose:         #STANDARD,
                 type:            #IDENTIFICATION_REFERENCE,
                 label:           'Employee',
                 position:        10 } ]
  @UI.hidden: true
  id_int;
  @UI: {
  lineItem:       [ { position: 10, importance: #HIGH } ],
  identification: [ { position: 10, label: 'ID [1,...,99999999]' } ] }
  @EndUserText.label: 'Employee Id'
  id_ext;
  @UI: {
      lineItem:       [ { position: 20, importance: #HIGH } ],
      identification: [ { position: 20, label: 'First Name' } ],
      selectionField: [{ position : 10 }]}
  @EndUserText.label: 'First name'
  @Search.defaultSearchElement: true
  firstname;
  @UI: {
      lineItem:       [ { position: 30, importance: #HIGH } ],
      identification: [ { position: 30, label: 'Last Name' } ] }
  @EndUserText.label: 'Last Name'
  lastname;
  @UI: {
            lineItem:       [ { position: 40, importance: #HIGH } ],
            identification: [ { position: 40, label: 'Salary' } ] }
  @EndUserText.label: 'Salary'
  salary;
  @UI: {
            lineItem:       [ { position: 50, importance: #HIGH } ],
            identification: [ { position: 50, label: 'Currency' } ] }
  currency;

  @UI: {
            lineItem:       [ { position: 60, importance: #HIGH } ],
            identification: [ { position: 60, label: 'Experience' } ] }
  @EndUserText.label: 'Experience'
  exp;

  @UI: {
            lineItem:       [ { position: 70, importance: #HIGH } ],
            identification: [ { position: 70, label: 'Role' } ] }
  @EndUserText.label: 'Role'
  role;

  @UI: {
            lineItem:       [ { position: 80 }, { type: #FOR_ACTION, dataAction: 'setActive', label: 'Set Active' } ],
            identification: [ { position: 80 }, { type: #FOR_ACTION, dataAction: 'setActive', label: 'Set Active' } ] }
  @EndUserText.label: 'Active ?'
  active;

  @UI.hidden: true
  created_by;
  @UI.hidden: true
  created_at;
  @UI.hidden: true
  last_changed_by;
  @UI.hidden: true
  last_changed_at;

}

Note the method associated with dataAction = ‘setActive’ would be used in Behavior Definition & Implementation.

Behavior Definition

This is listing of all the behaviors – Basically meaning how my entity would behave.

  • What is allowed – CUD ?
  • What is read only?
  • What function imports ( actions ), calculation and validations I can do ?
managed implementation in class zbp_c_test_empl unique;

define behavior for ZC_TEST_EMPL alias Empl
persistent table ZTEST_EMPL
lock master
//authorization master ( instance )
//etag master <field_name>
{

// CUD operations come by default
  create;
  update;
  delete;

// manage numburing, read only and mandatory behaviours of the entity
 field ( numbering : managed, readonly ) id_int;
 field ( readonly ) active, salary, currency;
 field ( readonly ) created_by, created_at, last_changed_by, last_changed_at;
 field ( mandatory ) firstname, lastname;

// Function import to set the Employee to active/inactive
action ( features : instance ) setActive result [1] $self;
//determination of Salary based on Experience and Role
determination calcSalary on save { field exp, role; }
//validate various fields user is going to enter on the UI
validation validateFirstName on save { field firstname; }


}

Let’s look at this one by one

  • Managed: This means frameworks is going to take care of the CUD operations. You could have unmanaged as well if you are looking to port some code from ABAP and want to change the default CUD operations.
  • Implementation in class: Please note that Behavior definition is just the definition (like you have interfaces in ABAP which need to be implemented using a class implementation ). The implementation needs to be done in the class zbp_c_test_empl.
  • Unique: The mandatory addition unique defines that each operation can be implemented exactly once.
  • define behavior for: Defines the behavior for a RAP business object
  • persistent table: DDIC database table a RAP BO is based on. The data on the persistent table is processed by RAP BO operations.
  • Lock master: Specifies the RAP locking mechanism for a RAP BO entity. The RAP locking mechanism prevents simultaneous modification access to data on the database by more than one user. Whenever a lock is requested for a specific entity instance, its lock master and all lock-dependent entity instances are locked for editing by a different user.
  • Create, Update, Delete: Create, update, and delete are standard operations. They are also known as CRUD operations, which is an acronym for create, read, update, delete. The read operation is always implicitly enabled for each entity listed in a CDS behavior definition (BDEF) and it mustn’t be declared explicitly.
  • field (numbering : managed, readonly ): The numbering should be managed by RAP framework and not by the user and it is Read only which means it can’t be changed manually.
  • field (mandatory): Defines that it is mandatory to enter values into the specified fields before persisting them on the database. These fields are marked as mandatory ( Adds a *) on the user interface. However, there is no runtime check for mandatory fields and no runtime error occurs if a mandatory field is not filled.
  • field (readonly) active, salary, currency: Salary, currency & active fields can’t be changed by users. Salary + Currency would be determined based on the years of experience & role and active field will be set by action called setActive.
  • action (features : instance ) setActive result [1] $self: RAP actions are non-standard RAP BO operations that modify the state of an entity instance. This is like a function import in traditional SEGW gateway project. The custom logic must be implemented in the RAP handler method FOR MODIFY. Set in metadata extension file.
  • determination calcSalary on save { field exp, role; } : Based on exp and role, calculate the salary when save is hit.
  • validation validateFirstName on save { field firstname; } : Validate the field firstname entered on the UI when save is hit.

Behavior Implementations

The Definition needs to be implemented in a class using custom logic using EML (entity manipulation language)

METHOD setActive.: This method sets the active flag to X when the UI button is hit. The UI button is set in the metadata extension and linked to this method.

MODIFY ENTITIES OF zc_test_empl IN LOCAL MODE
    ENTITY Empl
    UPDATE FIELDS ( active )
    WITH VALUE #(
    FOR wa IN keys (    %tky = wa-%tky
        active = abap_true ) )
       FAILED failed
       REPORTED reported.

    " Fill the response table
    READ ENTITIES OF zc_test_empl IN LOCAL MODE
    ENTITY Empl
    ALL FIELDS WITH CORRESPONDING #( keys )
    RESULT DATA(lt_empl).

    result = VALUE #( FOR wa_empl IN lt_empl
                        ( %tky   = wa_empl-%tky
                          %param = wa_empl ) ).
 ENDMETHOD.

What it does?

  1. Modify the CDS entity and set the field active = abap_true.
  2. Read the entity back
  3. Set the result : %param is a component group in BDEF derived types. It is used for the result parameter in the context of action and function %param must be filled by the action.

METHOD calcSalary.

METHOD calcSalary.
    READ ENTITIES OF zc_test_empl IN LOCAL MODE
     ENTITY Empl
     FIELDS ( exp role ) WITH CORRESPONDING #( keys )
     RESULT DATA(lt_empl).
    LOOP AT lt_empl ASSIGNING FIELD-SYMBOL(<fs_empl>).
      IF <fs_empl>-role = 'Director' AND
         <fs_empl>-exp >= '20'.
        <fs_empl>-salary = '50000000'.
        <fs_empl>-currency = 'INR'.
      ELSEIF <fs_empl>-role = 'Director' AND
         <fs_empl>-exp < '20'.
        <fs_empl>-salary = '45000000'.
        <fs_empl>-currency = 'INR'.
      ELSE.
        <fs_empl>-salary = '2000000'.
        <fs_empl>-currency = 'INR'.
      ENDIF.

      MODIFY ENTITIES OF zc_test_empl IN LOCAL MODE
      ENTITY Empl
      UPDATE FIELDS ( salary currency  )
      WITH VALUE #(
      (  %tky = <fs_empl>-%tky
      salary = <fs_empl>-salary
      currency =  <fs_empl>-currency ) ).
    ENDLOOP.
  ENDMETHOD.

What it does?

  1. Read the entity for the entered key and gets the data into internal table lt_empl.
  2. Loops over lt_empl and based on a logic sets the salary back to the entity using MODIFY.

METHOD validateFirstName.

METHOD validateFirstName.

    READ ENTITIES OF zc_test_empl IN LOCAL MODE
    ENTITY Empl
    FIELDS ( firstname ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_empl).

    READ TABLE lt_empl ASSIGNING FIELD-SYMBOL(<fs_empl>) INDEX 1.
    CHECK sy-subrc EQ 0.
    IF <fs_empl>-firstname CA '0123456789'.
      APPEND VALUE #( %tky = <fs_empl>-%tky ) TO failed-empl.
      APPEND VALUE #(  %tky = <fs_empl>-%tky
                        %msg      = new_message( id       = 'ZEMPL_MSG'
                                                  number   = '000'
                                                  v1       = <fs_empl>-firstname
                                                  severity = if_abap_behv_message=>severity-error ) )
                                                  TO reported-Empl.
    ENDIF.
  ENDMETHOD.

What it does?

  1. Read the entity into an internal table lt_empl.
  2. Read the table and check if the name contains a number. If it does, an error message is raised.

Service Definition

Right click on Consumption view and create Service Definition

Expose the Consumption view as a service

Service Binding

Right click on Service Definition and create Service Binding

UI

When you click on preview in the previous step, you will see the below screen.

Now click on Create Button. Here you can enter ID, First Name, Last Name, Experience and Role.

Salary will be calculated based on Experience and Role. Active flag can be set once everything is ok.

Enter the values and hit Create

The following screen appears.

Now click back and you may see the record is populated

You may select the record and use Set Active button to activate it. Once done the screen looks like below