Les Custom Entities permettent d’intégrer des sources de données externes dans des applications basées sur RAP. Contrairement aux CDS Views qui sont basées sur des tables de base de données, les Custom Entities sont remplies de données par du code ABAP.
Cas d’usage pour Custom Entities
Les Custom Entities sont idéales pour :
- API externes - Services REST, services Web SOAP, services OData
- Systèmes legacy - Modules fonction, BAPIs, Remote Function Calls
- Données calculées - Agrégations complexes non possibles en CDS
- Sources non-HANA - Fichiers, bases de données externes via ADBC
- Données virtuelles - Données générées uniquement au runtime
Différence avec CDS Views
| Aspect | CDS View | Custom Entity |
|---|---|---|
| Source de données | Tables de base de données | Code ABAP |
| SQL Pushdown | Oui | Non |
| Modélisation données | Déclarative | Impérative |
| Performance | Optimisée par HANA | Dépend de l’implémentation |
| Flexibilité | Limitée | Complète |
| Effort de maintenance | Faible | Plus élevé |
Définition CDS Custom Entity
Une Custom Entity est définie en CDS, mais les données proviennent d’une classe ABAP :
@EndUserText.label: 'Produits externes"@ObjectModel.query.implementedBy: 'ABAP:ZCL_PRODUCT_QUERY"define custom entity ZI_ExternalProduct{ key ProductId : abap.char(10); ProductName : abap.char(100); Category : abap.char(40); Price : abap.dec(13,2); Currency : abap.cuky; Stock : abap.int4; Supplier : abap.char(80); LastUpdated : abap.dats;}Annotations importantes
| Annotation | Description |
|---|---|
@ObjectModel.query.implementedBy | Référence la classe d’implémentation de requête |
La classe doit être spécifiée avec le préfixe ABAP:.
Query Implementation Class
La classe Query implémente l’interface IF_RAP_QUERY_PROVIDER :
CLASS zcl_product_query DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_rap_query_provider.ENDCLASS.
CLASS zcl_product_query IMPLEMENTATION. METHOD if_rap_query_provider~select. " 1. Vérifier si des données sont demandées IF io_request->is_data_requested( ).
" 2. Récupérer les informations de pagination DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ).
" 3. Récupérer le tri DATA(lt_sort) = io_request->get_sort_elements( ).
" 4. Récupérer les filtres DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" 5. Charger les données depuis la source externe DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct.
" Ici : Appel API ou autre source de données lt_products = load_products_from_api( iv_offset = lv_offset iv_page_size = lv_page_size it_filter = lt_filter it_sort = lt_sort ).
" 6. Transmettre les données à la response io_response->set_data( lt_products ). ENDIF.
" 7. Count demandé ? IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_products ) ). ENDIF. ENDMETHOD.ENDCLASS.Implémenter le Paging
Pour de grandes quantités de données, le paging est essentiel :
METHOD if_rap_query_provider~select. " Récupérer les paramètres de pagination DATA(lo_paging) = io_request->get_paging( ). DATA(lv_offset) = lo_paging->get_offset( ). DATA(lv_page_size) = lo_paging->get_page_size( ).
" Limiter la taille maximale de page IF lv_page_size > 1000 OR lv_page_size <= 0. lv_page_size = 100. " Valeur par défaut ENDIF.
" Charger les données (par ex. depuis API avec pagination) DATA lt_all_products TYPE STANDARD TABLE OF zi_externalproduct. lt_all_products = call_external_api( ).
" Appliquer le paging DATA lt_paged_products TYPE STANDARD TABLE OF zi_externalproduct. DATA(lv_from) = lv_offset + 1. DATA(lv_to) = lv_offset + lv_page_size.
LOOP AT lt_all_products INTO DATA(ls_product) FROM lv_from TO lv_to. APPEND ls_product TO lt_paged_products. ENDLOOP.
" Définir la response io_response->set_data( lt_paged_products ).
IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_all_products ) ). ENDIF.ENDMETHOD.Implémenter le tri
Le tri permet à l’utilisateur une présentation flexible :
METHOD if_rap_query_provider~select. " Charger les données DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct. lt_products = load_products( ).
" Récupérer et appliquer le tri DATA(lt_sort) = io_request->get_sort_elements( ).
LOOP AT lt_sort INTO DATA(ls_sort). CASE ls_sort-element_name. WHEN 'PRODUCTNAME'. IF ls_sort-descending = abap_true. SORT lt_products BY ProductName DESCENDING. ELSE. SORT lt_products BY ProductName ASCENDING. ENDIF.
WHEN 'PRICE'. IF ls_sort-descending = abap_true. SORT lt_products BY Price DESCENDING. ELSE. SORT lt_products BY Price ASCENDING. ENDIF.
WHEN 'CATEGORY'. IF ls_sort-descending = abap_true. SORT lt_products BY Category DESCENDING. ELSE. SORT lt_products BY Category ASCENDING. ENDIF. ENDCASE. ENDLOOP.
io_response->set_data( lt_products ).ENDMETHOD.Tri dynamique avec instruction SORT
Pour une solution plus élégante, vous pouvez utiliser le tri dynamique :
METHOD apply_sorting. DATA(lt_sort) = io_request->get_sort_elements( ).
" Construire la chaîne de tri DATA(lv_sort_string) = ``.
LOOP AT lt_sort INTO DATA(ls_sort). IF lv_sort_string IS NOT INITIAL. lv_sort_string = lv_sort_string && ` `. ENDIF.
lv_sort_string = lv_sort_string && ls_sort-element_name.
IF ls_sort-descending = abap_true. lv_sort_string = lv_sort_string && ` DESCENDING`. ELSE. lv_sort_string = lv_sort_string && ` ASCENDING`. ENDIF. ENDLOOP.
" Tri dynamique IF lv_sort_string IS NOT INITIAL. SORT ct_data BY (lv_sort_string). ENDIF.ENDMETHOD.Implémenter les filtres
Les filtres permettent des requêtes de données ciblées :
METHOD if_rap_query_provider~select. " Charger les données DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct. lt_products = load_all_products( ).
" Récupérer les filtres TRY. DATA(lt_filter_cond) = io_request->get_filter( )->get_as_ranges( ). CATCH cx_rap_query_filter_no_range. " Pas de conversion de filtre possible ENDTRY.
" Appliquer les filtres LOOP AT lt_filter_cond INTO DATA(ls_filter). CASE ls_filter-name. WHEN 'CATEGORY'. DELETE lt_products WHERE Category NOT IN ls_filter-range.
WHEN 'PRICE'. DELETE lt_products WHERE Price NOT IN ls_filter-range.
WHEN 'PRODUCTID'. DELETE lt_products WHERE ProductId NOT IN ls_filter-range. ENDCASE. ENDLOOP.
io_response->set_data( lt_products ).ENDMETHOD.Exemple : Connexion à une API REST externe
Un exemple complet pour la connexion d’une API produits externe :
1. Définition Custom Entity
@EndUserText.label: 'API Produits Externes"@ObjectModel.query.implementedBy: 'ABAP:ZCL_EXT_PRODUCT_QUERY"define custom entity ZI_ExtProductAPI{ key ProductUUID : sysuuid_x16; ProductId : abap.char(10); ProductName : abap.char(100); Description : abap.char(255); Category : abap.char(40); Price : abap.dec(13,2); Currency : abap.cuky; InStock : abap_boolean; ImageUrl : abap.char(255);}2. Query Implementation avec appel HTTP
CLASS zcl_ext_product_query DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_rap_query_provider.
PRIVATE SECTION. TYPES: BEGIN OF ty_api_product, id TYPE string, name TYPE string, description TYPE string, category TYPE string, price TYPE decfloat34, currency TYPE string, in_stock TYPE abap_bool, image_url TYPE string, END OF ty_api_product, tt_api_products TYPE STANDARD TABLE OF ty_api_product WITH EMPTY KEY.
METHODS call_product_api RETURNING VALUE(rt_products) TYPE tt_api_products RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS map_to_entity IMPORTING it_api_products TYPE tt_api_products RETURNING VALUE(rt_result) TYPE STANDARD TABLE OF zi_extproductapi.ENDCLASS.
CLASS zcl_ext_product_query IMPLEMENTATION. METHOD if_rap_query_provider~select. IF io_request->is_data_requested( ). TRY. " Appeler l'API DATA(lt_api_products) = call_product_api( ).
" Convertir au format Entity DATA(lt_products) = map_to_entity( lt_api_products ).
" Appliquer les filtres DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ). LOOP AT lt_filter INTO DATA(ls_filter). CASE ls_filter-name. WHEN 'CATEGORY'. DELETE lt_products WHERE Category NOT IN ls_filter-range. ENDCASE. ENDLOOP.
" Appliquer le paging DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ).
IF lv_page_size > 0. DATA(lv_from) = lv_offset + 1. DATA(lv_to) = lv_offset + lv_page_size.
DATA lt_paged TYPE STANDARD TABLE OF zi_extproductapi. LOOP AT lt_products INTO DATA(ls_prod) FROM lv_from TO lv_to. APPEND ls_prod TO lt_paged. ENDLOOP.
io_response->set_data( lt_paged ). ELSE. io_response->set_data( lt_products ). ENDIF.
" Total Count IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_products ) ). ENDIF.
CATCH cx_http_dest_provider_error cx_web_http_client_error INTO DATA(lx_error). " Gestion des erreurs RAISE EXCEPTION TYPE cx_rap_query_provider EXPORTING textid = cx_rap_query_provider=>query_failed previous = lx_error. ENDTRY. ENDIF. ENDMETHOD.
METHOD call_product_api. " Destination HTTP depuis le Destination Service DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'PRODUCT_API" i_authn_mode = if_a4c_cp_service=>service_specific ).
" Créer le client HTTP DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
" Exécuter la requête DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( '/api/v1/products' ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ). DATA(lv_json) = lo_response->get_text( ).
" Parser le JSON /ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = rt_products ).
lo_client->close( ). ENDMETHOD.
METHOD map_to_entity. LOOP AT it_api_products INTO DATA(ls_api). APPEND VALUE #( ProductUUID = cl_system_uuid=>create_uuid_x16_static( ) ProductId = ls_api-id ProductName = ls_api-name Description = ls_api-description Category = ls_api-category Price = ls_api-price Currency = ls_api-currency InStock = ls_api-in_stock ImageUrl = ls_api-image_url ) TO rt_result. ENDLOOP. ENDMETHOD.ENDCLASS.Intégrer Custom Entity dans RAP Business Object
Les Custom Entities peuvent aussi être intégrées dans un BO RAP :
Behavior Definition pour Custom Entity
unmanaged implementation in class zbp_i_extproduct unique;
define behavior for ZI_ExtProductAPI alias Product{ // Lecture seule - pas d'opérations CUD // Les actions peuvent quand même être définies action refreshFromAPI;}Service Definition et Binding
@EndUserText.label: 'Service Produits Externes"define service ZUI_ExtProducts { expose ZI_ExtProductAPI as Products;}Gestion des erreurs
Une gestion robuste des erreurs est importante pour les sources externes :
METHOD if_rap_query_provider~select. TRY. " Charger les données avec timeout DATA(lt_products) = load_external_data( ). io_response->set_data( lt_products ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest). " Destination introuvable ou erreur de configuration RAISE EXCEPTION TYPE cx_rap_query_provider EXPORTING textid = cx_rap_query_provider=>query_failed previous = lx_dest.
CATCH cx_web_http_client_error INTO DATA(lx_http). " Erreur HTTP (Timeout, réseau, etc.) RAISE EXCEPTION TYPE cx_rap_query_provider EXPORTING textid = cx_rap_query_provider=>query_failed previous = lx_http.
CATCH cx_sy_conversion_error INTO DATA(lx_conv). " Erreur de parsing JSON RAISE EXCEPTION TYPE cx_rap_query_provider EXPORTING textid = cx_rap_query_provider=>query_failed previous = lx_conv. ENDTRY.ENDMETHOD.Conseils de performance
- Caching - Mettre en cache les données fréquemment consultées
- Lazy Loading - Charger uniquement les données nécessaires
- Déléguer la pagination à l’API - Si l’API externe supporte le paging
- Appels asynchrones - Pour les requêtes de longue durée
- Connection Pooling - Réutiliser les connexions HTTP
" Exemple : Caching simpleCLASS zcl_product_query DEFINITION. PRIVATE SECTION. CLASS-DATA: gt_cache TYPE STANDARD TABLE OF zi_extproductapi, gv_cache_timestamp TYPE timestamp.
METHODS get_cached_data RETURNING VALUE(rt_data) TYPE STANDARD TABLE OF zi_extproductapi.ENDCLASS.
CLASS zcl_product_query IMPLEMENTATION. METHOD get_cached_data. " Validité du cache : 5 minutes DATA(lv_now) = utclong_current( ). DATA(lv_cache_age) = cl_abap_tstmp=>subtract( tstmp1 = lv_now tstmp2 = gv_cache_timestamp ).
IF gv_cache_timestamp IS INITIAL OR lv_cache_age > 300. " Recharger le cache gt_cache = call_external_api( ). gv_cache_timestamp = lv_now. ENDIF.
rt_data = gt_cache. ENDMETHOD.ENDCLASS.Bonnes pratiques
- Utiliser l’interface - Implémenter complètement
IF_RAP_QUERY_PROVIDER - Gestion des erreurs - Les sources externes peuvent échouer
- Supporter le paging - Les grandes quantités de données nécessitent la pagination
- Utiliser les filtres - Filtrer les données le plus tôt possible (à l’API si possible)
- Logging - Protocoler les appels externes pour le debugging
- Définir des timeouts - Éviter les temps d’attente longs
- Tester - Mocker les dépendances externes pour les tests unitaires
Sujets connexes
- RAP Grundlagen - Introduction au modèle de programmation RESTful ABAP
- Destination Service - Connecter en toute sécurité les systèmes externes
- CDS Table Functions avec AMDP - Alternative pour calculs complexes