Das Repository Pattern abstrahiert den Datenzugriff und entkoppelt die Geschaeftslogik von der Persistenzschicht. In ABAP Cloud ist es besonders wertvoll fuer komplexe Domaenen, Legacy-Integration und testbare Architekturen.
Grundkonzept
Ein Repository stellt eine Sammlung von Domaenenobjekten dar und bietet Methoden zum Laden, Speichern und Suchen. Die Geschaeftslogik arbeitet nur mit dem Repository-Interface, nicht direkt mit der Datenbank.
┌─────────────────────────────────────────────┐│ Geschaeftslogik ││ (Kennt nur Repository-Interface) │└───────────────────┬─────────────────────────┘ │ Verwendet ▼┌─────────────────────────────────────────────┐│ Repository Interface ││ find_by_id(), find_all(), save() │└───────────────────┬─────────────────────────┘ │ Implementiert ┌───────────┴───────────┐ ▼ ▼┌───────────────┐ ┌───────────────┐│ DB-Repository │ │ Mock-Repository││ (Produktiv) │ │ (Tests) │└───────────────┘ └───────────────┘Abgrenzung zu direktem Datenbankzugriff
| Aspekt | Direkter Zugriff | Repository Pattern |
|---|---|---|
| Testbarkeit | Schwer (DB erforderlich) | Einfach (Mocks) |
| Wartbarkeit | Verstreute SQL-Statements | Zentralisiert |
| Flexibilitaet | Starr an DB gebunden | Austauschbar |
| Abstraktion | Keine | Domaenenobjekte |
| Komplexitaet | Niedrig | Hoeher |
Wann Repository Pattern einsetzen?
Sinnvoll bei:
- Komplexer Domaenenlogik
- Mehreren Datenquellen (DB, RFC, HTTP)
- Hohen Testanforderungen
- Legacy-Integration (Unmanaged RAP)
Overkill bei:
- Einfachen CRUD-Operationen
- Reinen Managed-RAP-Szenarien
- Prototypen und MVPs
Repository Interface Definition
Das Interface definiert den Vertrag fuer den Datenzugriff. Es verwendet Domaenenobjekte, keine Datenbankstrukturen.
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.
" Lesen 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.
" Schreiben 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.
" Existenzpruefung METHODS exists IMPORTING iv_order_id TYPE sysuuid_x16 RETURNING VALUE(rv_exists) TYPE abap_bool.
ENDINTERFACE.Interface-Design-Prinzipien
- Domaenensprache verwenden:
find_by_customerstattselect_where_customer - Exceptions statt Rueckgabewerte: Klare Fehlerbehandlung
- Keine technischen Details: Keine SQL, keine Tabellenstruktur im Interface
- Immutable Inputs: Nur IMPORTING, keine CHANGING
Repository Implementation
Die Implementation kapselt alle Datenbankzugriffe und uebersetzt zwischen Domaenenobjekten und Datenbankstrukturen.
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 ).
" Timestamp setzen 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( ).
" Check ob neu oder Update IF exists( is_order-order_id ). " Update 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. " Insert 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.
" Items speichern falls vorhanden IF it_items IS NOT INITIAL. " Alte Items loeschen DELETE FROM zorder_item WHERE order_uuid = @is_order-order_id.
" Neue Items einfuegen 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.
" Items zuerst loeschen DELETE FROM zorder_item WHERE order_uuid = @iv_order_id.
" Dann Header 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.Unit of Work Pattern
Das Unit of Work Pattern sammelt alle Aenderungen und schreibt sie gebuendelt in einer Transaktion. Es verhindert inkonsistente Zwischenzustaende.
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. " Weitere Entity-Typen hinzufuegen 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. " Weitere Entity-Typen hinzufuegen 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. " Weitere Entity-Typen hinzufuegen ENDCASE. ENDMETHOD.
METHOD commit. TRY. " Neue Entities speichern LOOP AT mt_new_orders INTO DATA(ls_new_order). mo_order_repository->save( ls_new_order ). ENDLOOP.
" Geaenderte Entities speichern LOOP AT mt_dirty_orders INTO DATA(ls_dirty_order). mo_order_repository->save( ls_dirty_order ). ENDLOOP.
" Geloeschte Entities entfernen LOOP AT mt_deleted_orders INTO DATA(ls_deleted_order). mo_order_repository->delete( ls_deleted_order-order_id ). ENDLOOP.
" Zustand zuruecksetzen 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.Verwendung mit Unit of Work
METHOD create_order_with_items. " Unit of Work erstellen DATA(lo_uow) = NEW zcl_unit_of_work( mo_order_repository ).
" Neue Order anlegen 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' " Neu total_amount = 0 currency = 'EUR' ).
lo_uow->register_new( iv_entity_type = 'ORDER' is_entity = ls_order ).
" Items hinzufuegen LOOP AT it_items INTO DATA(ls_item). ls_order-total_amount = ls_order-total_amount + ( ls_item-quantity * ls_item-unit_price ). ENDLOOP.
" Order mit Total aktualisieren lo_uow->register_dirty( iv_entity_type = 'ORDER' is_entity = ls_order ).
" Alles in einer Transaktion speichern TRY. lo_uow->commit( ). CATCH zcx_uow_error INTO DATA(lx_error). " Fehlerbehandlung RAISE EXCEPTION TYPE zcx_order_creation_failed EXPORTING previous = lx_error. ENDTRY.ENDMETHOD.Mock-Repositories fuer Tests
Mock-Repositories simulieren die Datenzugriffsschicht und ermoeglichen Unit-Tests ohne Datenbank.
CLASS lcl_mock_order_repository DEFINITION FOR TESTING.
PUBLIC SECTION. INTERFACES zif_order_repository.
" Test-Helper 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. " Vereinfacht: Keine Items im Mock rt_items = VALUE #( ). ENDMETHOD.
METHOD zif_order_repository~save. " Zur Liste der gespeicherten Orders hinzufuegen APPEND is_order TO mt_saved_orders.
" In-Memory aktualisieren 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.Unit-Test mit Mock-Repository
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. " Mock-Repository erstellen mo_mock_repository = NEW lcl_mock_order_repository( ).
" Service mit Mock injizieren 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 = 'Order ID sollte gesetzt sein' ).
cl_abap_unit_assert=>assert_equals( act = ls_order-status exp = 'NE' msg = 'Status sollte NE (Neu) sein' ).
" Pruefen ob gespeichert wurde DATA(lt_saved) = mo_mock_repository->get_saved_orders( ). cl_abap_unit_assert=>assert_equals( act = lines( lt_saved ) exp = 1 msg = 'Eine Order sollte gespeichert worden sein' ). 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 = 'Status sollte CA (Cancelled) sein' ). 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 erwartet' ). CATCH zcx_not_found. " Erwartet ENDTRY. ENDMETHOD.
ENDCLASS.Integration mit RAP Unmanaged Scenario
Das Repository Pattern integriert sich nahtlos mit RAP Unmanaged Save, wo die Persistenz manuell implementiert wird.
Behavior Handler mit 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 ).
" Nur geaenderte Felder uebernehmen 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.Best Practices
1. Ein Repository pro Aggregate
" Gut: Repository fuer Order-Aggregate (inkl. Items)INTERFACE zif_order_repository. METHODS find_by_id RETURNING VALUE(ro_order) TYPE REF TO zcl_order.ENDINTERFACE.
" Schlecht: Separate Repositories fuer eng gekoppelte EntitiesINTERFACE zif_order_item_repository. " Nicht empfohlenENDINTERFACE.2. Query-Methoden nach Bedarf
" Fuege nur Methoden hinzu, die wirklich benoetigt werdenfind_by_id " Fast immerfind_by_customer " Wenn Kundenansicht benoetigtfind_pending " Spezifische Geschaeftsanforderung3. Pagination bei grossen Datenmengen
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 fuer Repository-Erstellung
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. " Hier kann zwischen DB, Mock, Cache etc. gewechselt werden ro_repository = NEW zcl_order_repository( ). ENDMETHOD.ENDCLASS.Zusammenfassung
| Aspekt | Empfehlung |
|---|---|
| Interface | Domaenensprache, keine SQL-Details |
| Implementation | Mapping zwischen Domaene und DB |
| Unit of Work | Fuer transaktionale Konsistenz |
| Mock | In-Memory fuer Unit-Tests |
| RAP-Integration | Im Behavior Handler via DI |
Das Repository Pattern erfordert mehr Initialaufwand, zahlt sich aber bei komplexen Domaenen durch bessere Testbarkeit, Wartbarkeit und Flexibilitaet aus.
Weiterführende Artikel
- Design Patterns fuer RAP - Factory, Strategy und weitere Patterns
- RAP Unmanaged Save - Manuelle Persistenz in RAP
- Test Doubles und Mocking - Teststrategien in ABAP Cloud