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 :

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.