SAP Destination Service : Connecter des systèmes externes depuis ABAP Cloud

Catégorie
Integration
Publié
Auteur
Johannes

Le SAP Destination Service est un service BTP central pour la connexion sécurisée aux systèmes externes. Il gère de manière centralisée les paramètres de connexion, les données d’authentification et les certificats, de sorte que les applications ABAP n’ont pas besoin de stocker des credentials sensibles dans le code.

Pourquoi le Destination Service ?

Le Destination Service résout plusieurs défis lors de l’intégration de systèmes externes :

AspectSans Destination ServiceAvec Destination Service
CredentialsDans le code ou tables de customizingGérés centralement dans BTP
Gestion des URLCodés en dur ou SM59Configurables centralement
AuthentificationÀ implémenter manuellementAutomatique (OAuth, Cert, etc.)
Changement d’environnementModifications de code nécessairesSeulement changement de configuration
SécuritéRisque de fuite de credentialsSecrets jamais dans le code
MonitoringPas de vue d’ensemble centraleDashboard BTP Cockpit

Configuration dans le BTP Cockpit

1. Créer une destination

Naviguez dans le BTP Cockpit vers votre sous-compte et sélectionnez 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. Types d’authentification

Le Destination Service prend en charge différentes méthodes d’authentification :

TypeDescriptionCas d’usage
NoAuthenticationPas d’authentificationAPIs publiques
BasicAuthenticationNom d’utilisateur/mot de passeSystèmes legacy
OAuth2ClientCredentialsClient ID/SecretMachine-to-Machine
OAuth2SAMLBearerAssertionÉchange de token SAMLPrincipal Propagation
OAuth2UserTokenExchangeTransmission de token utilisateurConserver le contexte utilisateur
ClientCertificateAuthenticationCertificat X.509APIs hautement sécurisées
PrincipalPropagationSAP Cloud ConnectorSystèmes On-Premise

3. Communication Arrangement dans le système ABAP

Pour utiliser les destinations depuis ABAP Cloud, vous avez besoin d’un 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 │
└──────────────────────────────────────────────────────────────┘

Récupérer une destination dans ABAP

Utiliser l’API Destination Service

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.

Modes d’authentification

Le paramètre i_authn_mode contrôle l’obtention du token :

" Mode 1 : Authentification spécifique au service (recommandé)
" Utilise les credentials configurés dans la destination
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'WEATHER_API"
i_authn_mode = if_a4c_cp_service=>service_specific ).
" Mode 2 : User Propagation
" Transmet le contexte de l'utilisateur métier actuel
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'SAP_SYSTEM"
i_authn_mode = if_a4c_cp_service=>user_propagation ).
" Mode 3 : Named User
" Utilise un utilisateur technique fixe de la destination
ro_destination = cl_http_destination_provider=>create_by_cloud_destination(
i_name = 'LEGACY_SYSTEM"
i_authn_mode = if_a4c_cp_service=>named_user ).

Client HTTP avec Destination

Requête GET simple

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.

Requête POST avec corps JSON

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.

Authentification OAuth2

Flux OAuth2 Client Credentials

L’authentification Machine-to-Machine la plus courante :

┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ 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 │
└─────────────┘ └──────────────────┘ └──────────────┘

Configuration de destination pour 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 avec échange de token utilisateur

Pour les scénarios où le contexte de l’utilisateur métier doit être transmis :

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.

Exemple complet : Intégration d’API REST

Classe wrapper d’API

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.

Configuration de destination (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

Pour un accès authentifié (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>

Gestion des erreurs

Gestion robuste des erreurs

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.

Logique de nouvelle tentative

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.

Bonnes pratiques

SujetRecommandation
Noms de destinationNoms parlants (ex. CRM_PRODUCTION, WEATHER_DEV)
EnvironnementsDestinations séparées pour DEV/QA/PROD
Gestion des erreursToujours gérer cx_http_dest_provider_error et cx_web_http_client_error
Pool de connexionsLibérer le client HTTP après utilisation avec close()
TimeoutsConfigurer des timeouts appropriés dans la destination
LoggingLogger les appels API pour le débogage (sans credentials)
Mise en cache des tokensLe Destination Service met automatiquement en cache les tokens
MonitoringUtiliser SAP Cloud ALM pour le monitoring des API

Sujets avancés