This is a test message to test the length of the message box.
Login
ABAP RAP Unmanaged
Created by Software-Heroes

RAP - Unmanaged (Local)

1129

In this article an example for the implementation of an unmanaged RAP object based on a locally available API.

In the last article we described how the unmanaged scenario for the ABAP RESTful Programming Model works in theory. In this article, we'll look at an implementation based on objects already implemented in the system that serve as local APIs for us.

 

Local API

To understand the local API here is a brief explanation of the objects and the implementations that are present. There is already a data model in the system which is based on the table ZBS_DMO_UN_DATA and has a corresponding DAO class (Data Access Object).

 

For the class there is the interface ZIF_BS_DEMO_RAP_DATA_HANDLER, which provides the CRUDQ methods and data types:

INTERFACE zif_bs_demo_rap_data_handler
  PUBLIC.

  TYPES:
    tt_r_key  TYPE RANGE OF zbs_dmo_un_data-gen_key,
    tt_r_text TYPE RANGE OF zbs_dmo_un_data-text,
    tt_r_date TYPE RANGE OF zbs_dmo_un_data-cdate,

    ts_data   TYPE zbs_dmo_un_data,
    tt_data   TYPE STANDARD TABLE OF ts_data WITH EMPTY KEY.

  METHODS:
    query
      IMPORTING it_r_key         TYPE tt_r_key  OPTIONAL
                it_r_text        TYPE tt_r_text OPTIONAL
                it_r_date        TYPE tt_r_date OPTIONAL
      RETURNING VALUE(rt_result) TYPE tt_data,

    read
      IMPORTING id_key           TYPE zbs_dmo_unmgnd-gen_key
      RETURNING VALUE(rs_result) TYPE ts_data,

    modify
      IMPORTING is_data          TYPE ts_data
      RETURNING VALUE(rd_result) TYPE abap_boolean,

    delete
      IMPORTING id_key           TYPE zbs_dmo_unmgnd-gen_key
      RETURNING VALUE(rd_result) TYPE abap_boolean.
ENDINTERFACE.

 

Then the following implementation of the class ZCL_BS_DEMO_RAP_DATA_HANDLER, which provides the implementation and logic:

CLASS zcl_bs_demo_rap_data_handler DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE.

  PUBLIC SECTION.
    INTERFACES zif_bs_demo_rap_data_handler.

    CLASS-METHODS create_instance
      RETURNING VALUE(ro_result) TYPE REF TO zif_bs_demo_rap_data_handler.
ENDCLASS.


CLASS zcl_bs_demo_rap_data_handler IMPLEMENTATION.
  METHOD create_instance.
    ro_result = NEW zcl_bs_demo_rap_data_handler( ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~delete.
    DELETE FROM zbs_dmo_un_data WHERE gen_key = @id_key.

    rd_result = xsdbool( sy-subrc = 0 ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~modify.
    DATA(ls_data) = is_data.
    GET TIME STAMP FIELD ls_data-last_changed.

    IF ls_data-gen_key IS INITIAL.
      TRY.
          ls_data-gen_key = cl_system_uuid=>create_uuid_x16_static( ).

          IF ls_data-cdate IS INITIAL.
            ls_data-cdate = cl_abap_context_info=>get_system_date( ).
          ENDIF.

        CATCH cx_uuid_error.
          rd_result = abap_false.
          RETURN.
      ENDTRY.

      INSERT zbs_dmo_un_data FROM @ls_data.

    ELSE.
      UPDATE zbs_dmo_un_data FROM @ls_data.

    ENDIF.

    rd_result = xsdbool( sy-subrc = 0 ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~query.
    SELECT FROM zbs_dmo_un_data
      FIELDS *
      WHERE gen_key IN @it_r_key
        AND text IN @it_r_text
        AND cdate IN @it_r_date
      INTO TABLE @rt_result.
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~read.
    SELECT SINGLE FROM zbs_dmo_un_data
      FIELDS *
      WHERE gen_key = @id_key
      INTO @rs_result.
  ENDMETHOD.
ENDCLASS.

 

The following picture emerges when we look at the existing objects:

 

 

Structure

The structure describes the rough structure of the objects and interdependencies. However, we will not go into the individual details here, but refer to the basics of RAP here if you want to understand the objects and structures.

 

Table

The structure of our unmanaged object is based on a "dummy" table that we use for modeling. This table is not 1:1 comparable to the data-holding table, but only includes the fields that we need for our RAP object:

@EndUserText.label : 'Unmanaged Data'
@AbapCatalog.tableCategory : #TRANSPARENT
define table zbs_dmo_unmgnd {
  key client  : abap.clnt not null;
  key gen_key : sysuuid_x16 not null;
  text        : abap.char(50);
  cdate       : abap.dats;
}

 

CDS Views

Accordingly, we set a root view on the table, which takes up the fields of the table and makes them available. We set the corresponding projection view on this, which then provides the interface to the application:

@EndUserText.label: 'Unmanaged Consumption'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_UNMANAGED_QUERY'
@UI: {
  headerInfo: {
    typeName: 'Unmanaged',
    typeNamePlural: 'Unmanaged',
    title: { value: 'Description' }
  }
}
define root view entity ZBS_C_DMOUnmanaged
  provider contract transactional_query
  as projection on ZBS_R_DMOUnmanaged
{
      @UI.facet      : [
        {
          id         : 'FacetData',
          label      : 'Data',
          type       : #FIELDGROUP_REFERENCE,
          targetQualifier: 'DATA'
        }
      ]

      @UI.lineItem: [{ position: 10, label: 'Key' }]
      @UI.fieldGroup: [{ position: 10, label: 'Key' }]
  key TableKey,
      @UI.selectionField: [{  position: 10 }]
      @UI.lineItem: [{ position: 20, label: 'Text' }]
      @UI.fieldGroup: [{ position: 20, label: 'Text', qualifier: 'DATA' }]
      Description,
      @UI.selectionField: [{  position: 20 }]
      @UI.lineItem: [{ position: 30, label: 'Created at' }]
      @UI.fieldGroup: [{ position: 20, label: 'Text', qualifier: 'DATA' }]
      CreationDate
}

 

In this case, we also mix the UI annotations into the view and do not create an extra metadata extension. Both scenarios are possible and lead to the result that our application receives a configured Fiori Elements interface.

 

Behavior definition

There are differences in the implementation of the behavior definition, as we already showed in the last article. The projection does not differ from the managed approach, so here is the definition at the lowest level:

unmanaged implementation in class zbp_bs_demo_unmanaged unique;
strict ( 1 );

define behavior for ZBS_R_DMOUnmanaged alias Unmanaged
lock master
authorization master ( instance )
{
  create;
  update;
  delete;

  field ( readonly ) TableKey;

  mapping for zbs_dmo_unmgnd
    {
      TableKey     = gen_key;
      Description  = text;
      CreationDate = cdate;
    }
}

 

The key is assigned automatically by the application, so it should only be displayed in the list and completely hidden on the object page.

 

More objects

Finally, we now create the service definition and the service binding in order to provide a UI OData service and to be able to carry out a first test with the app. We will create and consume an OData version 2 for the test.

 

Test Preview

If we now start the Fiori Elements Preview, then we get the following app in the preview, all important functions seem to be available for now.

 

When executing the selection using the "Go" button, however, no data is displayed. The creation of new data records does not work yet either.

 

Read

We now have to implement the reading of the data ourselves. The READ method would actually be suitable here in the behavior implementation, the class ZBP_BS_DEMO_UMMANAGED, but this is there for reading individual records and not for the query operation. To do this, we define the following annotation in the CDS of the projection to implement a reading class:

@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_UNMANAGED_QUERY'

 

The class is always called when the entity is requested via the OData service and returns the corresponding data. We already looked at this concept in the article on the custom entity. The following implementation for the class:

CLASS zcl_bs_demo_unmanaged_query DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_rap_query_provider.

  PRIVATE SECTION.
    METHODS get_data_from_request
      IMPORTING io_request       TYPE REF TO if_rap_query_request
      RETURNING VALUE(rt_result) TYPE zif_bs_demo_rap_data_handler=>tt_data
      RAISING   cx_rap_query_filter_no_range.
ENDCLASS.


CLASS zcl_bs_demo_unmanaged_query IMPLEMENTATION.
  METHOD if_rap_query_provider~select.
    DATA lt_output TYPE STANDARD TABLE OF ZBS_C_DMOUnmanaged.

    DATA(lt_database) = get_data_from_request( io_request ).
    lt_output = CORRESPONDING #( lt_database MAPPING TableKey = gen_key Description = text CreationDate = cdate ).

    IF io_request->is_data_requested( ).
      io_response->set_data( lt_output ).
    ENDIF.

    IF io_request->is_total_numb_of_rec_requested( ).
      io_response->set_total_number_of_records( lines( lt_output ) ).
    ENDIF.
  ENDMETHOD.


  METHOD get_data_from_request.
    DATA lt_r_key  TYPE zif_bs_demo_rap_data_handler=>tt_r_key.
    DATA lt_r_text TYPE zif_bs_demo_rap_data_handler=>tt_r_text.
    DATA lt_r_date TYPE zif_bs_demo_rap_data_handler=>tt_r_date.

    DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
    DATA(ld_offset) = io_request->get_paging( )->get_offset( ).
    DATA(ld_pagesize) = io_request->get_paging( )->get_page_size( ).

    LOOP AT lt_filter INTO DATA(ls_filter).
      CASE ls_filter-name.
        WHEN 'TABLEKEY'.
          lt_r_key = CORRESPONDING #( ls_filter-range ).
        WHEN 'DESCRIPTION'.
          lt_r_text = CORRESPONDING #( ls_filter-range ).
        WHEN 'CREATIONDATE'.
          lt_r_date = CORRESPONDING #( ls_filter-range ).
      ENDCASE.
    ENDLOOP.

    rt_result = zcl_bs_demo_rap_data_handler=>create_instance( )->query( it_r_key  = lt_r_key
                                                                         it_r_text = lt_r_text
                                                                         it_r_date = lt_r_date ).
  ENDMETHOD.
ENDCLASS.

 

For the implementation we unpack the request (IO_REQUEST) and get the filters from the frontend. We can then pass this to the API to get our data. Afterwards, these must be mapped from the database to the Core Data Service and then we transfer the data to the response (IO_RESPONSE). If we now click on the "GO" button in the application, we get the following result, the data is now read:

 

Buffer

So far, all methods have been implemented for the behavior implementation, but what is missing is the buffer that takes care of the data records before they are passed to the storage sequence. We have to develop this buffer ourselves, but there is also a best practice for implementation. We implement the buffer as a local class with static attributes, for our scenario the class would look like this:

CLASS lcl_data_buffer DEFINITION.
  PUBLIC SECTION.
    CLASS-DATA gt_create TYPE zif_bs_demo_rap_data_handler=>tt_data.
    CLASS-DATA gt_update TYPE zif_bs_demo_rap_data_handler=>tt_data.
    CLASS-DATA gt_delete TYPE zif_bs_demo_rap_data_handler=>tt_data.
ENDCLASS.

 

In principle, the class can also be implemented via an attribute with different characteristics, there are no limits to order and imagination. The attributes will take over the data at runtime and make it available to the storage sequence.

 

Create

Before we create the data, let's take a look at the content that we get when creating a data set in the method:

 

With ENTITIES we get a table whose structure contains the data for the system plus additional information such as the %CID and the %CONTROL structure. The CID is the unique key within the processing, so we can match other related records as well. The corresponding fields that can be edited in this process step are active in the CONTROL structure. We could also derive the assignment from this. The implementation of the Create method now looks like this:

METHOD create.
  INSERT LINES OF
         CORRESPONDING zif_bs_demo_rap_data_handler=>tt_data( entities MAPPING cdate = CreationDate text = Description )
         INTO TABLE lcl_data_buffer=>gt_create.
ENDMETHOD.

Basically, after mapping, we transfer the data to our buffer table for the new data records, there is nothing more to consider here.

 

Update

For updating the data we need to implement some more logic. We are handed back our entity, but only with the key fields and the changed data. This means that in the next step we first have to compare what has actually changed. The implementation would now look like this:

METHOD update.
  DATA(lo_data_handler) = zcl_bs_demo_rap_data_handler=>create_instance( ).

  LOOP AT entities INTO DATA(ls_entity).
    DATA(ls_original) = lo_data_handler->read( ls_entity-TableKey ).

    IF ls_entity-%control-Description = if_abap_behv=>mk-on.
      ls_original-text = ls_entity-Description.
    ENDIF.

    IF ls_entity-%control-CreationDate = if_abap_behv=>mk-on.
      ls_original-cdate = ls_entity-CreationDate.
    ENDIF.

    INSERT ls_original INTO TABLE lcl_data_buffer=>gt_update.
  ENDLOOP.
ENDMETHOD.

 

First of all we read the original data record using the key, then we check the CONTROL structure to see whether the field has been changed and then adopt the new content into the data. At the end we append the record to the buffer for follow-up processing.

 

Delete

The last step, deleting, is now implemented a little differently, since we don't get the whole entity, just the key. Basically we only have to adjust the mapping here and transfer the data to the buffer:

METHOD delete.
  INSERT LINES OF
         CORRESPONDING zif_bs_demo_rap_data_handler=>tt_data( keys MAPPING gen_key = TableKey )
         INTO TABLE lcl_data_buffer=>gt_delete.
ENDMETHOD.

 

 

Save

Finally, we can now implement the SAVE method. This is about reading the buffer and performing appropriate actions on the various entries:

METHOD save.
  DATA(lo_data_handler) = zcl_bs_demo_rap_data_handler=>create_instance( ).

  LOOP AT lcl_data_buffer=>gt_create INTO DATA(ls_create).
    lo_data_handler->modify( ls_create ).
  ENDLOOP.

  LOOP AT lcl_data_buffer=>gt_update INTO DATA(ls_update).
    lo_data_handler->modify( ls_update ).
  ENDLOOP.

  LOOP AT lcl_data_buffer=>gt_delete INTO DATA(ls_delete).
    lo_data_handler->delete( ls_delete-gen_key ).
  ENDLOOP.

  CLEAR: lcl_data_buffer=>gt_create, lcl_data_buffer=>gt_update, lcl_data_buffer=>gt_delete.
ENDMETHOD.

 

We instantiate the data handler and process all the records from the buffer passing them to our existing logic. Finally, we clear the buffer and we're done with that. The commit work for persisting the data records is set by the RAP framework, we don't have to worry about it here.

 

Full example

You can see the full example, all objects and changes via the commit on GitHub. The examples shown here are only excerpts from the complete source code and the objects that we have created.

 

Conclusion

In this first connection to a local API, we have introduced you to the unmanaged approach to a RAP object. This allows you to easily migrate most of your existing source code to RAP and thus build on the latest technologies without having to rework everything.


Included topics:
RAPBTPUnmanagedLocal
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


RAP - Deep Action in OData v4

Category - ABAP

In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.

05/24/2024

BTP - Connect On-Premise (Consumption Model v2)

Category - ABAP

In this article we want to provide another update on the connection of on-premise systems and how this is done with a communication arrangement.

12/15/2023

RAP - Show app count (Tile)

Category - ABAP

This example is about displaying a counter on the tile of a Fiori Elements application and how such a thing can be implemented.

10/06/2023

RAP - Generator (Fiori)

Category - ABAP

In this article we will look at the more complex RAP generator as a Fiori Elements application and what advantages you have with it.

09/15/2023

RAP - Generator (ADT)

Category - ABAP

Today, let's take a look at the RAP Generator, which is already integrated into ABAP Development Tools, and how you can use it to easily build new RAP apps.

09/08/2023