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:
- If we wish to call an external API and expose the data in a Fiori application deployed in S/4 Hana ( On-Premise, Private & Public Cloud ), then calls to this API will fail due to the same origin policy (Cross-Origin Resource Sharing – CORS).
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.