Le pattern Repository abstrait l’accès aux données et découple la logique métier de la couche de persistance. En ABAP Cloud, il est particulièrement précieux pour les domaines complexes, l’intégration legacy et les architectures testables.
Concept de base
Un repository représente une collection d’objets de domaine et fournit des méthodes pour charger, sauvegarder et rechercher. La logique métier ne travaille qu’avec l’interface du repository, pas directement avec la base de données.
┌─────────────────────────────────────────────┐│ Logique métier ││ (Connaît uniquement l'interface Repository) │└───────────────────┬─────────────────────────┘ │ Utilise ▼┌─────────────────────────────────────────────┐│ Interface Repository ││ find_by_id(), find_all(), save() │└───────────────────┬─────────────────────────┘ │ Implémenté par ┌───────────┴───────────┐ ▼ ▼┌───────────────┐ ┌───────────────┐│ DB-Repository │ │ Mock-Repository││ (Production) │ │ (Tests) │└───────────────┘ └───────────────┘Différence avec l’accès direct à la base de données
| Aspect | Accès direct | Pattern Repository |
|---|---|---|
| Testabilité | Difficile (BD requise) | Facile (Mocks) |
| Maintenabilité | Instructions SQL dispersées | Centralisé |
| Flexibilité | Lié rigidement à la BD | Échangeable |
| Abstraction | Aucune | Objets de domaine |
| Complexité | Faible | Plus élevée |
Quand utiliser le pattern Repository ?
Pertinent pour :
- Logique de domaine complexe
- Plusieurs sources de données (BD, RFC, HTTP)
- Exigences de test élevées
- Intégration legacy (Unmanaged RAP)
Surcharge pour :
- Opérations CRUD simples
- Scénarios RAP purement Managed
- Prototypes et MVPs
Définition de l’interface Repository
L’interface définit le contrat pour l’accès aux données. Elle utilise des objets de domaine, pas des structures de base de données.
INTERFACE zif_order_repository PUBLIC.
TYPES: BEGIN OF ty_order, order_id TYPE sysuuid_x16, customer_id TYPE sysuuid_x16, order_date TYPE dats, status TYPE char2, total_amount TYPE p LENGTH 16 DECIMALS 2, currency TYPE waerk, END OF ty_order, tt_orders TYPE STANDARD TABLE OF ty_order WITH KEY order_id.
TYPES: BEGIN OF ty_order_item, item_id TYPE sysuuid_x16, order_id TYPE sysuuid_x16, product_id TYPE sysuuid_x16, quantity TYPE i, unit_price TYPE p LENGTH 16 DECIMALS 2, currency TYPE waerk, END OF ty_order_item, tt_order_items TYPE STANDARD TABLE OF ty_order_item WITH KEY item_id.
" Lecture METHODS find_by_id IMPORTING iv_order_id TYPE sysuuid_x16 RETURNING VALUE(rs_order) TYPE ty_order RAISING zcx_not_found.
METHODS find_all RETURNING VALUE(rt_orders) TYPE tt_orders.
METHODS find_by_customer IMPORTING iv_customer_id TYPE sysuuid_x16 RETURNING VALUE(rt_orders) TYPE tt_orders.
METHODS find_by_status IMPORTING iv_status TYPE char2 RETURNING VALUE(rt_orders) TYPE tt_orders.
" Items METHODS get_items IMPORTING iv_order_id TYPE sysuuid_x16 RETURNING VALUE(rt_items) TYPE tt_order_items.
" Écriture METHODS save IMPORTING is_order TYPE ty_order it_items TYPE tt_order_items OPTIONAL RAISING zcx_save_error.
METHODS delete IMPORTING iv_order_id TYPE sysuuid_x16 RAISING zcx_not_found.
" Vérification d'existence METHODS exists IMPORTING iv_order_id TYPE sysuuid_x16 RETURNING VALUE(rv_exists) TYPE abap_bool.
ENDINTERFACE.Principes de conception d’interface
- Utiliser le langage du domaine:
find_by_customerau lieu deselect_where_customer - Exceptions au lieu de valeurs de retour: Gestion d’erreur claire
- Pas de détails techniques: Pas de SQL, pas de structure de table dans l’interface
- Entrées immuables: Uniquement IMPORTING, pas de CHANGING
Implémentation du Repository
L’implémentation encapsule tous les accès à la base de données et traduit entre les objets de domaine et les structures de base de données.
CLASS zcl_order_repository DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_order_repository.
ALIASES: find_by_id FOR zif_order_repository~find_by_id, find_all FOR zif_order_repository~find_all, find_by_customer FOR zif_order_repository~find_by_customer, find_by_status FOR zif_order_repository~find_by_status, get_items FOR zif_order_repository~get_items, save FOR zif_order_repository~save, delete FOR zif_order_repository~delete, exists FOR zif_order_repository~exists.
PRIVATE SECTION. TYPES: BEGIN OF ty_order_db, order_uuid TYPE sysuuid_x16, customer_uuid TYPE sysuuid_x16, order_date TYPE dats, order_status TYPE char2, total_amount TYPE p LENGTH 16 DECIMALS 2, currency_code TYPE waerk, created_by TYPE syuname, created_at TYPE timestampl, changed_by TYPE syuname, changed_at TYPE timestampl, END OF ty_order_db.
METHODS map_to_domain IMPORTING is_db_order TYPE ty_order_db RETURNING VALUE(rs_order) TYPE zif_order_repository=>ty_order.
METHODS map_to_db IMPORTING is_order TYPE zif_order_repository=>ty_order RETURNING VALUE(rs_db) TYPE ty_order_db.
ENDCLASS.
CLASS zcl_order_repository IMPLEMENTATION.
METHOD find_by_id. DATA ls_db_order TYPE ty_order_db.
SELECT SINGLE * FROM zorder_header WHERE order_uuid = @iv_order_id INTO @ls_db_order.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_not_found EXPORTING textid = zcx_not_found=>order_not_found order_id = iv_order_id. ENDIF.
rs_order = map_to_domain( ls_db_order ). ENDMETHOD.
METHOD find_all. DATA lt_db_orders TYPE STANDARD TABLE OF ty_order_db.
SELECT * FROM zorder_header ORDER BY order_date DESCENDING INTO TABLE @lt_db_orders.
LOOP AT lt_db_orders INTO DATA(ls_db_order). APPEND map_to_domain( ls_db_order ) TO rt_orders. ENDLOOP. ENDMETHOD.
METHOD find_by_customer. DATA lt_db_orders TYPE STANDARD TABLE OF ty_order_db.
SELECT * FROM zorder_header WHERE customer_uuid = @iv_customer_id ORDER BY order_date DESCENDING INTO TABLE @lt_db_orders.
LOOP AT lt_db_orders INTO DATA(ls_db_order). APPEND map_to_domain( ls_db_order ) TO rt_orders. ENDLOOP. ENDMETHOD.
METHOD find_by_status. DATA lt_db_orders TYPE STANDARD TABLE OF ty_order_db.
SELECT * FROM zorder_header WHERE order_status = @iv_status ORDER BY order_date DESCENDING INTO TABLE @lt_db_orders.
LOOP AT lt_db_orders INTO DATA(ls_db_order). APPEND map_to_domain( ls_db_order ) TO rt_orders. ENDLOOP. ENDMETHOD.
METHOD get_items. SELECT item_uuid, order_uuid, product_uuid, quantity, unit_price, currency_code FROM zorder_item WHERE order_uuid = @iv_order_id INTO TABLE @rt_items. ENDMETHOD.
METHOD save. DATA ls_db_order TYPE ty_order_db.
ls_db_order = map_to_db( is_order ).
" Définir l'horodatage GET TIME STAMP FIELD DATA(lv_timestamp). ls_db_order-changed_at = lv_timestamp. ls_db_order-changed_by = cl_abap_context_info=>get_user_technical_name( ).
" Vérifier si nouveau ou mise à jour IF exists( is_order-order_id ). " Mise à jour UPDATE zorder_header SET customer_uuid = @ls_db_order-customer_uuid, order_date = @ls_db_order-order_date, order_status = @ls_db_order-order_status, total_amount = @ls_db_order-total_amount, currency_code = @ls_db_order-currency_code, changed_by = @ls_db_order-changed_by, changed_at = @ls_db_order-changed_at WHERE order_uuid = @is_order-order_id.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_save_error EXPORTING textid = zcx_save_error=>update_failed. ENDIF. ELSE. " Insertion ls_db_order-created_at = lv_timestamp. ls_db_order-created_by = cl_abap_context_info=>get_user_technical_name( ).
INSERT zorder_header FROM @ls_db_order.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_save_error EXPORTING textid = zcx_save_error=>insert_failed. ENDIF. ENDIF.
" Sauvegarder les items si présents IF it_items IS NOT INITIAL. " Supprimer les anciens items DELETE FROM zorder_item WHERE order_uuid = @is_order-order_id.
" Insérer les nouveaux items INSERT zorder_item FROM TABLE @it_items. ENDIF. ENDMETHOD.
METHOD delete. IF NOT exists( iv_order_id ). RAISE EXCEPTION TYPE zcx_not_found EXPORTING textid = zcx_not_found=>order_not_found order_id = iv_order_id. ENDIF.
" Supprimer d'abord les items DELETE FROM zorder_item WHERE order_uuid = @iv_order_id.
" Puis l'en-tête DELETE FROM zorder_header WHERE order_uuid = @iv_order_id. ENDMETHOD.
METHOD exists. SELECT COUNT(*) FROM zorder_header WHERE order_uuid = @iv_order_id INTO @DATA(lv_count).
rv_exists = xsdbool( lv_count > 0 ). ENDMETHOD.
METHOD map_to_domain. rs_order = VALUE #( order_id = is_db_order-order_uuid customer_id = is_db_order-customer_uuid order_date = is_db_order-order_date status = is_db_order-order_status total_amount = is_db_order-total_amount currency = is_db_order-currency_code ). ENDMETHOD.
METHOD map_to_db. rs_db = VALUE #( order_uuid = is_order-order_id customer_uuid = is_order-customer_id order_date = is_order-order_date order_status = is_order-status total_amount = is_order-total_amount currency_code = is_order-currency ). ENDMETHOD.
ENDCLASS.Pattern Unit of Work
Le pattern Unit of Work collecte toutes les modifications et les écrit groupées dans une transaction. Il empêche les états intermédiaires incohérents.
INTERFACE zif_unit_of_work PUBLIC.
METHODS register_new IMPORTING iv_entity_type TYPE string is_entity TYPE any.
METHODS register_dirty IMPORTING iv_entity_type TYPE string is_entity TYPE any.
METHODS register_deleted IMPORTING iv_entity_type TYPE string is_entity TYPE any.
METHODS commit RAISING zcx_uow_error.
METHODS rollback.
ENDINTERFACE.CLASS zcl_unit_of_work DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_unit_of_work.
ALIASES: register_new FOR zif_unit_of_work~register_new, register_dirty FOR zif_unit_of_work~register_dirty, register_deleted FOR zif_unit_of_work~register_deleted, commit FOR zif_unit_of_work~commit, rollback FOR zif_unit_of_work~rollback.
METHODS constructor IMPORTING io_order_repository TYPE REF TO zif_order_repository.
PRIVATE SECTION. DATA mo_order_repository TYPE REF TO zif_order_repository.
DATA mt_new_orders TYPE zif_order_repository=>tt_orders. DATA mt_dirty_orders TYPE zif_order_repository=>tt_orders. DATA mt_deleted_orders TYPE zif_order_repository=>tt_orders.
METHODS clear_state.
ENDCLASS.
CLASS zcl_unit_of_work IMPLEMENTATION.
METHOD constructor. mo_order_repository = io_order_repository. ENDMETHOD.
METHOD register_new. CASE iv_entity_type. WHEN 'ORDER'. APPEND INITIAL LINE TO mt_new_orders ASSIGNING FIELD-SYMBOL(<ls_order>). <ls_order> = is_entity. WHEN OTHERS. " Ajouter d'autres types d'entités ENDCASE. ENDMETHOD.
METHOD register_dirty. CASE iv_entity_type. WHEN 'ORDER'. APPEND INITIAL LINE TO mt_dirty_orders ASSIGNING FIELD-SYMBOL(<ls_order>). <ls_order> = is_entity. WHEN OTHERS. " Ajouter d'autres types d'entités ENDCASE. ENDMETHOD.
METHOD register_deleted. CASE iv_entity_type. WHEN 'ORDER'. APPEND INITIAL LINE TO mt_deleted_orders ASSIGNING FIELD-SYMBOL(<ls_order>). <ls_order> = is_entity. WHEN OTHERS. " Ajouter d'autres types d'entités ENDCASE. ENDMETHOD.
METHOD commit. TRY. " Sauvegarder les nouvelles entités LOOP AT mt_new_orders INTO DATA(ls_new_order). mo_order_repository->save( ls_new_order ). ENDLOOP.
" Sauvegarder les entités modifiées LOOP AT mt_dirty_orders INTO DATA(ls_dirty_order). mo_order_repository->save( ls_dirty_order ). ENDLOOP.
" Supprimer les entités supprimées LOOP AT mt_deleted_orders INTO DATA(ls_deleted_order). mo_order_repository->delete( ls_deleted_order-order_id ). ENDLOOP.
" Réinitialiser l'état clear_state( ).
CATCH zcx_save_error zcx_not_found INTO DATA(lx_error). rollback( ). RAISE EXCEPTION TYPE zcx_uow_error EXPORTING previous = lx_error. ENDTRY. ENDMETHOD.
METHOD rollback. clear_state( ). ENDMETHOD.
METHOD clear_state. CLEAR: mt_new_orders, mt_dirty_orders, mt_deleted_orders. ENDMETHOD.
ENDCLASS.Utilisation avec Unit of Work
METHOD create_order_with_items. " Créer l'Unit of Work DATA(lo_uow) = NEW zcl_unit_of_work( mo_order_repository ).
" Créer une nouvelle commande DATA(ls_order) = VALUE zif_order_repository=>ty_order( order_id = cl_system_uuid=>create_uuid_x16_static( ) customer_id = iv_customer_id order_date = cl_abap_context_info=>get_system_date( ) status = 'NE' " Nouveau total_amount = 0 currency = 'EUR" ).
lo_uow->register_new( iv_entity_type = 'ORDER" is_entity = ls_order ).
" Ajouter les items LOOP AT it_items INTO DATA(ls_item). ls_order-total_amount = ls_order-total_amount + ( ls_item-quantity * ls_item-unit_price ). ENDLOOP.
" Mettre à jour la commande avec le total lo_uow->register_dirty( iv_entity_type = 'ORDER" is_entity = ls_order ).
" Tout sauvegarder dans une transaction TRY. lo_uow->commit( ). CATCH zcx_uow_error INTO DATA(lx_error). " Gestion d'erreur RAISE EXCEPTION TYPE zcx_order_creation_failed EXPORTING previous = lx_error. ENDTRY.ENDMETHOD.Repositories Mock pour les tests
Les repositories mock simulent la couche d’accès aux données et permettent les tests unitaires sans base de données.
CLASS lcl_mock_order_repository DEFINITION FOR TESTING.
PUBLIC SECTION. INTERFACES zif_order_repository.
" Aide pour les tests METHODS add_test_order IMPORTING is_order TYPE zif_order_repository=>ty_order.
METHODS get_saved_orders RETURNING VALUE(rt_orders) TYPE zif_order_repository=>tt_orders.
METHODS clear.
PRIVATE SECTION. DATA mt_orders TYPE zif_order_repository=>tt_orders. DATA mt_saved_orders TYPE zif_order_repository=>tt_orders.
ENDCLASS.
CLASS lcl_mock_order_repository IMPLEMENTATION.
METHOD zif_order_repository~find_by_id. READ TABLE mt_orders INTO rs_order WITH KEY order_id = iv_order_id.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_not_found. ENDIF. ENDMETHOD.
METHOD zif_order_repository~find_all. rt_orders = mt_orders. ENDMETHOD.
METHOD zif_order_repository~find_by_customer. rt_orders = VALUE #( FOR ls_order IN mt_orders WHERE ( customer_id = iv_customer_id ) ( ls_order ) ). ENDMETHOD.
METHOD zif_order_repository~find_by_status. rt_orders = VALUE #( FOR ls_order IN mt_orders WHERE ( status = iv_status ) ( ls_order ) ). ENDMETHOD.
METHOD zif_order_repository~get_items. " Simplifié : Pas d'items dans le mock rt_items = VALUE #( ). ENDMETHOD.
METHOD zif_order_repository~save. " Ajouter à la liste des commandes sauvegardées APPEND is_order TO mt_saved_orders.
" Mettre à jour en mémoire READ TABLE mt_orders ASSIGNING FIELD-SYMBOL(<ls_order>) WITH KEY order_id = is_order-order_id.
IF sy-subrc = 0. <ls_order> = is_order. ELSE. APPEND is_order TO mt_orders. ENDIF. ENDMETHOD.
METHOD zif_order_repository~delete. DELETE mt_orders WHERE order_id = iv_order_id.
IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_not_found. ENDIF. ENDMETHOD.
METHOD zif_order_repository~exists. rv_exists = xsdbool( line_exists( mt_orders[ order_id = iv_order_id ] ) ). ENDMETHOD.
METHOD add_test_order. APPEND is_order TO mt_orders. ENDMETHOD.
METHOD get_saved_orders. rt_orders = mt_saved_orders. ENDMETHOD.
METHOD clear. CLEAR: mt_orders, mt_saved_orders. ENDMETHOD.
ENDCLASS.Test unitaire avec repository mock
CLASS ltcl_order_service DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_mock_repository TYPE REF TO lcl_mock_order_repository. DATA mo_order_service TYPE REF TO zcl_order_service.
METHODS setup. METHODS teardown.
METHODS test_create_order FOR TESTING. METHODS test_cancel_order FOR TESTING. METHODS test_cancel_nonexistent FOR TESTING.
ENDCLASS.
CLASS ltcl_order_service IMPLEMENTATION.
METHOD setup. " Créer le repository mock mo_mock_repository = NEW lcl_mock_order_repository( ).
" Injecter le mock dans le service mo_order_service = NEW zcl_order_service( io_order_repository = mo_mock_repository ). ENDMETHOD.
METHOD teardown. mo_mock_repository->clear( ). ENDMETHOD.
METHOD test_create_order. " Given DATA(lv_customer_id) = cl_system_uuid=>create_uuid_x16_static( ).
" When DATA(ls_order) = mo_order_service->create_order( iv_customer_id = lv_customer_id iv_currency = 'EUR" ).
" Then cl_abap_unit_assert=>assert_not_initial( act = ls_order-order_id msg = 'L''ID de commande devrait être défini" ).
cl_abap_unit_assert=>assert_equals( act = ls_order-status exp = 'NE" msg = 'Le statut devrait être NE (Nouveau)" ).
" Vérifier si sauvegardé DATA(lt_saved) = mo_mock_repository->get_saved_orders( ). cl_abap_unit_assert=>assert_equals( act = lines( lt_saved ) exp = 1 msg = 'Une commande devrait avoir été sauvegardée" ). ENDMETHOD.
METHOD test_cancel_order. " Given DATA(ls_existing_order) = VALUE zif_order_repository=>ty_order( order_id = cl_system_uuid=>create_uuid_x16_static( ) customer_id = cl_system_uuid=>create_uuid_x16_static( ) order_date = cl_abap_context_info=>get_system_date( ) status = 'NE" total_amount = 100 currency = 'EUR" ). mo_mock_repository->add_test_order( ls_existing_order ).
" When mo_order_service->cancel_order( ls_existing_order-order_id ).
" Then DATA(lt_saved) = mo_mock_repository->get_saved_orders( ). cl_abap_unit_assert=>assert_equals( act = lt_saved[ 1 ]-status exp = 'CA" msg = 'Le statut devrait être CA (Annulé)" ). ENDMETHOD.
METHOD test_cancel_nonexistent. " Given DATA(lv_nonexistent_id) = cl_system_uuid=>create_uuid_x16_static( ).
" When / Then TRY. mo_order_service->cancel_order( lv_nonexistent_id ). cl_abap_unit_assert=>fail( msg = 'Exception attendue' ). CATCH zcx_not_found. " Attendu ENDTRY. ENDMETHOD.
ENDCLASS.Intégration avec RAP Unmanaged Scenario
Le pattern Repository s’intègre parfaitement avec RAP Unmanaged Save, où la persistance est implémentée manuellement.
Behavior Handler avec Repository
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION. DATA mo_repository TYPE REF TO zif_order_repository.
METHODS get_repository RETURNING VALUE(ro_repository) TYPE REF TO zif_order_repository.
METHODS create FOR MODIFY IMPORTING entities FOR CREATE order.
METHODS update FOR MODIFY IMPORTING entities FOR UPDATE order.
METHODS delete FOR MODIFY IMPORTING keys FOR DELETE order.
METHODS read FOR READ IMPORTING keys FOR READ order RESULT result.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD get_repository. IF mo_repository IS NOT BOUND. mo_repository = NEW zcl_order_repository( ). ENDIF. ro_repository = mo_repository. ENDMETHOD.
METHOD create. DATA(lo_repository) = get_repository( ).
LOOP AT entities ASSIGNING FIELD-SYMBOL(<ls_entity>). DATA(ls_order) = VALUE zif_order_repository=>ty_order( order_id = <ls_entity>-orderid customer_id = <ls_entity>-customerid order_date = <ls_entity>-orderdate status = <ls_entity>-status total_amount = <ls_entity>-totalamount currency = <ls_entity>-currency ).
TRY. lo_repository->save( ls_order ). CATCH zcx_save_error INTO DATA(lx_error). APPEND VALUE #( %cid = <ls_entity>-%cid ) TO failed-order. APPEND VALUE #( %cid = <ls_entity>-%cid %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD update. DATA(lo_repository) = get_repository( ).
LOOP AT entities ASSIGNING FIELD-SYMBOL(<ls_entity>). TRY. DATA(ls_order) = lo_repository->find_by_id( <ls_entity>-orderid ).
" Appliquer uniquement les champs modifiés IF <ls_entity>-%control-customerid = if_abap_behv=>mk-on. ls_order-customer_id = <ls_entity>-customerid. ENDIF. IF <ls_entity>-%control-status = if_abap_behv=>mk-on. ls_order-status = <ls_entity>-status. ENDIF. IF <ls_entity>-%control-totalamount = if_abap_behv=>mk-on. ls_order-total_amount = <ls_entity>-totalamount. ENDIF.
lo_repository->save( ls_order ).
CATCH zcx_not_found zcx_save_error INTO DATA(lx_error). APPEND VALUE #( orderid = <ls_entity>-orderid ) TO failed-order. APPEND VALUE #( orderid = <ls_entity>-orderid %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD delete. DATA(lo_repository) = get_repository( ).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key>). TRY. lo_repository->delete( <ls_key>-orderid ). CATCH zcx_not_found INTO DATA(lx_error). APPEND VALUE #( orderid = <ls_key>-orderid ) TO failed-order. APPEND VALUE #( orderid = <ls_key>-orderid %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_error->get_text( ) ) ) TO reported-order. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD read. DATA(lo_repository) = get_repository( ).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key>). TRY. DATA(ls_order) = lo_repository->find_by_id( <ls_key>-orderid ).
APPEND VALUE #( orderid = ls_order-order_id customerid = ls_order-customer_id orderdate = ls_order-order_date status = ls_order-status totalamount = ls_order-total_amount currency = ls_order-currency ) TO result.
CATCH zcx_not_found. APPEND VALUE #( orderid = <ls_key>-orderid ) TO failed-order. ENDTRY. ENDLOOP. ENDMETHOD.
ENDCLASS.Bonnes pratiques
1. Un Repository par agrégat
" Bien : Repository pour l'agrégat Order (inclus Items)INTERFACE zif_order_repository. METHODS find_by_id RETURNING VALUE(ro_order) TYPE REF TO zcl_order.ENDINTERFACE.
" Mal : Repositories séparés pour entités étroitement coupléesINTERFACE zif_order_item_repository. " Non recommandéENDINTERFACE.2. Méthodes de requête selon les besoins
" Ajouter uniquement les méthodes réellement nécessairesfind_by_id " Presque toujoursfind_by_customer " Si vue client nécessairefind_pending " Exigence métier spécifique3. Pagination pour grandes quantités de données
METHODS find_all IMPORTING iv_offset TYPE i DEFAULT 0 iv_limit TYPE i DEFAULT 100 RETURNING VALUE(rt_result) TYPE ty_paginated_result.4. Factory pour la création de Repository
CLASS zcl_repository_factory DEFINITION PUBLIC FINAL. PUBLIC SECTION. CLASS-METHODS get_order_repository RETURNING VALUE(ro_repository) TYPE REF TO zif_order_repository.ENDCLASS.
CLASS zcl_repository_factory IMPLEMENTATION. METHOD get_order_repository. " Ici on peut basculer entre BD, Mock, Cache etc. ro_repository = NEW zcl_order_repository( ). ENDMETHOD.ENDCLASS.Résumé
| Aspect | Recommandation |
|---|---|
| Interface | Langage du domaine, pas de détails SQL |
| Implémentation | Mapping entre domaine et BD |
| Unit of Work | Pour cohérence transactionnelle |
| Mock | En mémoire pour tests unitaires |
| Intégration RAP | Dans le Behavior Handler via DI |
Le pattern Repository nécessite plus d’effort initial, mais s’avère payant pour les domaines complexes grâce à une meilleure testabilité, maintenabilité et flexibilité.
Articles complémentaires
- Design Patterns pour RAP - Factory, Strategy et autres patterns
- RAP Unmanaged Save - Persistance manuelle en RAP
- Test Doubles et Mocking - Stratégies de test en ABAP Cloud