RAP External API Consumption - REST Services in ABAP Cloud konsumieren

kategorie
RAP
Veröffentlicht
autor
Johannes

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

AspektBeschreibung
HTTP ClientCL_WEB_HTTP_CLIENT_MANAGER für Cloud-native Aufrufe
KonfigurationCommunication Arrangements für sichere Credentials
JSON/UI2/CL_JSON oder XCO für Serialisierung
RAP-IntegrationCustom Entities oder Determinations/Actions
FehlerbehandlungRetry-Logik, Timeouts, Exception Handling

Voraussetzungen

Bevor du externe APIs in ABAP Cloud konsumieren kannst, benötigst du:

  1. Communication Scenario - Definiert den Kommunikationstyp
  2. Communication System - Beschreibt das Zielsystem
  3. 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 ObjectsCommunication ManagementCommunication 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:

FeldWert
ScenarioZ_WEATHER_API
Arrangement NameZ_WEATHER_PROD
Communication SystemWEATHER_API_SYSTEM
Service Path/api/v1
AuthenticationOAuth 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 Struktur
TYPES:
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 camelCase
TYPES:
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 erneuert
METHOD 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

EmpfehlungBeschreibung
Communication ArrangementsCredentials zentral verwalten, nie im Code
Retry-LogikExponential Backoff für transiente Fehler
Timeout setzenAngemessene Timeouts konfigurieren
close() aufrufenHTTP Client immer schließen (auch bei Fehlern)
Error HandlingSpezifische Exceptions für API-Fehler
LoggingAPI-Aufrufe für Debugging protokollieren
Batch-RequestsMehrere Datensätze in einem Call wenn möglich

DON’T

VermeidenGrund
Credentials im CodeSicherheitsrisiko, schwer zu ändern
Unbegrenztes RetryKann System überlasten
Synchrone lange CallsUI blockiert, Timeout-Risiko
Ignorierte FehlerFührt zu inkonsistenten Daten
Fehlende ValidierungAPI-Responses immer validieren

Performance-Tipps

" 1. Connection Reuse - Client wiederverwenden
DATA: 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 Requests
METHOD 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 Daten
CLASS-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

ProblemMögliche UrsacheLösung
401 UnauthorizedToken abgelaufen, falsche CredentialsCommunication Arrangement prüfen
403 ForbiddenFehlende BerechtigungenAPI-Scope prüfen
404 Not FoundFalscher PfadURL und Pfad validieren
429 Too Many RequestsRate Limit erreichtRetry-After Header beachten
500 Server ErrorBackend-ProblemLogs prüfen, Retry implementieren
Connection TimeoutNetzwerk, FirewallSAP Cloud Connector prüfen
SSL Certificate ErrorUngültiges ZertifikatTrust Store konfigurieren

Weiterführende Themen