Abstract Entities sind ein spezieller CDS-Typ in RAP, der keine Datenbanktabelle repraesentiert. Sie dienen als Datenstruktur fuer Action-Parameter, Funktionsergebnisse und Custom Queries mit maximaler Flexibilitaet.
Was sind Abstract Entities?
Abstract Entities werden mit define abstract entity definiert und existieren nur als Metadaten-Definition. Sie haben keine persistente Datenquelle und werden durch ABAP-Code mit Daten befuellt.
@EndUserText.label: 'Externe Wetterdaten'define abstract entity ZA_WeatherData{ City : abap.char(40); Temperature : abap.dec(5,2); Humidity : abap.int2; Conditions : abap.char(50); Timestamp : abap.utclong;}Unterschied zu anderen Entity-Typen
| Aspekt | CDS View | Custom Entity | Abstract Entity |
|---|---|---|---|
| Definition | define view entity | define custom entity | define abstract entity |
| Datenquelle | Datenbanktabellen | Query Provider Klasse | Keine (nur Struktur) |
| OData-exponierbar | Ja | Ja | Nur ueber Actions/Functions |
| Einsatzzweck | Datenmodellierung | Externe Datenquellen | Parameter, Results |
| Persistenz | Datenbank | Keine | Keine |
Einsatzszenarien
1. Action-Parameter
Abstract Entities definieren komplexe Eingabestrukturen fuer RAP Actions:
@EndUserText.label: 'Buchungsparameter'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);}Verwendung in der Behavior Definition:
action createBooking parameter ZA_BookingParameters result [1] $self;2. Function Results
Fuer komplexe Rueckgabewerte von RAP Functions:
@EndUserText.label: 'Preisberechnung Ergebnis'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 ohne Datenbanktabelle
Fuer Daten aus externen Systemen oder berechnete Werte:
@EndUserText.label: 'Waehrungskurse'@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 mit if_rap_query_provider
Die Query-Implementation-Klasse fuellt die Entity mit Daten:
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. Pruefen ob Daten angefordert werden IF io_request->is_data_requested( ) = abap_false. RETURN. ENDIF.
" 2. Filter abrufen DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" 3. Paging abrufen DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ).
" 4. Daten von externer API abrufen DATA(lt_rates) = fetch_rates_from_api( ).
" 5. Filter anwenden DATA lt_result TYPE STANDARD TABLE OF zi_exchangerate. LOOP AT lt_rates INTO DATA(ls_rate). " Filterkriterien pruefen 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. Gesamtanzahl fuer Paging IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_result ) ). ENDIF.
" 7. Paging anwenden 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. Daten zurueckgeben io_response->set_data( lt_result ). ENDMETHOD.
METHOD fetch_rates_from_api. " HTTP-Client erstellen 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( ).
" JSON parsen /ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = rt_rates ).
CATCH cx_root INTO DATA(lx_error). " Fehler loggen, leere Tabelle zurueckgeben " Oder: RAISE SHORTDUMP ENDTRY. ENDMETHOD.ENDCLASS.Integration mit externen APIs
REST API Anbindung
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( ).
" Query-Parameter setzen 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). " Fehlerbehandlung ENDTRY.ENDMETHOD.RFC-Anbindung
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. " Fehler loggen ENDIF.
CATCH cx_rfc_dest_provider_error INTO DATA(lx_error). " Destination nicht gefunden ENDTRY.ENDMETHOD.Komplexes Beispiel: Dashboard-Daten
Ein Dashboard, das Daten aus mehreren Quellen aggregiert:
Abstract Entity Definition
@EndUserText.label: 'Dashboard KPIs'@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;}Query Implementation
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.
" Daten aus verschiedenen Quellen aggregieren DATA lt_kpis TYPE STANDARD TABLE OF zi_dashboardkpi.
" Sales KPIs aus CDS View APPEND LINES OF get_sales_kpis( ) TO lt_kpis.
" Inventory KPIs aus Legacy-System via RFC APPEND LINES OF get_inventory_kpis( ) TO lt_kpis.
" Customer KPIs aus externer API APPEND LINES OF get_customer_kpis( ) TO lt_kpis.
" Filter anwenden 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. " Aktuelle und vorherige Periode aus 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 = 'Umsatz (30 Tage)' 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.
" Weitere Methoden...ENDCLASS.Best Practices fuer Performance
1. Caching implementieren
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 MinutenENDCLASS.
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.
" Cache pruefen IF is_cache_valid( ) = abap_false. refresh_cache( ). ENDIF.
" Daten aus Cache zurueckgeben io_response->set_data( gt_cache ). ENDMETHOD.
METHOD refresh_cache. " Externe Daten abrufen gt_cache = fetch_external_data( ). gv_cache_timestamp = utclong_current( ). ENDMETHOD.ENDCLASS.2. Parallele Verarbeitung
METHOD fetch_data_parallel. " Mehrere Datenquellen parallel abrufen DATA: lt_tasks TYPE TABLE OF REF TO zcl_data_fetch_task.
" Tasks erstellen 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.
" Parallel ausfuehren DATA(lo_parallel) = NEW cl_abap_parallel( ). lo_parallel->run_instances( EXPORTING p_in_tab = lt_tasks IMPORTING p_out_tab = DATA(lt_results) ).
" Ergebnisse aggregieren LOOP AT lt_results INTO DATA(lo_result). APPEND LINES OF lo_result->get_data( ) TO rt_combined_data. ENDLOOP.ENDMETHOD.3. Fehlerbehandlung
METHOD if_rap_query_provider~select. TRY. " Daten abrufen 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). " HTTP-Fehler: Leere Antwort oder Fallback-Daten io_response->set_data( VALUE #( ) ).
" Optional: Business Exception ausloesen 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). " Generischer Fehler loggen 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
| Anwendung | Empfehlung |
|---|---|
| Action-Parameter | Abstract Entity |
| Function Results | Abstract Entity |
| OData-Service mit Filterung | Custom Entity |
| Read-Only Daten | Custom Entity |
| Temporaere Strukturen | Abstract Entity |
| Dashboard-Daten | Custom Entity mit Cache |
Zusammenfassung
Abstract Entities und Custom Entities mit if_rap_query_provider bieten maximale Flexibilitaet fuer:
- Externe Datenquellen - REST APIs, RFC, Legacy-Systeme
- Berechnete Daten - Aggregationen, KPIs, Trends
- Action/Function Parameter - Komplexe Eingabe-/Ausgabestrukturen
- Hybride Loesungen - Kombination aus Datenbank und externen Quellen
Wichtige Punkte:
- Immer Caching fuer externe Datenquellen implementieren
- Paging und Filterung im Query Provider beruecksichtigen
- Robuste Fehlerbehandlung mit Fallback-Strategien
- Performance durch parallele Verarbeitung optimieren
Verwandte Themen
- RAP Custom Entities - Custom Entities mit externen Datenquellen
- RAP Grundlagen - Das RAP-Programmiermodell
- HTTP Client - REST APIs aufrufen in ABAP Cloud