Wrapper-Klassen sind ein zentrales Pattern in ABAP Cloud, um auf Funktionalität zuzugreifen, die nicht als Released API verfügbar ist. Dieser Artikel zeigt, wie du eigene Wrapper erstellst und dabei Best Practices einhältst.
Warum Wrapper-Klassen?
In ABAP Cloud gilt das strenge API-Konzept:
| API-Status | Nutzbar in ABAP Cloud | Beispiele |
|---|---|---|
| Released (C1) | Ja | CL_SALV_TABLE, CL_HTTP_CLIENT |
| Not Released | Nein (direkt) | FM BAPI_*, klassische Reports |
| Deprecated | Nein | Veraltete APIs |
Das Problem
Viele bewährte Funktionen sind in ABAP Cloud nicht direkt nutzbar:
- BAPI-Funktionsbausteine
- Klassische Dynpro-Module
- Nicht-released Klassen und Methoden
- Legacy-Schnittstellen
Die Lösung: Wrapper
Ein Wrapper kapselt die nicht-released API und stellt sie als C1-released API bereit:
[ABAP Cloud Code] --> [Wrapper (C1)] --> [Non-Released API]Released vs. Non-Released APIs
Prüfen des Release-Status
" In ADT: Rechtsklick auf Objekt -> Properties -> API State" Oder programmatisch:DATA(lo_api_state) = cl_oo_class=>get_instance( 'CL_HTTP_CLIENT' ).Kennzeichnung in ADT
- C1 (Released): Grünes Häkchen - für ABAP Cloud nutzbar
- C0 (Not Released): Kein Symbol - nur On-Premise
- Deprecated: Durchgestrichen - nicht mehr verwenden
Wrapper-Klasse erstellen
Schritt 1: Interface definieren
INTERFACE zif_number_range_wrapper PUBLIC.
TYPES: BEGIN OF ty_number, number TYPE numc10, END OF ty_number.
METHODS get_next_number IMPORTING iv_object TYPE nrobj iv_subobject TYPE nrsobj OPTIONAL RETURNING VALUE(rs_number) TYPE ty_number RAISING cx_number_range_error.
ENDINTERFACE.Schritt 2: Wrapper-Klasse implementieren
CLASS zcl_number_range_wrapper DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_number_range_wrapper.
ALIASES get_next_number FOR zif_number_range_wrapper~get_next_number.
PRIVATE SECTION. METHODS call_number_get_next IMPORTING iv_object TYPE nrobj iv_subobject TYPE nrsobj EXPORTING ev_number TYPE numc10 RAISING cx_number_range_error.
ENDCLASS.
CLASS zcl_number_range_wrapper IMPLEMENTATION.
METHOD zif_number_range_wrapper~get_next_number. " Interner Aufruf der nicht-released API call_number_get_next( EXPORTING iv_object = iv_object iv_subobject = COND #( WHEN iv_subobject IS INITIAL THEN '01' ELSE iv_subobject ) IMPORTING ev_number = rs_number-number ). ENDMETHOD.
METHOD call_number_get_next. " Aufruf des nicht-released Funktionsbausteins " Hinweis: Dies funktioniert nur On-Premise oder in " Embedded Steampunk, wenn der FB verfügbar ist DATA: lv_number TYPE numc10, lv_returncode TYPE char1.
CALL FUNCTION 'NUMBER_GET_NEXT' EXPORTING nr_range_nr = '01' object = iv_object subobject = iv_subobject IMPORTING number = lv_number returncode = lv_returncode EXCEPTIONS OTHERS = 1.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_number_range_error EXPORTING textid = cx_number_range_error=>nr_not_found. ENDIF.
ev_number = lv_number. ENDMETHOD.
ENDCLASS.Schritt 3: C1-Release setzen
In ADT:
- Klasse öffnen
- Properties -> API State
- Release State: “Released” (C1)
- Visibility: “Use in Cloud Development”
Best Practices für Wrapper-Design
1. Klare Schnittstelle
" SCHLECHT: Alle Parameter durchreichenMETHODS call_bapi IMPORTING it_params TYPE any_table.
" GUT: Typisierte, minimale SchnittstelleMETHODS create_sales_order IMPORTING is_header TYPE zif_sales_order_api=>ty_header it_items TYPE zif_sales_order_api=>tt_items RETURNING VALUE(rv_order_id) TYPE vbeln RAISING zcx_sales_order_error.2. Keine internen Typen exponieren
CLASS zcl_material_wrapper DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. " GUT: Eigene Typen definieren TYPES: BEGIN OF ty_material, material_id TYPE matnr, description TYPE maktx, material_type TYPE mtart, base_unit TYPE meins, END OF ty_material.
METHODS get_material IMPORTING iv_material_id TYPE matnr RETURNING VALUE(rs_material) TYPE ty_material RAISING zcx_material_not_found.
PRIVATE SECTION. " Interne Struktur für BAPI TYPES: BEGIN OF ty_bapi_material, matnr TYPE matnr, maktx TYPE maktx, mtart TYPE mtart, " ... viele weitere BAPI-Felder END OF ty_bapi_material.
ENDCLASS.
CLASS zcl_material_wrapper IMPLEMENTATION.
METHOD get_material. DATA: ls_bapi_material TYPE ty_bapi_material.
" BAPI aufrufen (non-released) CALL FUNCTION 'BAPI_MATERIAL_GET_DETAIL' EXPORTING material = iv_material_id IMPORTING material_general_data = ls_bapi_material EXCEPTIONS OTHERS = 1.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_material_not_found EXPORTING material_id = iv_material_id. ENDIF.
" Mapping auf öffentliche Struktur rs_material = VALUE #( material_id = ls_bapi_material-matnr description = ls_bapi_material-maktx material_type = ls_bapi_material-mtart base_unit = ls_bapi_material-meins ). ENDMETHOD.
ENDCLASS.3. Stateless Design
" SCHLECHT: Zustand in der Klasse haltenCLASS zcl_bad_wrapper DEFINITION. PRIVATE SECTION. DATA: mv_current_order TYPE vbeln.ENDCLASS.
" GUT: Alle Daten über Parameter übergebenCLASS zcl_good_wrapper DEFINITION. PUBLIC SECTION. METHODS process_order IMPORTING iv_order_id TYPE vbeln RETURNING VALUE(rs_result) TYPE ty_result.ENDCLASS.Fehlerbehandlung in Wrappern
Exception-Klasse definieren
CLASS zcx_wrapper_error DEFINITION PUBLIC INHERITING FROM cx_static_check FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_t100_message. INTERFACES if_t100_dyn_msg.
CONSTANTS: BEGIN OF api_call_failed, msgid TYPE symsgid VALUE 'ZWRAPPER', msgno TYPE symsgno VALUE '001', attr1 TYPE scx_attrname VALUE 'MV_API_NAME', attr2 TYPE scx_attrname VALUE 'MV_ERROR_TEXT', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF api_call_failed,
BEGIN OF not_authorized, msgid TYPE symsgid VALUE 'ZWRAPPER', msgno TYPE symsgno VALUE '002', attr1 TYPE scx_attrname VALUE 'MV_OPERATION', attr2 TYPE scx_attrname VALUE '', attr3 TYPE scx_attrname VALUE '', attr4 TYPE scx_attrname VALUE '', END OF not_authorized.
DATA: mv_api_name TYPE string READ-ONLY, mv_error_text TYPE string READ-ONLY, mv_operation TYPE string READ-ONLY.
METHODS constructor IMPORTING textid LIKE if_t100_message=>t100key OPTIONAL previous LIKE previous OPTIONAL api_name TYPE string OPTIONAL error_text TYPE string OPTIONAL operation TYPE string OPTIONAL.
ENDCLASS.
CLASS zcx_wrapper_error IMPLEMENTATION.
METHOD constructor. super->constructor( previous = previous ). mv_api_name = api_name. mv_error_text = error_text. mv_operation = operation.
CLEAR me->textid. IF textid IS INITIAL. if_t100_message~t100key = if_t100_message=>default_textid. ELSE. if_t100_message~t100key = textid. ENDIF. ENDMETHOD.
ENDCLASS.Fehler abfangen und transformieren
CLASS zcl_sales_order_wrapper IMPLEMENTATION.
METHOD create_order. DATA: lt_return TYPE bapiret2_t.
" BAPI aufrufen CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING order_header_in = is_header IMPORTING salesdocument = rv_order_id TABLES order_items_in = it_items return = lt_return.
" Fehler prüfen LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'.
" Fehler in eigene Exception transformieren RAISE EXCEPTION TYPE zcx_wrapper_error EXPORTING textid = zcx_wrapper_error=>api_call_failed api_name = 'BAPI_SALESORDER_CREATEFROMDAT2' error_text = <return>-message. ENDLOOP.
" Commit Work bei Erfolg IF rv_order_id IS NOT INITIAL. CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true. ENDIF. ENDMETHOD.
ENDCLASS.Factory-Pattern für Wrapper
Für flexible Instanziierung und Testbarkeit:
CLASS zcl_wrapper_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_wrapper_factory.
METHODS get_number_range_wrapper RETURNING VALUE(ro_wrapper) TYPE REF TO zif_number_range_wrapper.
METHODS get_material_wrapper RETURNING VALUE(ro_wrapper) TYPE REF TO zif_material_wrapper.
" Für Unit Tests: Mocks injizieren METHODS set_mock_number_range IMPORTING io_mock TYPE REF TO zif_number_range_wrapper.
PRIVATE SECTION. CLASS-DATA: go_instance TYPE REF TO zcl_wrapper_factory. DATA: mo_number_range_mock TYPE REF TO zif_number_range_wrapper.
ENDCLASS.
CLASS zcl_wrapper_factory IMPLEMENTATION.
METHOD get_instance. IF go_instance IS NOT BOUND. go_instance = NEW #( ). ENDIF. ro_instance = go_instance. ENDMETHOD.
METHOD get_number_range_wrapper. " Mock hat Vorrang (für Tests) IF mo_number_range_mock IS BOUND. ro_wrapper = mo_number_range_mock. ELSE. ro_wrapper = NEW zcl_number_range_wrapper( ). ENDIF. ENDMETHOD.
METHOD get_material_wrapper. ro_wrapper = NEW zcl_material_wrapper( ). ENDMETHOD.
METHOD set_mock_number_range. mo_number_range_mock = io_mock. ENDMETHOD.
ENDCLASS.Nutzung in produktivem Code
CLASS zcl_booking_service IMPLEMENTATION.
METHOD create_booking. " Wrapper über Factory holen DATA(lo_number_wrapper) = zcl_wrapper_factory=>get_instance( )->get_number_range_wrapper( ).
" Nächste Buchungsnummer holen DATA(ls_number) = lo_number_wrapper->get_next_number( iv_object = 'ZBOOKING' ).
rs_booking-booking_id = ls_number-number. " ... weitere Logik ENDMETHOD.
ENDCLASS.Nutzung in Unit Tests
CLASS ltc_booking_service DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO zcl_booking_service, mo_number_mock TYPE REF TO ltd_number_range_mock.
METHODS setup. METHODS test_create_booking FOR TESTING.
ENDCLASS.
CLASS ltc_booking_service IMPLEMENTATION.
METHOD setup. " Mock erstellen mo_number_mock = NEW #( ). mo_number_mock->set_next_number( '0000001234' ).
" Mock injizieren zcl_wrapper_factory=>get_instance( )->set_mock_number_range( mo_number_mock ).
mo_cut = NEW #( ). ENDMETHOD.
METHOD test_create_booking. DATA(ls_booking) = mo_cut->create_booking( is_input ).
cl_abap_unit_assert=>assert_equals( exp = '0000001234' act = ls_booking-booking_id ). ENDMETHOD.
ENDCLASS.Wrapper vs. Alternativen
| Ansatz | Vorteile | Nachteile |
|---|---|---|
| Wrapper | Volle Kontrolle, testbar | Aufwand, Wartung |
| Extension Include | SAP-Standard nutzen | Eingeschränkt, nicht für alle APIs |
| Released API warten | Kein eigener Code | Ungewisse Verfügbarkeit |
| Side-by-Side Extension | Sauber getrennt | BTP erforderlich |
Checkliste für Wrapper
Vor dem Erstellen eines Wrappers prüfen:
-
Gibt es eine Released API?
- SAP API Business Hub durchsuchen
- In ADT nach C1-Klassen suchen
-
Ist der Wrapper wirklich nötig?
- Kann die Funktionalität anders implementiert werden?
- Gibt es einen moderneren Ansatz?
-
Wrapper-Design planen:
- Interface definiert
- Eigene Typen (keine internen SAP-Typen exponieren)
- Exception-Klasse mit sprechenden Fehlern
- Factory für Testbarkeit
- Stateless implementiert
-
Dokumentation:
- Welche non-released API wird gekapselt
- Bekannte Einschränkungen
- Upgrade-Pfad (falls SAP Released API bereitstellt)
Weiterführende Themen
- ABAP Cloud Migration Guide - Von Classic zu ABAP Cloud
- Clean Core Strategie - Die Zukunft der SAP-Entwicklung
- Developer Extensibility - Erweiterungen in ABAP Cloud