Custom Entities ermöglichen es, externe Datenquellen in RAP-basierte Anwendungen einzubinden. Anders als CDS Views, die auf Datenbanktabellen basieren, werden Custom Entities durch ABAP-Code mit Daten befüllt.
Anwendungsfälle für Custom Entities
Custom Entities sind ideal für:
- Externe APIs - REST-Services, SOAP-Webservices, OData-Services
- Legacy-Systeme - Funktionsbausteine, BAPIs, Remote Function Calls
- Berechnete Daten - Komplexe Aggregationen, die nicht in CDS möglich sind
- Nicht-HANA-Quellen - Dateien, externe Datenbanken via ADBC
- Virtuelle Daten - Daten, die erst zur Laufzeit generiert werden
Unterschied zu CDS Views
| Aspekt | CDS View | Custom Entity |
|---|---|---|
| Datenquelle | Datenbanktabellen | ABAP-Code |
| SQL-Pushdown | Ja | Nein |
| Datenmodellierung | Deklarativ | Imperativ |
| Performance | Optimiert durch HANA | Abhängig von Implementation |
| Flexibilität | Eingeschränkt | Vollständig |
| Wartungsaufwand | Gering | Höher |
CDS Custom Entity Definition
Eine Custom Entity wird im CDS definiert, aber die Daten kommen aus einer ABAP-Klasse:
@EndUserText.label: 'Externe Produkte'@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;}Wichtige Annotationen
| Annotation | Beschreibung |
|---|---|
@ObjectModel.query.implementedBy | Verweist auf die Query-Implementierungsklasse |
Die Klasse muss mit dem Präfix ABAP: angegeben werden.
Query Implementation Class
Die Query-Klasse implementiert das 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. Prüfen ob Daten angefordert werden IF io_request->is_data_requested( ).
" 2. Paging-Informationen abrufen DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ).
" 3. Sortierung abrufen DATA(lt_sort) = io_request->get_sort_elements( ).
" 4. Filter abrufen DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" 5. Daten aus externer Quelle laden DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct.
" Hier: API-Aufruf oder andere Datenquelle 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. Daten an Response übergeben io_response->set_data( lt_products ). ENDIF.
" 7. Count angefordert? IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_products ) ). ENDIF. ENDMETHOD.ENDCLASS.Paging implementieren
Für große Datenmengen ist Paging essentiell:
METHOD if_rap_query_provider~select. " Paging-Parameter abrufen DATA(lo_paging) = io_request->get_paging( ). DATA(lv_offset) = lo_paging->get_offset( ). DATA(lv_page_size) = lo_paging->get_page_size( ).
" Maximale Seitengröße begrenzen IF lv_page_size > 1000 OR lv_page_size <= 0. lv_page_size = 100. " Default-Wert ENDIF.
" Daten laden (z.B. aus API mit Pagination) DATA lt_all_products TYPE STANDARD TABLE OF zi_externalproduct. lt_all_products = call_external_api( ).
" Paging anwenden 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.
" Response setzen 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.Sorting implementieren
Sortierung ermöglicht dem Benutzer flexible Darstellung:
METHOD if_rap_query_provider~select. " Daten laden DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct. lt_products = load_products( ).
" Sortierung abrufen und anwenden 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.Dynamische Sortierung mit SORT-Statement
Für eine elegantere Lösung können Sie dynamisches Sortieren verwenden:
METHOD apply_sorting. DATA(lt_sort) = io_request->get_sort_elements( ).
" Sortier-String aufbauen 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.
" Dynamisches Sortieren IF lv_sort_string IS NOT INITIAL. SORT ct_data BY (lv_sort_string). ENDIF.ENDMETHOD.Filter implementieren
Filter ermöglichen gezielte Datenabfragen:
METHOD if_rap_query_provider~select. " Daten laden DATA lt_products TYPE STANDARD TABLE OF zi_externalproduct. lt_products = load_all_products( ).
" Filter abrufen TRY. DATA(lt_filter_cond) = io_request->get_filter( )->get_as_ranges( ). CATCH cx_rap_query_filter_no_range. " Keine Filterkonvertierung möglich ENDTRY.
" Filter anwenden 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.Beispiel: Anbindung externer REST-API
Ein vollständiges Beispiel für die Anbindung einer externen Produkt-API:
1. Custom Entity Definition
@EndUserText.label: 'Externe Produkte API'@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 mit HTTP-Aufruf
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. " API aufrufen DATA(lt_api_products) = call_product_api( ).
" In Entity-Format konvertieren DATA(lt_products) = map_to_entity( lt_api_products ).
" Filter anwenden 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.
" Paging anwenden 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). " Fehlerbehandlung 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. " HTTP-Destination aus dem 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 ).
" HTTP-Client erstellen DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
" Request ausführen 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( ).
" JSON parsen /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.Custom Entity in RAP Business Object einbinden
Custom Entities können auch in RAP BO eingebunden werden:
Behavior Definition für Custom Entity
unmanaged implementation in class zbp_i_extproduct unique;
define behavior for ZI_ExtProductAPI alias Product{ // Nur lesend - keine CUD-Operationen // Actions können trotzdem definiert werden action refreshFromAPI;}Service Definition und Binding
@EndUserText.label: 'Externe Produkte Service'define service ZUI_ExtProducts { expose ZI_ExtProductAPI as Products;}Fehlerbehandlung
Robuste Fehlerbehandlung ist bei externen Quellen wichtig:
METHOD if_rap_query_provider~select. TRY. " Daten laden mit Timeout DATA(lt_products) = load_external_data( ). io_response->set_data( lt_products ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest). " Destination nicht gefunden oder Konfigurationsfehler 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). " HTTP-Fehler (Timeout, Netzwerk, 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). " JSON-Parsing-Fehler RAISE EXCEPTION TYPE cx_rap_query_provider EXPORTING textid = cx_rap_query_provider=>query_failed previous = lx_conv. ENDTRY.ENDMETHOD.Performance-Tipps
- Caching - Häufig abgerufene Daten zwischenspeichern
- Lazy Loading - Nur benötigte Daten laden
- Pagination an API delegieren - Wenn die externe API Paging unterstützt
- Asynchrone Aufrufe - Für lange laufende Abfragen
- Connection Pooling - HTTP-Verbindungen wiederverwenden
" Beispiel: Einfaches CachingCLASS 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. " Cache-Gültigkeit: 5 Minuten 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. " Cache neu laden gt_cache = call_external_api( ). gv_cache_timestamp = lv_now. ENDIF.
rt_data = gt_cache. ENDMETHOD.ENDCLASS.Best Practices
- Interface verwenden -
IF_RAP_QUERY_PROVIDERvollständig implementieren - Fehlerbehandlung - Externe Quellen können fehlschlagen
- Paging unterstützen - Große Datenmengen erfordern Pagination
- Filter nutzen - Daten möglichst früh filtern (bei API wenn möglich)
- Logging - Externe Aufrufe protokollieren für Debugging
- Timeouts setzen - Lange Wartezeiten vermeiden
- Testen - Externe Abhängigkeiten mocken für Unit Tests
Weiterführende Themen
- RAP Grundlagen - Einführung in das RESTful ABAP Programming Model
- Destination Service - Externe Systeme sicher anbinden
- CDS Table Functions mit AMDP - Alternative für komplexe Berechnungen