SAP Destination Service: Externe Systeme aus ABAP Cloud anbinden

kategorie
Integration
Veröffentlicht
autor
Johannes

Der SAP Destination Service ist ein zentraler BTP-Service für die sichere Verbindung zu externen Systemen. Er verwaltet Verbindungsparameter, Authentifizierungsdaten und Zertifikate zentral, sodass ABAP-Anwendungen keine sensiblen Credentials im Code speichern müssen.

Warum Destination Service?

Der Destination Service löst mehrere Herausforderungen bei der Integration externer Systeme:

AspektOhne Destination ServiceMit Destination Service
CredentialsIm Code oder Customizing-TabellenZentral in BTP verwaltet
URL-VerwaltungHardcoded oder SM59Zentral konfigurierbar
AuthentifizierungManuell implementierenAutomatisch (OAuth, Cert, etc.)
UmgebungswechselCode-Änderungen nötigNur Konfigurationsänderung
SicherheitRisiko bei Credential-LeaksSecrets nie im Code
MonitoringKeine zentrale ÜbersichtBTP Cockpit Dashboard

Konfiguration im BTP Cockpit

1. Destination erstellen

Navigiere im BTP Cockpit zu deinem Subaccount und wähle ConnectivityDestinations:

┌──────────────────────────────────────────────────────────────┐
│ Destination Configuration │
├──────────────────────────────────────────────────────────────┤
│ Name: WEATHER_API │
│ Type: HTTP │
│ URL: https://api.weatherservice.com/v1 │
│ Proxy Type: Internet │
│ Authentication: OAuth2ClientCredentials │
│ │
│ ┌─ OAuth2 Settings ───────────────────────────────────────┐ │
│ │ Client ID: abc123-client-id │ │
│ │ Client Secret: ******** │ │
│ │ Token URL: https://auth.weatherservice.com/token │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Additional Properties: │
│ ├── HTML5.DynamicDestination: true │
│ └── WebIDEEnabled: true │
└──────────────────────────────────────────────────────────────┘

2. Authentifizierungstypen

Der Destination Service unterstützt verschiedene Authentifizierungsmethoden:

TypBeschreibungUse Case
NoAuthenticationKeine AuthentifizierungÖffentliche APIs
BasicAuthenticationBenutzername/PasswortLegacy-Systeme
OAuth2ClientCredentialsClient ID/SecretMachine-to-Machine
OAuth2SAMLBearerAssertionSAML Token ExchangePrincipal Propagation
OAuth2UserTokenExchangeUser Token WeitergabeUser-Kontext erhalten
ClientCertificateAuthenticationX.509 ZertifikatHochsichere APIs
PrincipalPropagationSAP Cloud ConnectorOn-Premise Systeme

3. Communication Arrangement im ABAP-System

Um Destinations aus ABAP Cloud zu nutzen, benötigst du ein Communication Arrangement:

┌──────────────────────────────────────────────────────────────┐
│ Communication Arrangement │
├──────────────────────────────────────────────────────────────┤
│ Scenario: SAP_COM_0276 (Destination Service Integration)│
│ │
│ Communication System: BTP_DESTINATION_SERVICE │
│ ├── Host: destination-configuration.cfapps.eu10.hana... │
│ ├── Port: 443 │
│ └── Auth: OAuth 2.0 (Service Binding) │
│ │
│ Inbound Services: - │
│ Outbound Services: │
│ └── Destination Service API: Active │
└──────────────────────────────────────────────────────────────┘

Destination in ABAP abrufen

Destination Service API nutzen

CLASS zcl_destination_demo DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
METHODS get_destination
RETURNING VALUE(ro_destination) TYPE REF TO if_http_destination
RAISING cx_http_dest_provider_error.
ENDCLASS.
CLASS zcl_destination_demo IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
DATA(lo_destination) = get_destination( ).
out->write( |Destination erfolgreich abgerufen| ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest).
out->write( |Fehler: { lx_dest->get_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD get_destination.
" Destination vom BTP Destination Service abrufen
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'WEATHER_API'
i_service_instance_name = 'destination-service-instance'
i_authn_mode = if_a4c_cp_service=>service_specific ).
ENDMETHOD.
ENDCLASS.

Authentifizierungsmodi

Der Parameter i_authn_mode steuert die Token-Beschaffung:

" Modus 1: Service-spezifische Authentifizierung (empfohlen)
" Verwendet die in der Destination konfigurierten Credentials
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'WEATHER_API'
i_authn_mode = if_a4c_cp_service=>service_specific ).
" Modus 2: User Propagation
" Gibt den aktuellen Business-User-Kontext weiter
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'SAP_SYSTEM'
i_authn_mode = if_a4c_cp_service=>user_propagation ).
" Modus 3: Named User
" Verwendet festen technischen Benutzer aus Destination
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'LEGACY_SYSTEM'
i_authn_mode = if_a4c_cp_service=>named_user ).

HTTP Client mit Destination

Einfacher GET-Request

CLASS zcl_weather_client DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_weather,
city TYPE string,
temperature TYPE decfloat16,
humidity TYPE i,
description TYPE string,
END OF ty_weather.
METHODS get_weather
IMPORTING iv_city TYPE string
RETURNING VALUE(rs_result) TYPE ty_weather
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_weather_client IMPLEMENTATION.
METHOD get_weather.
" 1. Destination abrufen
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'WEATHER_API'
i_authn_mode = if_a4c_cp_service=>service_specific ).
" 2. HTTP Client erstellen
DATA(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={ iv_city }| ).
" 4. Request ausführen
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
" 5. Response verarbeiten
DATA(lv_status) = lo_response->get_status( )-code.
IF lv_status = 200.
DATA(lv_json) = lo_response->get_text( ).
" JSON parsen
/ui2/cl_json=>deserialize(
EXPORTING json = lv_json
CHANGING data = rs_result ).
ELSE.
RAISE EXCEPTION TYPE cx_web_http_client_error.
ENDIF.
" 6. Client schließen
lo_client->close( ).
ENDMETHOD.
ENDCLASS.

POST-Request mit JSON-Body

CLASS zcl_order_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_order_request,
customer_id TYPE string,
product_id TYPE string,
quantity TYPE i,
END OF ty_order_request,
BEGIN OF ty_order_response,
order_id TYPE string,
status TYPE string,
total_amount TYPE decfloat16,
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_service IMPLEMENTATION.
METHOD create_order.
" Destination und Client
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'ORDER_SERVICE_API'
i_authn_mode = if_a4c_cp_service=>service_specific ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
" Request konfigurieren
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( '/orders' ).
" JSON-Body erstellen
DATA(lv_json) = /ui2/cl_json=>serialize(
data = is_order
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
lo_request->set_text( lv_json ).
lo_request->set_header_field(
i_name = 'Content-Type'
i_value = 'application/json' ).
" POST ausführen
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Response verarbeiten
IF lo_response->get_status( )-code = 201.
/ui2/cl_json=>deserialize(
EXPORTING json = lo_response->get_text( )
CHANGING data = rs_result ).
ENDIF.
lo_client->close( ).
ENDMETHOD.
ENDCLASS.

OAuth2-Authentifizierung

OAuth2 Client Credentials Flow

Die häufigste Machine-to-Machine-Authentifizierung:

┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ ABAP Cloud │ │ Destination Svc │ │ OAuth Server │
│ │ │ │ │ │
│ Request │────>│ 1. Get Dest │ │ │
│ Destination│ │ │ │ │
│ │<────│ 2. Dest Config │ │ │
│ │ │ │ │ │
│ HTTP Call │────>│ 3. Get Token │────>│ │
│ │ │ (Client Creds) │ │ │
│ │<────│ 4. Access Token │<────│ │
│ │ │ │ │ │
│ API Call │─────────────────────────────>│ 5. Validate │
│ + Token │ │ │ │ Token │
│ │<─────────────────────────────│ 6. Response │
└─────────────┘ └──────────────────┘ └──────────────┘

Destination-Konfiguration für OAuth2

Name: EXTERNAL_CRM_API
Type: HTTP
URL: https://api.crm-system.com/v2
Proxy Type: Internet
Authentication: OAuth2ClientCredentials
Token Service URL: https://auth.crm-system.com/oauth/token
Client ID: crm-integration-client
Client Secret: ********
Additional Properties:
tokenServiceURLType: Dedicated
scope: api.read api.write

OAuth2 mit User Token Exchange

Für Szenarien, in denen der Business-User-Kontext weitergegeben werden soll:

METHOD call_with_user_context.
" Destination mit User Token Exchange
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'BACKEND_SYSTEM'
i_authn_mode = if_a4c_cp_service=>user_propagation ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
" Der HTTP-Request enthält automatisch den User-Token
" Backend-System kann den Benutzer identifizieren
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
lo_client->close( ).
ENDMETHOD.

Vollständiges Beispiel: REST API Integration

API-Wrapper-Klasse

CLASS zcl_github_api DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_repository,
id TYPE i,
name TYPE string,
full_name TYPE string,
description TYPE string,
html_url TYPE string,
stargazers_count TYPE i,
END OF ty_repository,
tt_repositories TYPE STANDARD TABLE OF ty_repository WITH EMPTY KEY.
METHODS constructor
RAISING cx_http_dest_provider_error.
METHODS get_user_repos
IMPORTING iv_username TYPE string
RETURNING VALUE(rt_result) TYPE tt_repositories
RAISING cx_web_http_client_error.
METHODS create_issue
IMPORTING iv_owner TYPE string
iv_repo TYPE string
iv_title TYPE string
iv_body TYPE string
RETURNING VALUE(rv_url) TYPE string
RAISING cx_web_http_client_error.
PRIVATE SECTION.
DATA mo_destination TYPE REF TO if_http_destination.
METHODS execute_request
IMPORTING iv_path TYPE string
iv_method TYPE i DEFAULT if_web_http_client=>get
iv_body TYPE string OPTIONAL
RETURNING VALUE(rv_result) TYPE string
RAISING cx_web_http_client_error.
ENDCLASS.
CLASS zcl_github_api IMPLEMENTATION.
METHOD constructor.
" Destination einmalig abrufen
mo_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'GITHUB_API'
i_authn_mode = if_a4c_cp_service=>service_specific ).
ENDMETHOD.
METHOD get_user_repos.
DATA(lv_json) = execute_request( iv_path = |/users/{ iv_username }/repos| ).
/ui2/cl_json=>deserialize(
EXPORTING json = lv_json
CHANGING data = rt_result ).
ENDMETHOD.
METHOD create_issue.
DATA: BEGIN OF ls_issue,
title TYPE string,
body TYPE string,
END OF ls_issue.
ls_issue-title = iv_title.
ls_issue-body = iv_body.
DATA(lv_body) = /ui2/cl_json=>serialize(
data = ls_issue
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
DATA(lv_json) = execute_request(
iv_path = |/repos/{ iv_owner }/{ iv_repo }/issues|
iv_method = if_web_http_client=>post
iv_body = lv_body ).
" URL aus Response extrahieren
DATA: BEGIN OF ls_response,
html_url TYPE string,
END OF ls_response.
/ui2/cl_json=>deserialize(
EXPORTING json = lv_json
CHANGING data = ls_response ).
rv_url = ls_response-html_url.
ENDMETHOD.
METHOD execute_request.
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
i_destination = mo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( iv_path ).
IF iv_body IS NOT INITIAL.
lo_request->set_text( iv_body ).
lo_request->set_header_field(
i_name = 'Content-Type'
i_value = 'application/json' ).
ENDIF.
lo_request->set_header_field(
i_name = 'Accept'
i_value = 'application/vnd.github.v3+json' ).
DATA(lo_response) = lo_client->execute( iv_method ).
DATA(lv_status) = lo_response->get_status( )-code.
IF lv_status >= 200 AND lv_status < 300.
rv_result = lo_response->get_text( ).
ELSE.
DATA(lv_error) = lo_response->get_text( ).
lo_client->close( ).
RAISE EXCEPTION TYPE cx_web_http_client_error
EXPORTING
textid = cx_web_http_client_error=>http_error.
ENDIF.
lo_client->close( ).
ENDMETHOD.
ENDCLASS.

Destination-Konfiguration (GitHub)

Name: GITHUB_API
Type: HTTP
URL: https://api.github.com
Proxy Type: Internet
Authentication: NoAuthentication (oder OAuth2ClientCredentials für höhere Rate Limits)
Additional Properties:
User-Agent: ABAP-Cloud-Integration

Für authentifizierten Zugriff (Personal Access Token):

Name: GITHUB_API
Type: HTTP
URL: https://api.github.com
Proxy Type: Internet
Authentication: BasicAuthentication
User: <github-username>
Password: <personal-access-token>

Error Handling

Robuste Fehlerbehandlung

CLASS zcl_api_client DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CLASS-METHODS call_api
IMPORTING iv_destination TYPE string
iv_path TYPE string
RETURNING VALUE(rv_result) TYPE string
RAISING zcx_api_error.
ENDCLASS.
CLASS zcl_api_client IMPLEMENTATION.
METHOD call_api.
DATA lo_client TYPE REF TO if_web_http_client.
TRY.
" Destination abrufen
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = iv_destination
i_authn_mode = if_a4c_cp_service=>service_specific ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest).
" Destination nicht gefunden oder Konfigurationsfehler
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING
previous = lx_dest
textid = zcx_api_error=>destination_error.
ENDTRY.
TRY.
" Client erstellen und Request ausführen
lo_client = cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( iv_path ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
DATA(lv_status) = lo_response->get_status( )-code.
CASE lv_status.
WHEN 200 OR 201.
rv_result = lo_response->get_text( ).
WHEN 401.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>authentication_failed.
WHEN 403.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>authorization_failed.
WHEN 404.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>not_found.
WHEN 429.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>rate_limit_exceeded.
WHEN 500 OR 502 OR 503 OR 504.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>server_error.
WHEN OTHERS.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING textid = zcx_api_error=>unexpected_error.
ENDCASE.
CATCH cx_web_http_client_error INTO DATA(lx_http).
" Netzwerkfehler, Timeout, etc.
RAISE EXCEPTION TYPE zcx_api_error
EXPORTING
previous = lx_http
textid = zcx_api_error=>connection_error.
ENDTRY.
IF lo_client IS BOUND.
lo_client->close( ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Retry-Logik

METHOD call_with_retry.
CONSTANTS: lc_max_retries TYPE i VALUE 3,
lc_retry_delay TYPE i VALUE 1000. " Millisekunden
DATA lv_retries TYPE i.
WHILE lv_retries < lc_max_retries.
TRY.
rv_result = call_api(
iv_destination = iv_destination
iv_path = iv_path ).
RETURN. " Erfolg
CATCH zcx_api_error INTO DATA(lx_error).
" Nur bei temporären Fehlern retry
IF lx_error->is_retryable( ).
lv_retries = lv_retries + 1.
IF lv_retries < lc_max_retries.
" Warten vor erneutem Versuch
cl_abap_session=>sleep( lc_retry_delay * lv_retries ).
ELSE.
RAISE EXCEPTION lx_error.
ENDIF.
ELSE.
RAISE EXCEPTION lx_error.
ENDIF.
ENDTRY.
ENDWHILE.
ENDMETHOD.

Best Practices

ThemaEmpfehlung
Destination-NamenSprechende Namen (z.B. CRM_PRODUCTION, WEATHER_DEV)
EnvironmentsSeparate Destinations für DEV/QA/PROD
Error HandlingImmer cx_http_dest_provider_error und cx_web_http_client_error behandeln
Connection PoolHTTP-Client nach Nutzung mit close() freigeben
TimeoutsAngemessene Timeouts in Destination konfigurieren
LoggingAPI-Aufrufe für Debugging loggen (ohne Credentials)
Token CachingDestination Service cached Tokens automatisch
MonitoringSAP Cloud ALM für API-Monitoring nutzen

Weiterführende Themen