Wrapper-Klassen in ABAP Cloud: Classic APIs zugänglich machen

kategorie
ABAP Cloud
Veröffentlicht
autor
Johannes

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-StatusNutzbar in ABAP CloudBeispiele
Released (C1)JaCL_SALV_TABLE, CL_HTTP_CLIENT
Not ReleasedNein (direkt)FM BAPI_*, klassische Reports
DeprecatedNeinVeraltete 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:

  1. Klasse öffnen
  2. Properties -> API State
  3. Release State: “Released” (C1)
  4. Visibility: “Use in Cloud Development”

Best Practices für Wrapper-Design

1. Klare Schnittstelle

" SCHLECHT: Alle Parameter durchreichen
METHODS call_bapi
IMPORTING
it_params TYPE any_table.
" GUT: Typisierte, minimale Schnittstelle
METHODS 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 halten
CLASS zcl_bad_wrapper DEFINITION.
PRIVATE SECTION.
DATA: mv_current_order TYPE vbeln.
ENDCLASS.
" GUT: Alle Daten über Parameter übergeben
CLASS 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

AnsatzVorteileNachteile
WrapperVolle Kontrolle, testbarAufwand, Wartung
Extension IncludeSAP-Standard nutzenEingeschränkt, nicht für alle APIs
Released API wartenKein eigener CodeUngewisse Verfügbarkeit
Side-by-Side ExtensionSauber getrenntBTP erforderlich

Checkliste für Wrapper

Vor dem Erstellen eines Wrappers prüfen:

  1. Gibt es eine Released API?

    • SAP API Business Hub durchsuchen
    • In ADT nach C1-Klassen suchen
  2. Ist der Wrapper wirklich nötig?

    • Kann die Funktionalität anders implementiert werden?
    • Gibt es einen moderneren Ansatz?
  3. Wrapper-Design planen:

    • Interface definiert
    • Eigene Typen (keine internen SAP-Typen exponieren)
    • Exception-Klasse mit sprechenden Fehlern
    • Factory für Testbarkeit
    • Stateless implementiert
  4. Dokumentation:

    • Welche non-released API wird gekapselt
    • Bekannte Einschränkungen
    • Upgrade-Pfad (falls SAP Released API bereitstellt)

Weiterführende Themen