Classes Wrapper en ABAP Cloud : rendre accessibles les APIs classiques

Catégorie
ABAP Cloud
Publié
Auteur
Johannes

Les classes wrapper sont un pattern central dans ABAP Cloud pour acceder aux fonctionnalites qui ne sont pas disponibles en tant qu’API released. Cet article montre comment creer vos propres wrappers tout en respectant les bonnes pratiques.

Pourquoi des classes wrapper ?

Dans ABAP Cloud, le concept d’API strict s’applique :

Statut APIUtilisable dans ABAP CloudExemples
Released (C1)OuiCL_SALV_TABLE, CL_HTTP_CLIENT
Not ReleasedNon (directement)FM BAPI_*, reports classiques
DeprecatedNonAPIs obsoletes

Le probleme

De nombreuses fonctions eprouvees ne sont pas directement utilisables dans ABAP Cloud :

  • Modules fonction BAPI
  • Modules Dynpro classiques
  • Classes et methodes non-released
  • Interfaces legacy

La solution : les wrappers

Un wrapper encapsule l’API non-released et la rend disponible en tant qu’API released C1 :

[Code ABAP Cloud] --> [Wrapper (C1)] --> [API Non-Released]

Released vs APIs Non-Released

Verification du statut de release

" Dans ADT: Clic droit sur l'objet -> Properties -> API State
" Ou par programmation:
DATA(lo_api_state) = cl_oo_class=>get_instance( 'CL_HTTP_CLIENT' ).

Identification dans ADT

  • C1 (Released) : Coche verte - utilisable pour ABAP Cloud
  • C0 (Not Released) : Pas de symbole - uniquement On-Premise
  • Deprecated : Barre - ne plus utiliser

Creer une classe wrapper

Etape 1 : Definir l’interface

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.

Etape 2 : Implementer la classe wrapper

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.
" Appel interne de l'API non-released
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.
" Appel du module fonction non-released
" Remarque: Ceci fonctionne uniquement On-Premise ou dans
" Embedded Steampunk, si le FM est disponible
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.

Etape 3 : Definir le release C1

Dans ADT :

  1. Ouvrir la classe
  2. Properties -> API State
  3. Release State: “Released” (C1)
  4. Visibility: “Use in Cloud Development”

Bonnes pratiques pour la conception des wrappers

1. Interface claire

" MAUVAIS: Transmettre tous les parametres
METHODS call_bapi
IMPORTING
it_params TYPE any_table.
" BON: Interface typee et minimale
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. Ne pas exposer les types internes

CLASS zcl_material_wrapper DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
" BON: Definir ses propres types
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.
" Structure interne pour BAPI
TYPES: BEGIN OF ty_bapi_material,
matnr TYPE matnr,
maktx TYPE maktx,
mtart TYPE mtart,
" ... beaucoup d'autres champs BAPI
END OF ty_bapi_material.
ENDCLASS.
CLASS zcl_material_wrapper IMPLEMENTATION.
METHOD get_material.
DATA: ls_bapi_material TYPE ty_bapi_material.
" Appeler le BAPI (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 vers la structure publique
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. Conception stateless

" MAUVAIS: Conserver l'etat dans la classe
CLASS zcl_bad_wrapper DEFINITION.
PRIVATE SECTION.
DATA: mv_current_order TYPE vbeln.
ENDCLASS.
" BON: Passer toutes les donnees via les parametres
CLASS zcl_good_wrapper DEFINITION.
PUBLIC SECTION.
METHODS process_order
IMPORTING
iv_order_id TYPE vbeln
RETURNING
VALUE(rs_result) TYPE ty_result.
ENDCLASS.

Gestion des erreurs dans les wrappers

Definir la classe d’exception

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.

Intercepter et transformer les erreurs

CLASS zcl_sales_order_wrapper IMPLEMENTATION.
METHOD create_order.
DATA: lt_return TYPE bapiret2_t.
" Appeler le BAPI
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.
" Verifier les erreurs
LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>)
WHERE type CA 'EA'.
" Transformer l'erreur en exception propre
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 en cas de succes
IF rv_order_id IS NOT INITIAL.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT"
EXPORTING
wait = abap_true.
ENDIF.
ENDMETHOD.
ENDCLASS.

Pattern Factory pour les wrappers

Pour une instanciation flexible et la testabilite :

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.
" Pour les tests unitaires: injecter des mocks
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.
" Le mock a la priorite (pour les 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.

Utilisation dans le code productif

CLASS zcl_booking_service IMPLEMENTATION.
METHOD create_booking.
" Obtenir le wrapper via la factory
DATA(lo_number_wrapper) = zcl_wrapper_factory=>get_instance(
)->get_number_range_wrapper( ).
" Obtenir le prochain numero de reservation
DATA(ls_number) = lo_number_wrapper->get_next_number(
iv_object = 'ZBOOKING"
).
rs_booking-booking_id = ls_number-number.
" ... logique supplementaire
ENDMETHOD.
ENDCLASS.

Utilisation dans les tests unitaires

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.
" Creer le mock
mo_number_mock = NEW #( ).
mo_number_mock->set_next_number( '0000001234' ).
" Injecter le mock
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 Alternatives

ApprocheAvantagesInconvenients
WrapperControle total, testableEffort, maintenance
Extension IncludeUtilise le standard SAPLimite, pas pour toutes les APIs
Attendre l’API releasedPas de code propreDisponibilite incertaine
Extension Side-by-SideSeparation propreBTP requis

Checklist pour les wrappers

Avant de creer un wrapper, verifier :

  1. Existe-t-il une API Released ?

    • Rechercher dans SAP API Business Hub
    • Rechercher les classes C1 dans ADT
  2. Le wrapper est-il vraiment necessaire ?

    • La fonctionnalite peut-elle etre implementee autrement ?
    • Existe-t-il une approche plus moderne ?
  3. Planifier la conception du wrapper :

    • Interface definie
    • Types propres (ne pas exposer les types SAP internes)
    • Classe d’exception avec des erreurs explicites
    • Factory pour la testabilite
    • Implementation stateless
  4. Documentation :

    • Quelle API non-released est encapsulee
    • Limitations connues
    • Chemin de mise a niveau (si SAP fournit une API released)

Sujets connexes