RAP Abstract Entities : Requetes personnalisees flexibles sans base de donnees

Catégorie
RAP
Publié
Auteur
Johannes

Les Abstract Entities sont un type CDS special dans RAP qui ne represente pas de table de base de donnees. Elles servent de structure de donnees pour les parametres d’Action, les resultats de Function et les Custom Queries avec une flexibilite maximale.

Que sont les Abstract Entities ?

Les Abstract Entities sont definies avec define abstract entity et n’existent que comme definition de metadonnees. Elles n’ont pas de source de donnees persistante et sont alimentees en donnees par du code ABAP.

@EndUserText.label: 'Donnees meteo externes"
define abstract entity ZA_WeatherData
{
City : abap.char(40);
Temperature : abap.dec(5,2);
Humidity : abap.int2;
Conditions : abap.char(50);
Timestamp : abap.utclong;
}

Difference avec les autres types d’entites

AspectCDS ViewCustom EntityAbstract Entity
Definitiondefine view entitydefine custom entitydefine abstract entity
Source de donneesTables de base de donneesClasse Query ProviderAucune (structure uniquement)
Exposable via ODataOuiOuiUniquement via Actions/Functions
Cas d’utilisationModelisation de donneesSources de donnees externesParametres, Resultats
PersistanceBase de donneesAucuneAucune

Scenarios d’utilisation

1. Parametres d’Action

Les Abstract Entities definissent des structures d’entree complexes pour les RAP Actions :

@EndUserText.label: 'Parametres de reservation"
define abstract entity ZA_BookingParameters
{
BookingDate : abap.dats;
CustomerID : abap.numc(10);
ProductID : abap.char(18);
Quantity : abap.int4;
DiscountCode : abap.char(10);
PaymentMethod : abap.char(20);
}

Utilisation dans la Behavior Definition :

action createBooking parameter ZA_BookingParameters result [1] $self;

2. Resultats de Function

Pour des valeurs de retour complexes des RAP Functions :

@EndUserText.label: 'Resultat de calcul de prix"
define abstract entity ZA_PriceCalculationResult
{
NetPrice : abap.dec(15,2);
TaxAmount : abap.dec(15,2);
GrossPrice : abap.dec(15,2);
Currency : abap.cuky;
DiscountApplied : abap_boolean;
DiscountPercent : abap.dec(5,2);
ValidUntil : abap.dats;
}

3. Custom Queries sans table de base de donnees

Pour des donnees provenant de systemes externes ou des valeurs calculees :

@EndUserText.label: 'Taux de change"
@ObjectModel.query.implementedBy: 'ABAP:ZCL_EXCHANGE_RATE_QUERY"
define custom entity ZI_ExchangeRate
{
key SourceCurrency : abap.cuky;
key TargetCurrency : abap.cuky;
ExchangeRate : abap.dec(15,5);
RateDate : abap.dats;
Provider : abap.char(20);
}

Custom Query avec if_rap_query_provider

La classe d’implementation de Query alimente l’entite en donnees :

CLASS zcl_exchange_rate_query DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
PRIVATE SECTION.
METHODS fetch_rates_from_api
RETURNING VALUE(rt_rates) TYPE zt_exchange_rates.
ENDCLASS.
CLASS zcl_exchange_rate_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
" 1. Verifier si des donnees sont demandees
IF io_request->is_data_requested( ) = abap_false.
RETURN.
ENDIF.
" 2. Recuperer le filtre
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" 3. Recuperer la pagination
DATA(lv_offset) = io_request->get_paging( )->get_offset( ).
DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ).
" 4. Recuperer les donnees depuis l'API externe
DATA(lt_rates) = fetch_rates_from_api( ).
" 5. Appliquer le filtre
DATA lt_result TYPE STANDARD TABLE OF zi_exchangerate.
LOOP AT lt_rates INTO DATA(ls_rate).
" Verifier les criteres de filtre
IF lt_filter IS NOT INITIAL.
DATA(lv_match) = abap_true.
LOOP AT lt_filter INTO DATA(ls_filter).
CASE ls_filter-name.
WHEN 'SOURCECURRENCY'.
IF NOT ls_rate-source_currency IN ls_filter-range.
lv_match = abap_false.
ENDIF.
WHEN 'TARGETCURRENCY'.
IF NOT ls_rate-target_currency IN ls_filter-range.
lv_match = abap_false.
ENDIF.
ENDCASE.
ENDLOOP.
IF lv_match = abap_false.
CONTINUE.
ENDIF.
ENDIF.
APPEND VALUE #(
SourceCurrency = ls_rate-source_currency
TargetCurrency = ls_rate-target_currency
ExchangeRate = ls_rate-rate
RateDate = ls_rate-rate_date
Provider = ls_rate-provider
) TO lt_result.
ENDLOOP.
" 6. Nombre total pour la pagination
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines( lt_result ) ).
ENDIF.
" 7. Appliquer la pagination
IF lv_page_size > 0.
DATA(lv_to) = lv_offset + lv_page_size.
IF lv_to > lines( lt_result ).
lv_to = lines( lt_result ).
ENDIF.
lt_result = VALUE #( FOR i = lv_offset + 1 WHILE i <= lv_to
( lt_result[ i ] ) ).
ENDIF.
" 8. Retourner les donnees
io_response->set_data( lt_result ).
ENDMETHOD.
METHOD fetch_rates_from_api.
" Creer le client HTTP
TRY.
DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'Z_EXCHANGE_RATE_API"
service_id = 'Z_EXCHANGE_RATE_OUTBOUND"
).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( '/api/v1/rates' ).
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_rates
).
CATCH cx_root INTO DATA(lx_error).
" Logger l'erreur, retourner une table vide
" Ou : RAISE SHORTDUMP
ENDTRY.
ENDMETHOD.
ENDCLASS.

Integration avec des APIs externes

Connexion API REST

METHOD fetch_weather_data.
TRY.
DATA(lo_dest) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'Z_WEATHER_API"
).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_dest ).
DATA(lo_request) = lo_client->get_http_request( ).
" Definir les parametres de requete
lo_request->set_uri_path( |/weather?city={ iv_city }&units=metric| ).
lo_request->set_header_field(
i_name = 'Accept"
i_value = 'application/json"
).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code = 200.
DATA(lv_json) = lo_response->get_text( ).
parse_weather_json( EXPORTING iv_json = lv_json
IMPORTING es_weather = rs_weather ).
ENDIF.
CATCH cx_http_dest_provider_error
cx_web_http_client_error INTO DATA(lx_error).
" Gestion des erreurs
ENDTRY.
ENDMETHOD.

Connexion RFC

METHOD fetch_from_legacy_system.
DATA: lv_message TYPE c LENGTH 200.
TRY.
DATA(lo_dest) = cl_rfc_destination_provider=>create_by_cloud_destination(
i_name = 'SAP_ECC_SYSTEM"
).
CALL FUNCTION 'Z_GET_PRODUCT_STOCK"
DESTINATION lo_dest->get_destination_name( )
EXPORTING
iv_material = iv_material
IMPORTING
ev_stock = rv_stock
EXCEPTIONS
communication_failure = 1 MESSAGE lv_message
system_failure = 2 MESSAGE lv_message
OTHERS = 3.
IF sy-subrc <> 0.
" Logger l'erreur
ENDIF.
CATCH cx_rfc_dest_provider_error INTO DATA(lx_error).
" Destination non trouvee
ENDTRY.
ENDMETHOD.

Exemple complexe : Donnees de tableau de bord

Un tableau de bord qui agrege des donnees provenant de plusieurs sources :

Definition de l’Abstract Entity

@EndUserText.label: 'KPIs du tableau de bord"
@ObjectModel.query.implementedBy: 'ABAP:ZCL_DASHBOARD_QUERY"
define custom entity ZI_DashboardKPI
{
key KPIId : abap.char(20);
KPIName : abap.char(60);
KPIValue : abap.dec(15,2);
KPIUnit : abap.char(10);
Trend : abap.int1; // -1, 0, 1
TrendPercent : abap.dec(5,2);
ComparisonValue : abap.dec(15,2);
Status : abap.char(10); // Good, Warning, Critical
Category : abap.char(30);
LastUpdated : abap.utclong;
}

Implementation de la Query

CLASS zcl_dashboard_query DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
PRIVATE SECTION.
METHODS get_sales_kpis RETURNING VALUE(rt_kpis) TYPE zt_dashboard_kpis.
METHODS get_inventory_kpis RETURNING VALUE(rt_kpis) TYPE zt_dashboard_kpis.
METHODS get_customer_kpis RETURNING VALUE(rt_kpis) TYPE zt_dashboard_kpis.
METHODS calculate_trend
IMPORTING iv_current TYPE decfloat34
iv_previous TYPE decfloat34
EXPORTING ev_trend TYPE i
ev_percent TYPE decfloat34.
ENDCLASS.
CLASS zcl_dashboard_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
IF io_request->is_data_requested( ) = abap_false.
RETURN.
ENDIF.
" Agreger les donnees de differentes sources
DATA lt_kpis TYPE STANDARD TABLE OF zi_dashboardkpi.
" KPIs ventes depuis CDS View
APPEND LINES OF get_sales_kpis( ) TO lt_kpis.
" KPIs inventaire depuis systeme legacy via RFC
APPEND LINES OF get_inventory_kpis( ) TO lt_kpis.
" KPIs clients depuis API externe
APPEND LINES OF get_customer_kpis( ) TO lt_kpis.
" Appliquer le filtre
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
IF lt_filter IS NOT INITIAL.
LOOP AT lt_kpis ASSIGNING FIELD-SYMBOL(<ls_kpi>).
LOOP AT lt_filter INTO DATA(ls_filter).
CASE ls_filter-name.
WHEN 'CATEGORY'.
IF NOT <ls_kpi>-Category IN ls_filter-range.
DELETE lt_kpis.
ENDIF.
ENDCASE.
ENDLOOP.
ENDLOOP.
ENDIF.
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines( lt_kpis ) ).
ENDIF.
io_response->set_data( lt_kpis ).
ENDMETHOD.
METHOD get_sales_kpis.
" Periode actuelle et precedente depuis CDS View
SELECT SUM( net_amount ) AS current_sales
FROM zi_salesorder
WHERE created_at >= @( cl_abap_context_info=>get_system_date( ) - 30 )
INTO @DATA(lv_current_sales).
SELECT SUM( net_amount ) AS previous_sales
FROM zi_salesorder
WHERE created_at >= @( cl_abap_context_info=>get_system_date( ) - 60 )
AND created_at < @( cl_abap_context_info=>get_system_date( ) - 30 )
INTO @DATA(lv_previous_sales).
DATA: lv_trend TYPE i,
lv_percent TYPE decfloat34.
calculate_trend(
EXPORTING iv_current = lv_current_sales
iv_previous = lv_previous_sales
IMPORTING ev_trend = lv_trend
ev_percent = lv_percent ).
APPEND VALUE #(
KPIId = 'SALES_30D"
KPIName = 'Chiffre d''affaires (30 jours)"
KPIValue = lv_current_sales
KPIUnit = 'EUR"
Trend = lv_trend
TrendPercent = lv_percent
ComparisonValue = lv_previous_sales
Status = COND #( WHEN lv_trend >= 0 THEN 'Good"
WHEN lv_percent > -10 THEN 'Warning"
ELSE 'Critical' )
Category = 'Sales"
LastUpdated = utclong_current( )
) TO rt_kpis.
ENDMETHOD.
METHOD calculate_trend.
IF iv_previous = 0.
ev_trend = 0.
ev_percent = 0.
ELSE.
ev_percent = ( iv_current - iv_previous ) / iv_previous * 100.
ev_trend = COND #( WHEN ev_percent > 0 THEN 1
WHEN ev_percent < 0 THEN -1
ELSE 0 ).
ENDIF.
ENDMETHOD.
" Autres methodes...
ENDCLASS.

Bonnes pratiques pour la performance

1. Implementer la mise en cache

CLASS zcl_cached_query DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
PRIVATE SECTION.
CLASS-DATA: gt_cache TYPE zt_cached_data,
gv_cache_timestamp TYPE utclong.
CLASS-METHODS is_cache_valid RETURNING VALUE(rv_valid) TYPE abap_bool.
METHODS refresh_cache.
CONSTANTS: gc_cache_duration_seconds TYPE i VALUE 300. " 5 minutes
ENDCLASS.
CLASS zcl_cached_query IMPLEMENTATION.
METHOD is_cache_valid.
IF gv_cache_timestamp IS INITIAL.
rv_valid = abap_false.
RETURN.
ENDIF.
DATA(lv_age) = utclong_diff(
high = utclong_current( )
low = gv_cache_timestamp
).
rv_valid = xsdbool( lv_age < gc_cache_duration_seconds ).
ENDMETHOD.
METHOD if_rap_query_provider~select.
IF io_request->is_data_requested( ) = abap_false.
RETURN.
ENDIF.
" Verifier le cache
IF is_cache_valid( ) = abap_false.
refresh_cache( ).
ENDIF.
" Retourner les donnees depuis le cache
io_response->set_data( gt_cache ).
ENDMETHOD.
METHOD refresh_cache.
" Recuperer les donnees externes
gt_cache = fetch_external_data( ).
gv_cache_timestamp = utclong_current( ).
ENDMETHOD.
ENDCLASS.

2. Traitement parallele

METHOD fetch_data_parallel.
" Recuperer plusieurs sources de donnees en parallele
DATA: lt_tasks TYPE TABLE OF REF TO zcl_data_fetch_task.
" Creer les taches
APPEND NEW zcl_data_fetch_task( 'SOURCE_A' ) TO lt_tasks.
APPEND NEW zcl_data_fetch_task( 'SOURCE_B' ) TO lt_tasks.
APPEND NEW zcl_data_fetch_task( 'SOURCE_C' ) TO lt_tasks.
" Executer en parallele
DATA(lo_parallel) = NEW cl_abap_parallel( ).
lo_parallel->run_instances(
EXPORTING
p_in_tab = lt_tasks
IMPORTING
p_out_tab = DATA(lt_results)
).
" Agreger les resultats
LOOP AT lt_results INTO DATA(lo_result).
APPEND LINES OF lo_result->get_data( ) TO rt_combined_data.
ENDLOOP.
ENDMETHOD.

3. Gestion des erreurs

METHOD if_rap_query_provider~select.
TRY.
" Recuperer les donnees
DATA(lt_data) = fetch_external_data( ).
io_response->set_data( lt_data ).
CATCH cx_http_dest_provider_error
cx_web_http_client_error INTO DATA(lx_http_error).
" Erreur HTTP : Reponse vide ou donnees de secours
io_response->set_data( VALUE #( ) ).
" Optionnel : Lever une Business Exception
RAISE EXCEPTION TYPE zcx_external_service_error
EXPORTING
textid = zcx_external_service_error=>service_unavailable
service_name = 'Weather API'.
CATCH cx_root INTO DATA(lx_error).
" Logger l'erreur generique
cl_bali_log=>create_with_header(
EXPORTING
header = cl_bali_header_setter=>create(
object = 'ZDASHBOARD"
subobject = 'QUERY"
)
)->add_item(
item = cl_bali_exception_setter=>create(
exception = lx_error
)
)->save( ).
io_response->set_data( VALUE #( ) ).
ENDTRY.
ENDMETHOD.

Abstract Entities vs. Custom Entities

UtilisationRecommandation
Parametres d’ActionAbstract Entity
Resultats de FunctionAbstract Entity
Service OData avec filtrageCustom Entity
Donnees en lecture seuleCustom Entity
Structures temporairesAbstract Entity
Donnees de tableau de bordCustom Entity avec cache

Resume

Les Abstract Entities et les Custom Entities avec if_rap_query_provider offrent une flexibilite maximale pour :

  • Sources de donnees externes - APIs REST, RFC, systemes legacy
  • Donnees calculees - Agregations, KPIs, tendances
  • Parametres Action/Function - Structures d’entree/sortie complexes
  • Solutions hybrides - Combinaison de base de donnees et sources externes

Points importants :

  • Toujours implementer la mise en cache pour les sources de donnees externes
  • Prendre en compte la pagination et le filtrage dans le Query Provider
  • Gestion des erreurs robuste avec strategies de secours
  • Optimiser la performance par le traitement parallele

Sujets connexes