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 API | Utilisable dans ABAP Cloud | Exemples |
|---|---|---|
| Released (C1) | Oui | CL_SALV_TABLE, CL_HTTP_CLIENT |
| Not Released | Non (directement) | FM BAPI_*, reports classiques |
| Deprecated | Non | APIs 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 :
- Ouvrir la classe
- Properties -> API State
- Release State: “Released” (C1)
- Visibility: “Use in Cloud Development”
Bonnes pratiques pour la conception des wrappers
1. Interface claire
" MAUVAIS: Transmettre tous les parametresMETHODS call_bapi IMPORTING it_params TYPE any_table.
" BON: Interface typee et minimaleMETHODS 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 classeCLASS zcl_bad_wrapper DEFINITION. PRIVATE SECTION. DATA: mv_current_order TYPE vbeln.ENDCLASS.
" BON: Passer toutes les donnees via les parametresCLASS 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
| Approche | Avantages | Inconvenients |
|---|---|---|
| Wrapper | Controle total, testable | Effort, maintenance |
| Extension Include | Utilise le standard SAP | Limite, pas pour toutes les APIs |
| Attendre l’API released | Pas de code propre | Disponibilite incertaine |
| Extension Side-by-Side | Separation propre | BTP requis |
Checklist pour les wrappers
Avant de creer un wrapper, verifier :
-
Existe-t-il une API Released ?
- Rechercher dans SAP API Business Hub
- Rechercher les classes C1 dans ADT
-
Le wrapper est-il vraiment necessaire ?
- La fonctionnalite peut-elle etre implementee autrement ?
- Existe-t-il une approche plus moderne ?
-
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
-
Documentation :
- Quelle API non-released est encapsulee
- Limitations connues
- Chemin de mise a niveau (si SAP fournit une API released)
Sujets connexes
- Guide de migration ABAP Cloud - De Classic vers ABAP Cloud
- Strategie Clean Core - L’avenir du developpement SAP
- Developer Extensibility - Extensions dans ABAP Cloud