RAP Unmanaged Save - Integration mit Legacy-Code

kategorie
RAP
Veröffentlicht
autor
Johannes

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 Auftragsanlage
CALL 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

AspektManagedUnmanagedUnmanaged Save
InteraktionsphaseFrameworkSie selbstFramework
Transient BufferFramework verwaltetSie selbstFramework verwaltet
Save-PhaseFrameworkSie selbstSie selbst
Draft-Support✅ Ja❌ Nein✅ Ja
Legacy-Integration❌ Schwierig✅ Ja✅ Ja
Typischer EinsatzGreenfieldVollständig CustomLegacy-Integration

Unmanaged Save aktivieren

Behavior Definition

managed implementation in class zbp_i_salesorder unique;
strict ( 2 );
with draft;
" Wichtig: Unmanaged Save aktivieren
with unmanaged save;
define behavior for ZI_SalesOrder alias SalesOrder
persistent table zsalesorder
draft table zd_salesorder
lock master total etag LastChangedAt
authorization 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 Item
persistent table zsalesorderitem
draft table zd_salesorderitem
lock dependent by _SalesOrder
authorization 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

  1. COMMIT WORK in save_modified: Jeder BAPI braucht sein COMMIT WORK
  2. Kein ROLLBACK WORK: Bei Fehlern RAP-reported füllen, Framework macht Rollback
  3. 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 übergeben
METHOD 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 implementieren
CLASS 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 SalesOrder
persistent table zsalesorder
draft table zd_salesorder
lock master total etag LastChangedAt
authorization 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 Item
persistent table zsalesorderitem
draft table zd_salesorderitem
lock dependent by _SalesOrder
authorization 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-Aufruf
DATA(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).
" Fehlerbehandlung
ENDTRY.

2. %control immer auswerten

" NUR geänderte Felder an BAPI übergeben
IF 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 BAPI
CALL FUNCTION 'BAPI_...'
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = abap_true.
" NICHT: Mehrere BAPIs sammeln und am Ende committen

4. Fehlermeldungen standardisieren

" Einheitliche Exception-Klasse für alle Legacy-Fehler
CLASS zcx_legacy_error DEFINITION
INHERITING FROM cx_static_check.
" Mit T100-Messages für Mehrsprachigkeit
ENDCLASS.

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?

SzenarioEmpfehlung
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