Create a RAP Business Object with an external API

Treatment in progress…
You're done! You are in the list.

In our last article, we saw how to create a proxy to consume an external API.
Building on this, we’re now going to integrate it into a RAP business object.

This practice can enable a number of different things, for example:

By creating a RAP object that consumes this external API, we no longer have the problem, as the call is made directly in the backend and the result of this call is made available via an OData service developed with our RAP BO.

PS: the other solution would be to deport the creation and deployment of the Fiori application to BTP, and create a destination to consume this service.
The destination is to be considered as an Internet proxy available in BTP to enable calls with URLs having the same origin (the Fiori application deployed on BTP calls the destination to the API with the same domain => www.my-btp-domain.com ).

  • If we wish to extend an OData service or API, for example from an S/4 Hana system, without extending it directly in the source system, then it is possible to consume this API in BTP and keep the additional data in BTP ( Next article to come ).

Here, we’ll consume the Northwind API and expose it in an OData service by creating a RAP object. This RAP object will then enable us to create a Fiori application to visualize this data.

1/ Creating a proxy via the Service Consumption Model

See https://abapeur.fr/en/easily-call-apis-in-abap/

2/ Create a Custom CDS Entity

  • Create a custom CDS Entity ( ZTEST_CUST_NORTHWIND ) that will contain the data we wish to expose in our Fiori application.
  • As this is a custom CDS Entity, we need to implement the query in an unmanaged way in a class ( ZTEST_CRT_PROXY ). This class will then be responsible for populating the CDS data.
  • We also add annotations for display in the Fiori application ( UI annotations can be moved to a Metadata Extension to respect the “separation of concerns” principle. For simplicity’s sake, this is not done here )
@EndUserText.label: 'test custom CDS Northwind'
@ObjectModel.query.implementedBy: 'ABAP:ZTEST_CRT_PROXY'
define custom entity ZTEST_CUST_NORTHWIND
{
@UI.lineItem: [{ position: 10 }]
@EndUserText.label: 'Product ID'
  key   product_id : int4;
@UI.lineItem: [{ position: 20 }]
@EndUserText.label: 'Product Name'
        product_name : abap.char( 100 );
@UI.lineItem: [{ position: 30 }]        
@EndUserText.label: 'Supplier ID'
        supplier_id : int4;
@UI.lineItem: [{ position: 40 }]
@EndUserText.label: 'Category ID'
        category_id : int4;
@UI.lineItem: [{ position: 50 }]
@EndUserText.label: 'Quantity Per Unit'
        quantity_per_unit : abap.char( 100 );
@UI.lineItem: [{ position: 60 }]
@EndUserText.label: 'Unit Price'
        unit_price : abap.dec( 16, 0 );
@UI.lineItem: [{ position: 70}]
@EndUserText.label: 'Units In Stock'
        units_in_stock : int2;
@UI.lineItem: [{ position: 80 }]
@EndUserText.label: 'Units On Order'
        units_on_order : int2;
  
}

3/ Implemente the ZTEST_CRT_PROXY class

To do this, the class must implement the select method of the if_rap_query_provider interface.

  • Here’s the class signature
CLASS ztest_crt_proxy DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

  INTERFACES : if_rap_query_provider.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.
  • Here’s how to implement the if_rap_query_provider~select method

1/ Create the destination and call the API via the proxy.

This code retrieves all data from the PRODUCTS entity.

    TRY.
        " Create http client
        DATA(lo_destination) = cl_http_destination_provider=>create_by_url( 'https://services.odata.org' ).
        lo_http_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
        lo_client_proxy = /iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
          EXPORTING
             is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                                 proxy_model_id      = 'ZTEST_NORTHWIND'
                                                 proxy_model_version = '0001' )
            io_http_client             = lo_http_client
            iv_relative_service_root   = '/v2/northwind/northwind.svc' ).

        ASSERT lo_http_client IS BOUND.

        DATA: lo_read_list_request    TYPE REF TO /iwbep/if_cp_request_read_list,
              lo_entity_list_resource TYPE REF TO /iwbep/if_cp_resource_list,
              lo_read_list_response   TYPE REF TO /iwbep/if_cp_response_read_lst.

"Set the entity requested to PRODUCTS
        lo_entity_list_resource = lo_client_proxy->create_resource_for_entity_set( 'PRODUCTS' ). 

"Specify that you come to read the list of data present in the entity PRODUCTS
        lo_read_list_request = lo_entity_list_resource->create_request_for_read( ). 

"Execute request
        lo_read_list_response = lo_read_list_request->execute( ).

"data returned via the API are in lt_business_data
        lo_read_list_response->get_business_data( IMPORTING et_business_data = lt_business_data ). 

      CATCH /iwbep/cx_cp_remote INTO DATA(lx_remote).

      CATCH /iwbep/cx_gateway INTO DATA(lx_gateway).

      CATCH cx_web_http_client_error INTO DATA(lx_web_http_client_error).

        RAISE SHORTDUMP lx_web_http_client_error.

    ENDTRY.

2/ Fill in the data in the Custom Entity CDS

    io_response->set_total_number_of_records( lines( lt_business_data  ) ).

    TYPES : tty_products TYPE STANDARD TABLE OF ztest_cust_northwind WITH EMPTY KEY.


    DATA(lt_data) = VALUE tty_products(
      FOR ls_data IN lt_business_data
      ( product_id = ls_data-product_id
        category_id = ls_data-category_id
        product_name = ls_data-product_name
        quantity_per_unit = ls_data-quantity_per_unit
        supplier_id = ls_data-supplier_id
        unit_price = ls_data-unit_price
        units_on_order = ls_data-units_on_order )
    ).
    io_response->set_data( lt_data ).

IMPORTANT: If you also want to allow the user to filter, sort, limit the amount of data, etc., then you also need to implement all the necessary logic within the method.

1/ Retrieve actions requested by the user

    DATA(top)     = io_request->get_paging( )->get_page_size( ). "Paging
    DATA(skip)    = io_request->get_paging( )->get_offset( ). "Offset 
    DATA(requested_fields)  = io_request->get_requested_elements( ). "Fields requested
    DATA(sort_order)    = io_request->get_sort_elements( ). "Sort
    data(filter) = io_request->get_filter( )."Filters

2/ Once you have these elements, you need to adapt the API call and the processing of the data received to them. (We’ll look at an example in my next article).

Complete code

CLASS ztest_crt_proxy DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

  INTERFACES : if_rap_query_provider.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS ztest_crt_proxy IMPLEMENTATION.

  METHOD if_rap_query_provider~select.

    DATA(top)     = io_request->get_paging( )->get_page_size( ).
    DATA(skip)    = io_request->get_paging( )->get_offset( ).
    DATA(requested_fields)  = io_request->get_requested_elements( ).
    DATA(sort_order)    = io_request->get_sort_elements( ).
    data(filter) = io_request->get_filter( ).

    DATA:
      ls_entity_key    TYPE ztest_northwind=>tys_alphabetical_list_of_produ,
      ls_business_data TYPE ztest_northwind=>tys_alphabetical_list_of_produ,
      lt_business_data TYPE TABLE OF ztest_northwind=>tys_alphabetical_list_of_produ,
      lo_http_client   TYPE REF TO if_web_http_client,
      lo_client_proxy  TYPE REF TO /iwbep/if_cp_client_proxy.



    TRY.
        " Create http client
        DATA(lo_destination) = cl_http_destination_provider=>create_by_url( 'https://services.odata.org' ).
        lo_http_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
        lo_client_proxy = /iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
          EXPORTING
             is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                                 proxy_model_id      = 'ZTEST_NORTHWIND'
                                                 proxy_model_version = '0001' )
            io_http_client             = lo_http_client
            iv_relative_service_root   = '/v2/northwind/northwind.svc' ).

        ASSERT lo_http_client IS BOUND.

        DATA: lo_read_list_request    TYPE REF TO /iwbep/if_cp_request_read_list,
              lo_entity_list_resource TYPE REF TO /iwbep/if_cp_resource_list,
              lo_read_list_response   TYPE REF TO /iwbep/if_cp_response_read_lst.

        lo_entity_list_resource = lo_client_proxy->create_resource_for_entity_set( 'PRODUCTS' ).
        lo_read_list_request = lo_entity_list_resource->create_request_for_read( ).
        lo_read_list_response = lo_read_list_request->execute( ).
        lo_read_list_response->get_business_data( IMPORTING et_business_data = lt_business_data ).

      CATCH /iwbep/cx_cp_remote INTO DATA(lx_remote).

      CATCH /iwbep/cx_gateway INTO DATA(lx_gateway).

      CATCH cx_web_http_client_error INTO DATA(lx_web_http_client_error).

        RAISE SHORTDUMP lx_web_http_client_error.

    ENDTRY.

    io_response->set_total_number_of_records( lines( lt_business_data  ) ).

    TYPES : tty_products TYPE STANDARD TABLE OF ztest_cust_northwind WITH EMPTY KEY.


    DATA(lt_data) = VALUE tty_products(
      FOR ls_data IN lt_business_data
      ( product_id = ls_data-product_id
        category_id = ls_data-category_id
        product_name = ls_data-product_name
        quantity_per_unit = ls_data-quantity_per_unit
        supplier_id = ls_data-supplier_id
        unit_price = ls_data-unit_price
        units_on_order = ls_data-units_on_order )
    ).
    io_response->set_data( lt_data ).

  ENDMETHOD.

ENDCLASS.

4/ Expose the OData service

Create Service Definition

@EndUserText.label: 'Northwind test'
define service ZTEST_NORTHWIND {
  expose ZTEST_CUST_NORTHWIND;
}

And service binding

And preview the application: we receive and expose the Northwind API elements in the Fiori application.

Conclusion

We’ve seen how to consume an external API, expose it in a RAP object and use it in a Fiori application, as well as the issues it addresses.

Next we’ll look at how to extend an API using the same principle.