Dans notre dernier article, nous avions vu comment créer un proxy pour consommer une API externe.
En ce basant sur cela, nous allons maintenant l’intégrer a un business object RAP.
Cette pratique peut permettre différentes choses, par exemple :
- Si nous souhaitons appeler une API externe et exposer les données dans une application Fiori déployée dans S/4 Hana ( On-Premise, Private & Public Cloud ), alors les appels à cette API échouerons en raison de la politique de même origine (Cross-Origin Resource Sharing – CORS)
En créant un objet RAP qui vient consommer cette API externe, alors nous n’avons plus le problème car l’appel se fait directement dans le backend et le résultat de cet appel est rendu disponible via un service OData developpé avec notre BO RAP.
PS : l’autre solution serait de déporter la création et le déploiement de l’application Fiori dans BTP, et de créer une destination pour consommer ce service.
La destination est à considérer comme un proxy internet disponible dans BTP pour permettre des appels avec des URL disposant de la même origine ( l’application Fiori deployée sur BTP appelle la destination vers l’API avec le même domaine => www.mon-domaine-btp.com ).
- Si nous souhaitons étendre un service OData ou une API, par exemple d’un système S/4 Hana, sans l’étendre directement dans le système source, alors il est possible de consommer cette API dans BTP et de conserver les données supplémentaire dans BTP ( Prochain article a venir ).
Ici nous allons donc consommer l’API Northwind et l’exposer dans un service OData via la création d’un objet RAP. Cet objet RAP nous permettra ensuite de créer une application Fiori pour visualiser ces données.
1/ Creation d’un proxy via Service Consumption Model
Voir https://abapeur.fr/fr/appeler-facilement-des-apis-en-abap/
2/ Creation d’un Custom CDS Entity
- Créer un custom CDS Entity ( ZTEST_CUST_NORTHWIND ) qui contiendra les données que l’on souhaite exposer dans notre application Fiori.
- Comme, c’est un custom CDS Entity, alors nous devons implémenter le query de manière unmanaged dans une classe ( ZTEST_CRT_PROXY ). Cette classe aura donc la responsabilité de remplir les données de la CDS.
- Nous ajoutons également des annotations pour l’affichage dans l’application Fiori ( Les annotations UI peuvent etre déplacée vers un Metadata Extension pour respecter le principe « separation of concerns ». Par simplicité, ici ce n’est pas fait )
@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/ Implémenter la classe ZTEST_CRT_PROXY
Pour cela il faut que cette classe implémente la méthode select l’interface if_rap_query_provider.
- Voici la signature de la classe
CLASS ztest_crt_proxy DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES : if_rap_query_provider.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
- Voici l’implémentation de la méthode if_rap_query_provider~select
1/ Créer la destination et l’appel a l’API via le proxy.
Ce code permet de récupérer toutes les données de l’entity PRODUCTS.
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/ Remplir les données dans le CDS Custom Entity
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 : Si vous voulez également permettre a l’utilisateur de filtrer, trier, limiter le nombre des données, etc… Alors il faut également mettre en place toute la logique nécessaire au sein de la méthode.
1/ Récupérer les actions demandées par l’utilisateur
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/ Une fois que vous disposez de ces élements, il faut adapter l’appel API et le traitement des données reçues en fonction de ces derniers. (Nous verrons un exemple dans mon prochain article).
Code complet
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/ Exposer le service OData
Créer le Service Definition
@EndUserText.label: 'Northwind test'
define service ZTEST_NORTHWIND {
expose ZTEST_CUST_NORTHWIND;
}
Et le service binding
Et prévisualiser l’application : nous recevons et exposons dans l’application Fiori les élements de l’API Northwind
Conclusion
Nous avons vu comment consommer une API externe, l’exposer dans un objet RAP, l’utiliser dans une application Fiori ainsi que les problématiques auxquelles cela répond.
Prochainement nous verrons comment étendre une API en se basant sur le meme principe.