Pattern Repository en ABAP Cloud : Couche d

Catégorie
Best Practices
Publié
Auteur
Johannes

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

AspectAccès directPattern Repository
TestabilitéDifficile (BD requise)Facile (Mocks)
MaintenabilitéInstructions SQL disperséesCentralisé
FlexibilitéLié rigidement à la BDÉchangeable
AbstractionAucuneObjets de domaine
ComplexitéFaiblePlus é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

  1. Utiliser le langage du domaine: find_by_customer au lieu de select_where_customer
  2. Exceptions au lieu de valeurs de retour: Gestion d’erreur claire
  3. Pas de détails techniques: Pas de SQL, pas de structure de table dans l’interface
  4. 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ées
INTERFACE zif_order_item_repository. " Non recommandé
ENDINTERFACE.

2. Méthodes de requête selon les besoins

" Ajouter uniquement les méthodes réellement nécessaires
find_by_id " Presque toujours
find_by_customer " Si vue client nécessaire
find_pending " Exigence métier spécifique

3. 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é

AspectRecommandation
InterfaceLangage du domaine, pas de détails SQL
ImplémentationMapping entre domaine et BD
Unit of WorkPour cohérence transactionnelle
MockEn mémoire pour tests unitaires
Intégration RAPDans 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