Externe REST APIs sind ein zentraler Baustein moderner Cloud-Anwendungen. In ABAP Cloud und RAP gibt es standardisierte Wege, um externe Services sicher und robust anzubinden. Dieser Artikel zeigt den vollständigen Workflow von der Konfiguration bis zur Implementierung.
Übersicht: API-Integration in RAP
| Aspekt | Beschreibung |
|---|---|
| HTTP Client | CL_WEB_HTTP_CLIENT_MANAGER für Cloud-native Aufrufe |
| Konfiguration | Communication Arrangements für sichere Credentials |
| JSON | /UI2/CL_JSON oder XCO für Serialisierung |
| RAP-Integration | Custom Entities oder Determinations/Actions |
| Fehlerbehandlung | Retry-Logik, Timeouts, Exception Handling |
Voraussetzungen
Bevor du externe APIs in ABAP Cloud konsumieren kannst, benötigst du:
- Communication Scenario - Definiert den Kommunikationstyp
- Communication System - Beschreibt das Zielsystem
- Communication Arrangement - Verbindet Scenario mit System
┌────────────────────────────────────────────────────────────┐│ ABAP Cloud System ││ ││ ┌──────────────────┐ ┌─────────────────────────────┐ ││ │ Communication │ │ Communication Arrangement │ ││ │ Scenario │───>│ │ ││ │ Z_WEATHER_API │ │ Scenario: Z_WEATHER_API │ ││ └──────────────────┘ │ System: WEATHER_SERVICE │ ││ │ Service: /weather │ ││ ┌──────────────────┐ │ │ ││ │ Communication │───>│ Auth: OAuth2 / API-Key │ ││ │ System │ └─────────────────────────────┘ ││ │ WEATHER_SERVICE │ │ ││ │ api.weather.com │ │ ││ └──────────────────┘ ▼ ││ ┌─────────────────────────────┐ ││ │ HTTP Destination │ ││ │ cl_http_destination_provider│ ││ └─────────────────────────────┘ │└────────────────────────────────────────────────────────────┘Communication Scenario erstellen
1. Outbound Service Definition
" Outbound Service: Z_WEATHER_OUTBOUND" Service ID: Z_WEATHER_OS_REST" Service Type: HTTP
@EndUserText.label: 'Weather API Outbound Service'define outbound service Z_WEATHER_OS_REST { service binding Z_WEATHER_SB;}2. Communication Scenario
Das Communication Scenario wird im ADT unter Other ABAP Repository Objects → Communication Management → Communication Scenario erstellt:
<?xml version="1.0" encoding="utf-8"?><scn:scenario xmlns:scn="http://sap.com/xi/BASIS/Communication" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" scn:id="Z_WEATHER_API" scn:version="1"> <scn:label>Weather API Integration</scn:label> <scn:description>Scenario für Weather API Anbindung</scn:description> <scn:allowedInstances>MULTIPLE</scn:allowedInstances> <scn:communicationType>OUTBOUND</scn:communicationType> <scn:outboundServices> <scn:service scn:id="Z_WEATHER_OS_REST" scn:authMethod="BASIC_OR_OAUTH"/> </scn:outboundServices></scn:scenario>3. Communication Arrangement einrichten
Im Fiori Launchpad unter Communication Arrangements:
| Feld | Wert |
|---|---|
| Scenario | Z_WEATHER_API |
| Arrangement Name | Z_WEATHER_PROD |
| Communication System | WEATHER_API_SYSTEM |
| Service Path | /api/v1 |
| Authentication | OAuth 2.0 Client Credentials |
HTTP Client in ABAP Cloud
Grundlegender API-Aufruf
CLASS zcl_weather_api DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_weather_response, location TYPE string, temperature TYPE decfloat16, humidity TYPE i, condition TYPE string, wind_speed TYPE decfloat16, updated_at TYPE timestamp, END OF ty_weather_response.
METHODS get_current_weather IMPORTING iv_city TYPE string RETURNING VALUE(rs_result) TYPE ty_weather_response RAISING cx_http_dest_provider_error cx_web_http_client_error.
PRIVATE SECTION. CONSTANTS: c_comm_scenario TYPE if_com_scenario_factory=>ty_cscn_id VALUE 'Z_WEATHER_API', c_outbound_service TYPE if_com_outbound_api_factory=>ty_service_id VALUE 'Z_WEATHER_OS_REST'.ENDCLASS.
CLASS zcl_weather_api IMPLEMENTATION. METHOD get_current_weather. DATA lo_client TYPE REF TO if_web_http_client.
TRY. " 1. Communication Arrangement abrufen DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = c_comm_scenario service_id = c_outbound_service ).
" 2. HTTP Client erstellen lo_client = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
" 3. Request konfigurieren DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/weather?city={ cl_web_http_utility=>escape_url( iv_city ) }| ). lo_request->set_header_field( i_name = 'Accept' i_value = 'application/json' ).
" 4. Request ausführen DATA(lo_response) = lo_client->execute( if_web_http_client=>get ). DATA(lv_status) = lo_response->get_status( )-code.
" 5. Response verarbeiten IF lv_status = 200. DATA(lv_json) = lo_response->get_text( ).
/ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = rs_result ). ELSE. " Fehlerbehandlung DATA(lv_error_body) = lo_response->get_text( ). RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
CATCH cx_http_dest_provider_error cx_web_http_client_error. RAISE.
CLEANUP. IF lo_client IS BOUND. lo_client->close( ). ENDIF. ENDTRY. ENDMETHOD.ENDCLASS.JSON Parsing und Mapping
Komplexe JSON-Strukturen
" API Response StrukturTYPES: BEGIN OF ty_coord, lat TYPE decfloat16, lon TYPE decfloat16, END OF ty_coord,
BEGIN OF ty_weather_detail, id TYPE i, main TYPE string, description TYPE string, icon TYPE string, END OF ty_weather_detail, tt_weather_details TYPE STANDARD TABLE OF ty_weather_detail WITH EMPTY KEY,
BEGIN OF ty_main, temp TYPE decfloat16, feels_like TYPE decfloat16, temp_min TYPE decfloat16, temp_max TYPE decfloat16, pressure TYPE i, humidity TYPE i, END OF ty_main,
BEGIN OF ty_full_response, coord TYPE ty_coord, weather TYPE tt_weather_details, main TYPE ty_main, name TYPE string, cod TYPE i, END OF ty_full_response.
METHOD parse_weather_response. DATA ls_response TYPE ty_full_response.
" JSON mit verschachtelten Strukturen parsen /ui2/cl_json=>deserialize( EXPORTING json = iv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = ls_response ).
" In Zielstruktur mappen rs_weather = VALUE #( location = ls_response-name temperature = ls_response-main-temp humidity = ls_response-main-humidity condition = VALUE #( ls_response-weather[ 1 ]-description OPTIONAL ) latitude = ls_response-coord-lat longitude = ls_response-coord-lon ).ENDMETHOD.Name Mapping bei unterschiedlichen Konventionen
" API verwendet snake_case, ABAP verwendet camelCaseTYPES: BEGIN OF ty_api_response, user_id TYPE string, " API: user_id first_name TYPE string, " API: first_name last_name TYPE string, " API: last_name email_addr TYPE string, " API: email_addr created_at TYPE string, " API: created_at END OF ty_api_response.
METHOD deserialize_with_mapping. " pretty_mode-extended für snake_case zu ABAP Mapping /ui2/cl_json=>deserialize( EXPORTING json = iv_json pretty_name = /ui2/cl_json=>pretty_mode-extended CHANGING data = rs_result ).ENDMETHOD.POST Request mit JSON Body
Daten an externe API senden
CLASS zcl_order_api DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_order_item, product_id TYPE string, quantity TYPE i, price TYPE decfloat16, END OF ty_order_item, tt_order_items TYPE STANDARD TABLE OF ty_order_item WITH EMPTY KEY,
BEGIN OF ty_order_request, customer_id TYPE string, order_date TYPE string, shipping_addr TYPE string, items TYPE tt_order_items, END OF ty_order_request,
BEGIN OF ty_order_response, order_id TYPE string, status TYPE string, total_amount TYPE decfloat16, estimated_date TYPE string, END OF ty_order_response.
METHODS create_order IMPORTING is_order TYPE ty_order_request RETURNING VALUE(rs_result) TYPE ty_order_response RAISING cx_http_dest_provider_error cx_web_http_client_error.ENDCLASS.
CLASS zcl_order_api IMPLEMENTATION. METHOD create_order. DATA lo_client TYPE REF TO if_web_http_client.
TRY. " Destination abrufen DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_ORDER_API' service_id = 'Z_ORDER_OS_REST' ).
" HTTP Client erstellen lo_client = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
" Request Body als JSON DATA(lv_json_body) = /ui2/cl_json=>serialize( data = is_order compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
" Request konfigurieren DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( '/orders' ). lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ). lo_request->set_header_field( i_name = 'Accept' i_value = 'application/json' ). lo_request->set_text( lv_json_body ).
" POST ausführen DATA(lo_response) = lo_client->execute( if_web_http_client=>post ). DATA(lv_status) = lo_response->get_status( )-code.
" Response verarbeiten IF lv_status = 201 OR lv_status = 200. /ui2/cl_json=>deserialize( EXPORTING json = lo_response->get_text( ) pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = rs_result ). ELSE. RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
CLEANUP. IF lo_client IS BOUND. lo_client->close( ). ENDIF. ENDTRY. ENDMETHOD.ENDCLASS.Authentifizierung
API Key Authentication
METHOD call_with_api_key. DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_API_SCENARIO' service_id = 'Z_API_SERVICE' ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
" API Key im Header lo_request->set_header_field( i_name = 'X-API-Key' i_value = lv_api_key " Aus Secure Store oder Destination ).
" Oder als Query Parameter lo_request->set_uri_path( |/data?api_key={ lv_api_key }| ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ). lo_client->close( ).ENDMETHOD.OAuth 2.0 Client Credentials
OAuth wird automatisch über das Communication Arrangement gehandhabt:
Communication System Konfiguration:┌────────────────────────────────────────┐│ OAuth 2.0 Settings │├────────────────────────────────────────┤│ Token Endpoint: /oauth/token ││ Client ID: my-client-id ││ Client Secret: ******** ││ Scope: api.read api.write │└────────────────────────────────────────┘" Bei OAuth wird das Token automatisch geholt und erneuertMETHOD call_with_oauth. " Communication Arrangement enthält OAuth-Konfiguration DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_OAUTH_API' service_id = 'Z_OAUTH_SERVICE' ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
" Authorization Header wird automatisch hinzugefügt DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
lo_client->close( ).ENDMETHOD.Bearer Token manuell setzen
METHOD call_with_bearer_token. DATA(lo_request) = lo_client->get_http_request( ).
" Bearer Token aus vorherigem Auth-Call lo_request->set_header_field( i_name = 'Authorization' i_value = |Bearer { lv_access_token }| ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).ENDMETHOD.Fehlerbehandlung und Retry-Logik
Robuste Exception-Klasse
CLASS zcx_external_api_error DEFINITION PUBLIC INHERITING FROM cx_static_check CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_t100_message. INTERFACES if_t100_dyn_msg.
CONSTANTS: BEGIN OF connection_failed, msgid TYPE symsgid VALUE 'Z_API', msgno TYPE symsgno VALUE '001', attr1 TYPE scx_attrname VALUE 'MV_URL', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF connection_failed,
BEGIN OF http_error, msgid TYPE symsgid VALUE 'Z_API', msgno TYPE symsgno VALUE '002', attr1 TYPE scx_attrname VALUE 'MV_STATUS_CODE', attr2 TYPE scx_attrname VALUE 'MV_STATUS_TEXT', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF http_error,
BEGIN OF timeout, msgid TYPE symsgid VALUE 'Z_API', msgno TYPE symsgno VALUE '003', attr1 TYPE scx_attrname VALUE 'MV_URL', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF timeout,
BEGIN OF rate_limit_exceeded, msgid TYPE symsgid VALUE 'Z_API', msgno TYPE symsgno VALUE '004', attr1 TYPE scx_attrname VALUE 'MV_RETRY_AFTER', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF rate_limit_exceeded.
DATA: mv_url TYPE string, mv_status_code TYPE i, mv_status_text TYPE string, mv_retry_after TYPE i, mv_is_retryable TYPE abap_bool.
METHODS constructor IMPORTING textid LIKE if_t100_message=>t100key OPTIONAL previous LIKE previous OPTIONAL iv_url TYPE string OPTIONAL iv_status TYPE i OPTIONAL iv_text TYPE string OPTIONAL iv_retryable TYPE abap_bool DEFAULT abap_false.
METHODS is_retryable RETURNING VALUE(rv_retryable) TYPE abap_bool.ENDCLASS.
CLASS zcx_external_api_error IMPLEMENTATION. METHOD constructor. super->constructor( previous = previous ).
mv_url = iv_url. mv_status_code = iv_status. mv_status_text = iv_text. mv_is_retryable = iv_retryable.
IF textid IS INITIAL. if_t100_message~t100key = if_t100_message=>default_textid. ELSE. if_t100_message~t100key = textid. ENDIF. ENDMETHOD.
METHOD is_retryable. rv_retryable = mv_is_retryable. ENDMETHOD.ENDCLASS.Retry-Logik mit Exponential Backoff
CLASS zcl_api_client_with_retry DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. CONSTANTS: c_max_retries TYPE i VALUE 3, c_initial_delay TYPE i VALUE 1000, " Millisekunden c_max_delay TYPE i VALUE 30000. " Millisekunden
METHODS execute_with_retry IMPORTING io_client TYPE REF TO if_web_http_client iv_method TYPE i DEFAULT if_web_http_client=>get RETURNING VALUE(ro_response) TYPE REF TO if_web_http_response RAISING zcx_external_api_error.
PRIVATE SECTION. METHODS is_retryable_status IMPORTING iv_status TYPE i RETURNING VALUE(rv_retryable) TYPE abap_bool.
METHODS wait_with_backoff IMPORTING iv_attempt TYPE i.ENDCLASS.
CLASS zcl_api_client_with_retry IMPLEMENTATION. METHOD execute_with_retry. DATA lv_attempts TYPE i. DATA lx_last_error TYPE REF TO zcx_external_api_error.
WHILE lv_attempts < c_max_retries. lv_attempts = lv_attempts + 1.
TRY. " Request ausführen ro_response = io_client->execute( iv_method ). DATA(lv_status) = ro_response->get_status( )-code.
" Erfolgreiche Antwort IF lv_status >= 200 AND lv_status < 300. RETURN. ENDIF.
" Rate Limit IF lv_status = 429. " Retry-After Header auswerten DATA(lv_retry_after) = ro_response->get_header_field( 'Retry-After' ). wait_with_backoff( lv_attempts ). CONTINUE. ENDIF.
" Server-Fehler (5xx) - retry IF is_retryable_status( lv_status ). IF lv_attempts < c_max_retries. wait_with_backoff( lv_attempts ). CONTINUE. ENDIF. ENDIF.
" Client-Fehler (4xx) - kein Retry RAISE EXCEPTION TYPE zcx_external_api_error EXPORTING textid = zcx_external_api_error=>http_error iv_status = lv_status iv_text = ro_response->get_status( )-reason iv_retryable = abap_false.
CATCH cx_web_http_client_error INTO DATA(lx_http). " Netzwerkfehler - retry IF lv_attempts < c_max_retries. wait_with_backoff( lv_attempts ). CONTINUE. ENDIF.
RAISE EXCEPTION TYPE zcx_external_api_error EXPORTING textid = zcx_external_api_error=>connection_failed previous = lx_http iv_retryable = abap_true. ENDTRY. ENDWHILE.
" Max Retries erreicht RAISE EXCEPTION TYPE zcx_external_api_error EXPORTING textid = zcx_external_api_error=>connection_failed iv_retryable = abap_false. ENDMETHOD.
METHOD is_retryable_status. " 5xx Server Errors und 429 Rate Limit rv_retryable = xsdbool( iv_status = 429 OR iv_status = 500 OR iv_status = 502 OR iv_status = 503 OR iv_status = 504 ). ENDMETHOD.
METHOD wait_with_backoff. " Exponential Backoff: delay = initial_delay * 2^(attempt-1) DATA(lv_delay) = c_initial_delay * ipow( base = 2 exp = iv_attempt - 1 ).
" Maximum begrenzen IF lv_delay > c_max_delay. lv_delay = c_max_delay. ENDIF.
" Warten cl_abap_session=>sleep( lv_delay ). ENDMETHOD.ENDCLASS.Integration in RAP
Determination mit API-Aufruf
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS calculate_shipping FOR DETERMINE ON SAVE IMPORTING keys FOR Order~CalculateShipping.ENDCLASS.
CLASS lhc_order IMPLEMENTATION. METHOD calculate_shipping. " Bestellungen lesen READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( ShippingAddress PostalCode Country Weight ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Externe Shipping-API aufrufen DATA(lo_shipping_api) = NEW zcl_shipping_api( ).
LOOP AT lt_orders INTO DATA(ls_order). TRY. " Versandkosten von externem Service abrufen DATA(ls_shipping) = lo_shipping_api->calculate_shipping( iv_country = ls_order-Country iv_postal_code = ls_order-PostalCode iv_weight = ls_order-Weight ).
" Order aktualisieren MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( ShippingCost EstimatedDelivery ) WITH VALUE #( ( %tky = ls_order-%tky ShippingCost = ls_shipping-cost EstimatedDelivery = ls_shipping-delivery_date ) ).
CATCH zcx_external_api_error INTO DATA(lx_error). " Fehler als Message anhängen APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( text = lx_error->get_text( ) ) %element-ShippingCost = if_abap_behv=>mk-on ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.ENDCLASS.Action mit API-Integration
CLASS lhc_product DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS refresh_stock FOR MODIFY IMPORTING keys FOR ACTION Product~RefreshStock RESULT result.ENDCLASS.
CLASS lhc_product IMPLEMENTATION. METHOD refresh_stock. " Produkte lesen READ ENTITIES OF zi_product IN LOCAL MODE ENTITY Product FIELDS ( ProductId ExternalId ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_products).
" Externe Inventory-API aufrufen DATA(lo_inventory_api) = NEW zcl_inventory_api( ).
" Batch-Request für alle Produkte DATA lt_product_ids TYPE string_table. LOOP AT lt_products INTO DATA(ls_product). APPEND ls_product-ExternalId TO lt_product_ids. ENDLOOP.
TRY. " Lagerbestände abrufen DATA(lt_stock) = lo_inventory_api->get_stock_levels( it_product_ids = lt_product_ids ).
" Produkte aktualisieren LOOP AT lt_products INTO ls_product. DATA(ls_stock) = VALUE #( lt_stock[ product_id = ls_product-ExternalId ] OPTIONAL ).
IF ls_stock IS NOT INITIAL. MODIFY ENTITIES OF zi_product IN LOCAL MODE ENTITY Product UPDATE FIELDS ( StockQuantity LastStockUpdate ) WITH VALUE #( ( %tky = ls_product-%tky StockQuantity = ls_stock-quantity LastStockUpdate = utclong_current( ) ) ). ENDIF. ENDLOOP.
" Ergebnis zurückgeben READ ENTITIES OF zi_product IN LOCAL MODE ENTITY Product ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_updated).
result = VALUE #( FOR wa IN lt_updated ( %tky = wa-%tky %param = wa ) ).
CATCH zcx_external_api_error INTO DATA(lx_error). LOOP AT keys INTO DATA(ls_key). APPEND VALUE #( %tky = ls_key-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-product. ENDLOOP. ENDTRY. ENDMETHOD.ENDCLASS.Vollständiges Beispiel: Wetter-Integration
1. Custom Entity für Wetterdaten
@EndUserText.label: 'Wetterdaten'@ObjectModel.query.implementedBy: 'ABAP:ZCL_WEATHER_QUERY'define custom entity ZI_WeatherData{ key City : abap.char(100); Country : abap.char(2); Temperature : abap.dec(5,2); FeelsLike : abap.dec(5,2); Humidity : abap.int4; Pressure : abap.int4; WindSpeed : abap.dec(5,2); Condition : abap.char(50); Icon : abap.char(10); UpdatedAt : timestampl;}2. Query Provider Implementation
CLASS zcl_weather_query DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_rap_query_provider.
PRIVATE SECTION. TYPES: BEGIN OF ty_api_weather, id TYPE i, main TYPE string, description TYPE string, icon TYPE string, END OF ty_api_weather, tt_api_weather TYPE STANDARD TABLE OF ty_api_weather WITH EMPTY KEY,
BEGIN OF ty_api_main, temp TYPE decfloat16, feels_like TYPE decfloat16, pressure TYPE i, humidity TYPE i, END OF ty_api_main,
BEGIN OF ty_api_wind, speed TYPE decfloat16, END OF ty_api_wind,
BEGIN OF ty_api_sys, country TYPE string, END OF ty_api_sys,
BEGIN OF ty_api_response, weather TYPE tt_api_weather, main TYPE ty_api_main, wind TYPE ty_api_wind, sys TYPE ty_api_sys, name TYPE string, dt TYPE i, END OF ty_api_response.
METHODS fetch_weather IMPORTING iv_city TYPE string RETURNING VALUE(rs_result) TYPE zi_weatherdata RAISING cx_http_dest_provider_error cx_web_http_client_error.ENDCLASS.
CLASS zcl_weather_query IMPLEMENTATION. METHOD if_rap_query_provider~select. IF io_request->is_data_requested( ) = abap_false. RETURN. ENDIF.
" Filter auslesen DATA lt_filter TYPE if_rap_query_filter=>tt_range_option. TRY. lt_filter = io_request->get_filter( )->get_as_ranges( ). CATCH cx_rap_query_filter_no_range. ENDTRY.
" Stadt aus Filter extrahieren DATA lv_city TYPE string. LOOP AT lt_filter INTO DATA(ls_filter) WHERE name = 'CITY'. lv_city = VALUE #( ls_filter-range[ 1 ]-low OPTIONAL ). ENDLOOP.
IF lv_city IS INITIAL. lv_city = 'Berlin'. " Default ENDIF.
" Wetterdaten abrufen DATA lt_result TYPE STANDARD TABLE OF zi_weatherdata.
TRY. DATA(ls_weather) = fetch_weather( lv_city ). APPEND ls_weather TO lt_result. CATCH cx_http_dest_provider_error cx_web_http_client_error INTO DATA(lx_error). " Leere Ergebnisse bei Fehler ENDTRY.
io_response->set_data( lt_result ).
IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_result ) ). ENDIF. ENDMETHOD.
METHOD fetch_weather. DATA lo_client TYPE REF TO if_web_http_client.
TRY. " Communication Arrangement nutzen DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_WEATHER_API' service_id = 'Z_WEATHER_SERVICE' ).
lo_client = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
" Request DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/weather?q={ cl_web_http_utility=>escape_url( iv_city ) }&units=metric| ).
" Ausführen DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code = 200. DATA ls_api TYPE ty_api_response.
/ui2/cl_json=>deserialize( EXPORTING json = lo_response->get_text( ) pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = ls_api ).
" Mapping rs_result = VALUE #( City = ls_api-name Country = ls_api-sys-country Temperature = ls_api-main-temp FeelsLike = ls_api-main-feels_like Humidity = ls_api-main-humidity Pressure = ls_api-main-pressure WindSpeed = ls_api-wind-speed Condition = VALUE #( ls_api-weather[ 1 ]-description OPTIONAL ) Icon = VALUE #( ls_api-weather[ 1 ]-icon OPTIONAL ) UpdatedAt = utclong_current( ) ). ENDIF.
CLEANUP. IF lo_client IS BOUND. lo_client->close( ). ENDIF. ENDTRY. ENDMETHOD.ENDCLASS.3. Service Definition
@EndUserText.label: 'Weather Service'define service ZUI_Weather { expose ZI_WeatherData as Weather;}Best Practices
DO
| Empfehlung | Beschreibung |
|---|---|
| Communication Arrangements | Credentials zentral verwalten, nie im Code |
| Retry-Logik | Exponential Backoff für transiente Fehler |
| Timeout setzen | Angemessene Timeouts konfigurieren |
| close() aufrufen | HTTP Client immer schließen (auch bei Fehlern) |
| Error Handling | Spezifische Exceptions für API-Fehler |
| Logging | API-Aufrufe für Debugging protokollieren |
| Batch-Requests | Mehrere Datensätze in einem Call wenn möglich |
DON’T
| Vermeiden | Grund |
|---|---|
| Credentials im Code | Sicherheitsrisiko, schwer zu ändern |
| Unbegrenztes Retry | Kann System überlasten |
| Synchrone lange Calls | UI blockiert, Timeout-Risiko |
| Ignorierte Fehler | Führt zu inkonsistenten Daten |
| Fehlende Validierung | API-Responses immer validieren |
Performance-Tipps
" 1. Connection Reuse - Client wiederverwendenDATA: go_client TYPE REF TO if_web_http_client.
METHOD get_or_create_client. IF go_client IS NOT BOUND. DATA(lo_dest) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_API' service_id = 'Z_SERVICE' ). go_client = cl_web_http_client_manager=>create_by_http_destination( lo_dest ). ENDIF. ro_client = go_client.ENDMETHOD.
" 2. Batch statt einzelne RequestsMETHOD fetch_multiple_products. " SCHLECHT: N einzelne Requests LOOP AT it_product_ids INTO DATA(lv_id). APPEND fetch_single( lv_id ) TO rt_products. ENDLOOP.
" GUT: Ein Batch-Request DATA(lv_ids) = concat_lines_of( table = it_product_ids sep = ',' ). rt_products = fetch_batch( lv_ids ).ENDMETHOD.
" 3. Caching für statische DatenCLASS-DATA: gt_cache TYPE STANDARD TABLE OF ty_product, gv_cache_time TYPE timestamp.
METHOD get_products_cached. " Cache 5 Minuten gültig DATA(lv_now) = utclong_current( ). DATA(lv_age) = cl_abap_tstmp=>subtract( tstmp1 = lv_now tstmp2 = gv_cache_time ).
IF gv_cache_time IS INITIAL OR lv_age > 300. gt_cache = fetch_from_api( ). gv_cache_time = lv_now. ENDIF.
rt_products = gt_cache.ENDMETHOD.Troubleshooting
| Problem | Mögliche Ursache | Lösung |
|---|---|---|
| 401 Unauthorized | Token abgelaufen, falsche Credentials | Communication Arrangement prüfen |
| 403 Forbidden | Fehlende Berechtigungen | API-Scope prüfen |
| 404 Not Found | Falscher Pfad | URL und Pfad validieren |
| 429 Too Many Requests | Rate Limit erreicht | Retry-After Header beachten |
| 500 Server Error | Backend-Problem | Logs prüfen, Retry implementieren |
| Connection Timeout | Netzwerk, Firewall | SAP Cloud Connector prüfen |
| SSL Certificate Error | Ungültiges Zertifikat | Trust Store konfigurieren |
Weiterführende Themen
- RAP mit Custom Entities - Externe Daten in RAP einbinden
- SAP Destination Service - Destinations konfigurieren
- JSON Verarbeitung - JSON Serialisierung und Parsing
- HTTP Client - HTTP Grundlagen in ABAP
- Wrapper-Klassen - Externes API sauber kapseln