Unmanaged Save ist das hybride Szenario in RAP, bei dem das Framework die Interaktionsphase verwaltet, aber Sie die Persistierung selbst übernehmen. Perfekt für die Integration von Legacy-Code wie BAPIs und Funktionsbausteinen.
Das Problem: Legacy trifft Modern
Sie haben bewährte Geschäftslogik in BAPIs oder Funktionsbausteinen, möchten aber moderne Fiori-Apps mit RAP bauen:
" Bestehender BAPI für AuftragsanlageCALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING order_header_in = ls_header TABLES order_items_in = lt_items return = lt_return.Das Dilemma:
- Managed RAP: Framework macht INSERT/UPDATE/DELETE – aber wie ruft man den BAPI?
- Unmanaged RAP: Volle Kontrolle, aber KEIN Draft-Support
Die Lösung: Unmanaged Save – das Beste aus beiden Welten.
Managed vs. Unmanaged vs. Unmanaged Save
| Aspekt | Managed | Unmanaged | Unmanaged Save |
|---|---|---|---|
| Interaktionsphase | Framework | Sie selbst | Framework |
| Transient Buffer | Framework verwaltet | Sie selbst | Framework verwaltet |
| Save-Phase | Framework | Sie selbst | Sie selbst |
| Draft-Support | ✅ Ja | ❌ Nein | ✅ Ja |
| Legacy-Integration | ❌ Schwierig | ✅ Ja | ✅ Ja |
| Typischer Einsatz | Greenfield | Vollständig Custom | Legacy-Integration |
Unmanaged Save aktivieren
Behavior Definition
managed implementation in class zbp_i_salesorder unique;strict ( 2 );with draft;
" Wichtig: Unmanaged Save aktivierenwith unmanaged save;
define behavior for ZI_SalesOrder alias SalesOrderpersistent table zsalesorderdraft table zd_salesorderlock master total etag LastChangedAtauthorization master ( instance ){ create; update; delete;
field ( readonly ) SalesOrderId; field ( numbering : managed ) SalesOrderId;
validation validateCustomer on save { field CustomerId; } determination calcTotalPrice on modify { field Quantity, UnitPrice; }
action confirmOrder result [1] $self;
draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume;
association _Items { create; with draft; }}
define behavior for ZI_SalesOrderItem alias Itempersistent table zsalesorderitemdraft table zd_salesorderitemlock dependent by _SalesOrderauthorization dependent by _SalesOrder{ update; delete;
field ( readonly ) SalesOrderId, ItemId; field ( numbering : managed ) ItemId;
association _SalesOrder { with draft; }}Schlüsselwort: with unmanaged save; aktiviert den manuellen Save-Handler.
Behavior Implementation: Saver Class
CLASS lsc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS: " MUSS implementiert werden bei unmanaged save save_modified REDEFINITION,
" Optional: Cleanup nach COMMIT/ROLLBACK cleanup_finalize REDEFINITION.ENDCLASS.
CLASS lsc_salesorder IMPLEMENTATION.
METHOD save_modified. " Hier rufen Sie Ihre Legacy-APIs auf! " Framework hat Daten in Transient Buffer verwaltet, " jetzt müssen Sie sie persistieren.
" CREATE: Neue Entities anlegen IF create-salesorder IS NOT INITIAL. LOOP AT create-salesorder INTO DATA(ls_create). me->call_bapi_create( ls_create ). ENDLOOP. ENDIF.
" UPDATE: Bestehende Entities ändern IF update-salesorder IS NOT INITIAL. LOOP AT update-salesorder INTO DATA(ls_update). me->call_bapi_update( ls_update ). ENDLOOP. ENDIF.
" DELETE: Entities löschen IF delete-salesorder IS NOT INITIAL. LOOP AT delete-salesorder INTO DATA(ls_delete). me->call_bapi_delete( ls_delete ). ENDLOOP. ENDIF.
" Analog für Child-Entities (Items) IF create-item IS NOT INITIAL. me->call_bapi_create_items( create-item ). ENDIF.
IF update-item IS NOT INITIAL. me->call_bapi_update_items( update-item ). ENDIF.
IF delete-item IS NOT INITIAL. me->call_bapi_delete_items( delete-item ). ENDIF. ENDMETHOD.
METHOD cleanup_finalize. " Nach COMMIT oder ROLLBACK aufräumen " z.B. temporäre Daten löschen, Locks freigeben ENDMETHOD.
ENDCLASS.BAPI-Integration im Detail
Create: Neue Aufträge anlegen
CLASS lsc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_saver. PRIVATE SECTION. METHODS call_bapi_create IMPORTING is_order TYPE zsalesorder.ENDCLASS.
CLASS lsc_salesorder IMPLEMENTATION.
METHOD call_bapi_create. DATA: ls_header TYPE bapisdhead, lt_items TYPE TABLE OF bapisditem, lt_return TYPE bapiret2_t, lv_vbeln TYPE vbeln.
" 1. Mapping: RAP-Struktur → BAPI-Struktur ls_header = VALUE #( doc_type = 'TA' sales_org = is_order-SalesOrg distr_chan = is_order-DistrChannel division = is_order-Division sold_to = is_order-CustomerId purch_no = is_order-PurchaseOrderNo doc_date = is_order-OrderDate ).
" 2. Items vorbereiten (falls vorhanden) " Hinweis: Bei Deep Insert werden Items separat übergeben " Hier beispielhaft ausgelassen
" 3. BAPI aufrufen CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING order_header_in = ls_header IMPORTING salesdocument = lv_vbeln TABLES return = lt_return.
" 4. Fehlerprüfung LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'. " Fehler in RAP-reported-Struktur übernehmen APPEND VALUE #( %tky = is_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = <return>-message ) ) TO reported-salesorder.
" Save abbrechen RETURN. ENDLOOP.
" 5. Commit Work (BAPI-Standard) CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.
" 6. Generierte ID zurückmappen (optional, wenn managed numbering) " Bei managed numbering hat Framework bereits ID vergeben ENDMETHOD.
ENDCLASS.Update: Bestehende Aufträge ändern
METHOD call_bapi_update. DATA: ls_header_in TYPE bapisdh1, ls_header_inx TYPE bapisdh1x, lt_return TYPE bapiret2_t.
" 1. Header-Daten vorbereiten ls_header_in-purch_no = is_order-PurchaseOrderNo.
" 2. %control auswerten: Welche Felder wurden geändert? ls_header_inx-updateflag = 'U'.
IF is_order-%control-PurchaseOrderNo = if_abap_behv=>mk-on. ls_header_inx-purch_no = abap_true. ENDIF.
IF is_order-%control-OrderDate = if_abap_behv=>mk-on. ls_header_in-doc_date = is_order-OrderDate. ls_header_inx-doc_date = abap_true. ENDIF.
" Weitere Felder analog...
" 3. BAPI aufrufen CALL FUNCTION 'BAPI_SALESORDER_CHANGE' EXPORTING salesdocument = CONV #( is_order-SalesOrderId ) order_header_in = ls_header_in order_header_inx = ls_header_inx TABLES return = lt_return.
" 4. Fehlerprüfung (wie bei Create) LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'. APPEND VALUE #( %tky = is_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = <return>-message ) ) TO reported-salesorder. RETURN. ENDLOOP.
" 5. Commit CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.ENDMETHOD.Delete: Aufträge löschen
METHOD call_bapi_delete. DATA: lt_return TYPE bapiret2_t.
" Bei manchen Objekten: Stornierung statt Löschung " Hier exemplarisch mit Reject-BAPI
CALL FUNCTION 'BAPI_SALESORDER_CHANGE' EXPORTING salesdocument = CONV #( is_order-SalesOrderId ) order_header_in = VALUE bapisdh1( ref_doc = 'DELETED' ) order_header_inx = VALUE bapisdh1x( updateflag = 'D' ) TABLES return = lt_return.
" Alternativ: Direkte Löschung (wenn BAPI existiert) " CALL FUNCTION 'BAPI_SALESORDER_DELETE' ...
" Fehlerprüfung LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'. APPEND VALUE #( %tky = is_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = <return>-message ) ) TO reported-salesorder. RETURN. ENDLOOP.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.ENDMETHOD.Locking bei Unmanaged Save
Framework-Lock vs. Legacy-Lock
Bei Unmanaged Save verwaltet das Framework die Locks während der Interaktionsphase. In der Save-Phase müssen Sie ggf. zusätzliche Legacy-Locks berücksichtigen.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS lock FOR LOCK IMPORTING keys FOR LOCK SalesOrder.ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD lock. " RAP-Lock wird vom Framework verwaltet " Zusätzlich: Legacy-System sperren (falls nötig)
LOOP AT keys INTO DATA(ls_key). " ENQUEUE für Legacy-Tabellen CALL FUNCTION 'ENQUEUE_EVVBAKE' EXPORTING mode_vbak = 'E' mandt = sy-mandt vbeln = CONV #( ls_key-SalesOrderId ) EXCEPTIONS foreign_lock = 1 OTHERS = 2.
IF sy-subrc <> 0. APPEND VALUE #( %tky = ls_key-%tky %fail-cause = if_abap_behv=>cause-locked ) TO failed-salesorder.
APPEND VALUE #( %tky = ls_key-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Auftrag { ls_key-SalesOrderId } ist gesperrt| ) ) TO reported-salesorder. ENDIF. ENDLOOP. ENDMETHOD.
ENDCLASS.Lock-Freigabe in cleanup_finalize
METHOD cleanup_finalize. " Nach COMMIT/ROLLBACK: Locks freigeben
" Alle gehaltenen Legacy-Locks freigeben CALL FUNCTION 'DEQUEUE_ALL'.
" Oder spezifisch: " CALL FUNCTION 'DEQUEUE_EVVBAKE' ...ENDMETHOD.Transaktionssteuerung
RAP-Transaktion vs. BAPI-Transaktion
┌─────────────────────────────────────────────────────────┐│ RAP-Transaktion ││ ││ ┌──────────────────┐ ┌──────────────────┐ ││ │ Interaktionsphase │ │ Save-Phase │ ││ │ │ │ │ ││ │ - Modify Entity │ │ save_modified: │ ││ │ - Validations │ │ ┌─────────────┐ │ ││ │ - Determinations │ │ │ BAPI Call 1 │ │ ││ │ - Actions │ │ │ COMMIT WORK │ │ ││ │ │ │ └─────────────┘ │ ││ │ (Transient) │ │ ┌─────────────┐ │ ││ │ │ │ │ BAPI Call 2 │ │ ││ │ │ │ │ COMMIT WORK │ │ ││ │ │ │ └─────────────┘ │ ││ └──────────────────┘ └──────────────────┘ ││ ││ COMMIT ENTITIES (nach save_modified) │└─────────────────────────────────────────────────────────┘Wichtige Regeln
- COMMIT WORK in save_modified: Jeder BAPI braucht sein COMMIT WORK
- Kein ROLLBACK WORK: Bei Fehlern RAP-reported füllen, Framework macht Rollback
- Reihenfolge beachten: Parent vor Children (bei Deep Operations)
METHOD save_modified. " 1. Parent-Entities zuerst IF create-salesorder IS NOT INITIAL. LOOP AT create-salesorder INTO DATA(ls_order). me->call_bapi_create_order( ls_order ). ENDLOOP. ENDIF.
" 2. Dann Child-Entities IF create-item IS NOT INITIAL. LOOP AT create-item INTO DATA(ls_item). me->call_bapi_create_item( ls_item ). ENDLOOP. ENDIF.
" 3. Updates können parallel sein IF update-salesorder IS NOT INITIAL. " ... ENDIF.
" 4. Deletes zuletzt (Children vor Parent) IF delete-item IS NOT INITIAL. " Items zuerst löschen ENDIF.
IF delete-salesorder IS NOT INITIAL. " Dann Order löschen ENDIF.ENDMETHOD.Fehlerbehandlung
Fehler aus BAPI in RAP übernehmen
METHOD handle_bapi_return. " Hilfsmethode: BAPI-RETURN → RAP-reported
LOOP AT it_return ASSIGNING FIELD-SYMBOL(<return>). CASE <return>-type. WHEN 'E' OR 'A'. " Error / Abort APPEND VALUE #( %tky = is_key %msg = new_message( id = <return>-id number = <return>-number severity = if_abap_behv_message=>severity-error v1 = <return>-message_v1 v2 = <return>-message_v2 v3 = <return>-message_v3 v4 = <return>-message_v4 ) ) TO reported-salesorder.
" Flag setzen: Transaktion fehlgeschlagen rv_has_error = abap_true.
WHEN 'W'. " Warning APPEND VALUE #( %tky = is_key %msg = new_message( id = <return>-id number = <return>-number severity = if_abap_behv_message=>severity-warning v1 = <return>-message_v1 v2 = <return>-message_v2 v3 = <return>-message_v3 v4 = <return>-message_v4 ) ) TO reported-salesorder.
WHEN 'I' OR 'S'. " Info / Success " Optional: Erfolgsmeldungen loggen ENDCASE. ENDLOOP.ENDMETHOD.Rollback bei Fehlern
METHOD save_modified. DATA: lv_error_occurred TYPE abap_bool.
" Alle Creates verarbeiten IF create-salesorder IS NOT INITIAL. LOOP AT create-salesorder INTO DATA(ls_create). me->call_bapi_create( EXPORTING is_order = ls_create IMPORTING ev_error = lv_error_occurred ).
IF lv_error_occurred = abap_true. " Bei Fehler: Keine weiteren Operations " Framework macht ROLLBACK RETURN. ENDIF. ENDLOOP. ENDIF.
" Weitere Operations nur wenn kein Fehler...ENDMETHOD.Nummernvergabe bei Legacy-Integration
Szenario 1: RAP vergibt Nummer (Empfohlen)
" In BDEF:field ( numbering : managed ) SalesOrderId;
" In save_modified: RAP-Nummer an BAPI übergebenMETHOD call_bapi_create. " RAP hat bereits SalesOrderId vergeben ls_header-doc_number = is_order-SalesOrderId.
CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING order_header_in = ls_header " ...ENDMETHOD.Szenario 2: Legacy vergibt Nummer
" In BDEF:field ( readonly ) SalesOrderId;" KEINE managed numbering!
" In Handler: early numbering implementierenCLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS get_next_number FOR NUMBERING IMPORTING entities FOR CREATE SalesOrder.ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD get_next_number. " Nummer aus Legacy-System holen LOOP AT entities ASSIGNING FIELD-SYMBOL(<entity>). " Nummernkreis aufrufen TRY. <entity>-SalesOrderId = cl_numberrange_runtime=>number_get( nr_range_nr = '01' object = 'SD_VBELN' ).
APPEND VALUE #( %cid = <entity>-%cid SalesOrderId = <entity>-SalesOrderId ) TO mapped-salesorder.
CATCH cx_number_ranges INTO DATA(lx_nr). APPEND VALUE #( %cid = <entity>-%cid %fail-cause = if_abap_behv=>cause-unspecific ) TO failed-salesorder. ENDTRY. ENDLOOP. ENDMETHOD.
ENDCLASS.Vollständiges Beispiel: Auftragsverwaltung
CDS View
@AbapCatalog.viewEnhancementCategory: [#NONE]@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Sales Order'
define root view entity ZI_SalesOrder as select from zsalesorder composition [0..*] of ZI_SalesOrderItem as _Items{ key sales_order_id as SalesOrderId, customer_id as CustomerId, sales_org as SalesOrg, distr_channel as DistrChannel, division as Division, order_date as OrderDate, purchase_order_no as PurchaseOrderNo, @Semantics.amount.currencyCode: 'CurrencyCode' total_amount as TotalAmount, @Semantics.currencyCode: true currency_code as CurrencyCode, status as Status, @Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt, @Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy, @Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt,
_Items}Behavior Definition
managed implementation in class zbp_i_salesorder unique;strict ( 2 );with draft;with unmanaged save;
define behavior for ZI_SalesOrder alias SalesOrderpersistent table zsalesorderdraft table zd_salesorderlock master total etag LastChangedAtauthorization master ( instance ){ create; update; delete;
field ( readonly ) SalesOrderId, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt; field ( numbering : managed ) SalesOrderId; field ( readonly : update ) CustomerId, SalesOrg;
validation validateCustomer on save { field CustomerId; } determination setDefaults on modify { create; } determination calcTotal on modify { field TotalAmount; }
action ( features : instance ) confirmOrder result [1] $self;
draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume; draft determine action Prepare;
association _Items { create; with draft; }
mapping for zsalesorder corresponding { SalesOrderId = sales_order_id; CustomerId = customer_id; SalesOrg = sales_org; DistrChannel = distr_channel; Division = division; OrderDate = order_date; PurchaseOrderNo = purchase_order_no; TotalAmount = total_amount; CurrencyCode = currency_code; Status = status; CreatedBy = created_by; CreatedAt = created_at; LastChangedBy = last_changed_by; LastChangedAt = last_changed_at; }}
define behavior for ZI_SalesOrderItem alias Itempersistent table zsalesorderitemdraft table zd_salesorderitemlock dependent by _SalesOrderauthorization dependent by _SalesOrder{ update; delete;
field ( readonly ) SalesOrderId, ItemId; field ( numbering : managed ) ItemId;
determination calcItemAmount on modify { field Quantity, UnitPrice; }
association _SalesOrder { with draft; }
mapping for zsalesorderitem corresponding { SalesOrderId = sales_order_id; ItemId = item_id; ProductId = product_id; Quantity = quantity; UnitPrice = unit_price; ItemAmount = item_amount; CurrencyCode = currency_code; }}Behavior Implementation
CLASS lsc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS save_modified REDEFINITION. METHODS cleanup_finalize REDEFINITION.
PRIVATE SECTION. METHODS call_legacy_create_order IMPORTING is_order TYPE zsalesorder RAISING zcx_legacy_error.
METHODS call_legacy_update_order IMPORTING is_order TYPE zsalesorder RAISING zcx_legacy_error.
METHODS call_legacy_delete_order IMPORTING iv_order_id TYPE zsalesorder-sales_order_id RAISING zcx_legacy_error.
METHODS call_legacy_create_items IMPORTING it_items TYPE STANDARD TABLE RAISING zcx_legacy_error.ENDCLASS.
CLASS lsc_salesorder IMPLEMENTATION.
METHOD save_modified. " 1. Parent Creates LOOP AT create-salesorder INTO DATA(ls_create_order). TRY. me->call_legacy_create_order( ls_create_order ). CATCH zcx_legacy_error INTO DATA(lx_create). APPEND VALUE #( %tky = ls_create_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_create->get_text( ) ) ) TO reported-salesorder. RETURN. " Abbruch bei Fehler ENDTRY. ENDLOOP.
" 2. Parent Updates LOOP AT update-salesorder INTO DATA(ls_update_order). TRY. me->call_legacy_update_order( ls_update_order ). CATCH zcx_legacy_error INTO DATA(lx_update). APPEND VALUE #( %tky = ls_update_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_update->get_text( ) ) ) TO reported-salesorder. RETURN. ENDTRY. ENDLOOP.
" 3. Child Creates IF create-item IS NOT INITIAL. TRY. me->call_legacy_create_items( create-item ). CATCH zcx_legacy_error INTO DATA(lx_items). " Fehler an erstes Item melden APPEND VALUE #( %tky = create-item[ 1 ]-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_items->get_text( ) ) ) TO reported-item. RETURN. ENDTRY. ENDIF.
" 4. Child Deletes (vor Parent Delete) LOOP AT delete-item INTO DATA(ls_delete_item). " Legacy-Delete für Item... ENDLOOP.
" 5. Parent Deletes LOOP AT delete-salesorder INTO DATA(ls_delete_order). TRY. me->call_legacy_delete_order( ls_delete_order-SalesOrderId ). CATCH zcx_legacy_error INTO DATA(lx_delete). APPEND VALUE #( %tky = ls_delete_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = lx_delete->get_text( ) ) ) TO reported-salesorder. RETURN. ENDTRY. ENDLOOP. ENDMETHOD.
METHOD cleanup_finalize. " Locks freigeben CALL FUNCTION 'DEQUEUE_ALL'. ENDMETHOD.
METHOD call_legacy_create_order. DATA: ls_header TYPE bapisdhead, lt_return TYPE bapiret2_t, lv_vbeln TYPE vbeln.
" Mapping ls_header = VALUE #( doc_type = 'TA' sales_org = is_order-sales_org distr_chan = is_order-distr_channel division = is_order-division sold_to = is_order-customer_id purch_no = is_order-purchase_order_no doc_date = is_order-order_date ).
" BAPI aufrufen CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING order_header_in = ls_header IMPORTING salesdocument = lv_vbeln TABLES return = lt_return.
" Fehlerprüfung LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'. RAISE EXCEPTION TYPE zcx_legacy_error EXPORTING textid = zcx_legacy_error=>bapi_error message = <return>-message. ENDLOOP.
" Commit CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true. ENDMETHOD.
METHOD call_legacy_update_order. DATA: ls_header_in TYPE bapisdh1, ls_header_inx TYPE bapisdh1x, lt_return TYPE bapiret2_t.
" %control auswerten ls_header_inx-updateflag = 'U'.
IF is_order-%control-purchase_order_no = if_abap_behv=>mk-on. ls_header_in-purch_no = is_order-purchase_order_no. ls_header_inx-purch_no = abap_true. ENDIF.
" BAPI aufrufen CALL FUNCTION 'BAPI_SALESORDER_CHANGE' EXPORTING salesdocument = CONV #( is_order-sales_order_id ) order_header_in = ls_header_in order_header_inx = ls_header_inx TABLES return = lt_return.
" Fehlerprüfung LOOP AT lt_return ASSIGNING FIELD-SYMBOL(<return>) WHERE type CA 'EA'. RAISE EXCEPTION TYPE zcx_legacy_error EXPORTING textid = zcx_legacy_error=>bapi_error message = <return>-message. ENDLOOP.
" Commit CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true. ENDMETHOD.
METHOD call_legacy_delete_order. " Implementierung analog... ENDMETHOD.
METHOD call_legacy_create_items. " Bulk-Insert für Items... ENDMETHOD.
ENDCLASS.Best Practices
1. Wrapper-Klassen nutzen
Kapseln Sie BAPI-Aufrufe in wiederverwendbare Wrapper:
" Wrapper statt direkter BAPI-AufrufDATA(lo_order_api) = NEW zcl_salesorder_wrapper( ).
TRY. lo_order_api->create_order( EXPORTING is_order = ls_order_data IMPORTING ev_order_id = lv_order_id ). CATCH zcx_order_error INTO DATA(lx_error). " FehlerbehandlungENDTRY.2. %control immer auswerten
" NUR geänderte Felder an BAPI übergebenIF is_entity-%control-Description = if_abap_behv=>mk-on. ls_bapi_data-description = is_entity-Description. ls_bapi_datax-description = abap_true.ENDIF.3. Transaktionsgrenzen beachten
" COMMIT WORK nach jedem BAPICALL FUNCTION 'BAPI_...'
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.
" NICHT: Mehrere BAPIs sammeln und am Ende committen4. Fehlermeldungen standardisieren
" Einheitliche Exception-Klasse für alle Legacy-FehlerCLASS zcx_legacy_error DEFINITION INHERITING FROM cx_static_check. " Mit T100-Messages für MehrsprachigkeitENDCLASS.5. Logging implementieren
METHOD save_modified. " Business Application Logging für Nachvollziehbarkeit DATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( object = 'ZSALES' subobject = 'SAVE' ) ).
" Erfolge und Fehler loggen lo_log->add_item( cl_bali_message_setter=>create_from_sy( ) ). lo_log->save( ).ENDMETHOD.Wann Unmanaged Save verwenden?
| Szenario | Empfehlung |
|---|---|
| Neue Anwendung ohne Legacy | ❌ Managed (ohne unmanaged save) |
| BAPI-Integration erforderlich | ✅ Unmanaged Save |
| Draft-Funktionalität + Legacy | ✅ Unmanaged Save |
| Komplexe Transaktionen | ✅ Unmanaged Save |
| Migration von Dynpro-Anwendung | ✅ Unmanaged Save |
| Volle Kontrolle, kein Draft | ❌ Unmanaged (komplett) |
Weitere Ressourcen
- Managed vs. Unmanaged: RAP Managed vs. Unmanaged
- Wrapper-Klassen: Wrapper-Klassen in ABAP Cloud
- EML Grundlagen: Entity Manipulation Language
- RAP Basics: RAP Grundlagen
- Draft Handling: RAP Draft Handling