RAP Managed vs Unmanaged: Wann welches Szenario nutzen?

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Managed vs. Unmanaged ist die zentrale Architekturentscheidung in RAP (RESTful ABAP Programming). Sie bestimmt, wer die Transaktionssteuerung und CRUD-Operationen übernimmt: das RAP Framework (Managed) oder Sie selbst (Unmanaged).

Die Grundfrage

" Wer führt CREATE, UPDATE, DELETE aus?
" Managed: "SAP, mach du das!"
managed implementation in class zbp_i_travel unique;
" Unmanaged: "Ich mache es selbst!"
unmanaged implementation in class zbp_i_travel unique;

Managed Szenario: Framework macht die Arbeit

Wann Managed nutzen?

Perfekt für:

  • Neue Anwendungen (Greenfield-Entwicklung)
  • Standardgeschäftsprozesse ohne komplexe Legacy-Logik
  • Transaktionale Fiori-Apps mit CRUD-Operationen
  • Wenn Sie schnell ein funktionierendes BO brauchen
  • Draft-Funktionalität (Zwischenspeichern)

Nicht geeignet für:

  • Integration mit Legacy-Code (Funktionsbausteine, BAPIs)
  • Komplexe Transaktionslogik außerhalb von RAP
  • Wenn Sie volle Kontrolle über DB-Zugriffe brauchen
  • Migration von Dynpro/Web Dynpro mit bestehendem Verhalten

Managed: Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with draft; " Draft nur in Managed verfügbar!
define behavior for ZI_Travel alias Travel
persistent table ztravel " Framework schreibt hier automatisch
draft table zdraft_travel " Für Draft-Daten
lock master " Framework verwaltet Locks
total etag LastChangedAt " Optimistic Locking via ETag
authorization master ( instance )
{
// CRUD: NUR deklarieren, keine Implementierung nötig!
create;
update;
delete;
// Felder: Framework kümmert sich um Mapping
field ( readonly ) TravelId;
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
field ( numbering : managed ) TravelId; " Auto-Nummernvergabe!
// Geschäftslogik: Hier implementieren Sie
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Draft Actions: Framework liefert sie automatisch
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
// Assoziationen
association _Bookings { create; with draft; }
}
define behavior for ZI_Booking alias Booking
persistent table zbooking
draft table zdraft_booking
lock dependent by _Travel " Lock vom Parent
authorization dependent by _Travel
{
update;
delete;
field ( readonly ) TravelId, BookingId;
field ( numbering : managed ) BookingId;
association _Travel { with draft; }
}

Managed: Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" ✅ NUR Geschäftslogik implementieren!
" Validation: Wird bei Save ausgeführt
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
" Determination: Automatische Werte setzen
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
" Action: Geschäftsoperation
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
" ❌ NICHT implementieren: get_global_authorizations, read, create, update, delete
" → Framework macht das automatisch!
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates.
" Framework hat Daten BEREITS gelesen → nur validieren
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel).
" Business Rule prüfen
IF ls_travel-EndDate < ls_travel-BeginDate.
" Framework-Struktur für Fehler füllen
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Enddatum muss nach Beginndatum liegen'
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setStatusNew.
" Framework hat Entity erstellt → Defaultwerte setzen
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" Nur Neue (Status initial) auf 'O' (Open) setzen
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL )
( %tky = travel-%tky
Status = 'O' ) )
REPORTED DATA(reported_modify).
ENDMETHOD.
METHOD acceptTravel.
" Action = Business Operation (nicht einfach nur Update)
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status LastChangedAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'A'
LastChangedAt = cl_abap_context_info=>get_system_date( ) ) )
FAILED failed
REPORTED reported.
" Ergebnis zurückgeben (result [1] $self)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
ENDCLASS.

Was Sie NICHT schreiben müssen:

  • SELECT * FROM ztravel – Framework macht das
  • INSERT ztravel FROM ... – Framework macht das
  • UPDATE ztravel SET ... – Framework macht das
  • DELETE FROM ztravel – Framework macht das
  • ❌ Lock-Handling – Framework macht das
  • ❌ Nummernvergabe – Framework macht das (bei numbering : managed)

Unmanaged Szenario: Sie haben volle Kontrolle

Wann Unmanaged nutzen?

Perfekt für:

  • Legacy-Integration (BAPIs, Funktionsbausteine einbinden)
  • Komplexe Transaktionslogik (mehrstufige Commits)
  • Migrating von bestehenden Dynpro/Web-Dynpro-Programmen
  • Wenn Sie spezielle DB-Operationen brauchen (z.B. Native SQL)
  • Custom Lock-Mechanismen

Nicht geeignet für:

  • Quick Prototyping
  • Standard-CRUD ohne Besonderheiten
  • Draft-Funktionalität (nicht verfügbar in Unmanaged!)

Unmanaged: Behavior Definition

unmanaged implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
lock master
authorization master ( instance )
etag master LastChangedAt
{
// CRUD: Alles muss implementiert werden!
create;
update;
delete;
// Auch Read MUSS implementiert werden (anders als Managed!)
// Geschäftslogik wie bei Managed
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Lock-Handling müssen SIE implementieren
lock ( lock_key );
}

Unmanaged: Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" ✅ Alles SELBST implementieren:
" Create: Neue Entities erstellen
create FOR MODIFY
IMPORTING entities FOR CREATE Travel,
" Update: Bestehende Entities ändern
update FOR MODIFY
IMPORTING entities FOR UPDATE Travel,
" Delete: Entities löschen
delete FOR MODIFY
IMPORTING keys FOR DELETE Travel,
" Read: Entities lesen
read FOR READ
IMPORTING keys FOR READ Travel RESULT result,
" Lock: Sperren verwalten
lock FOR LOCK
IMPORTING keys FOR LOCK Travel,
" Feature Control
get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR Travel RESULT result,
" Geschäftslogik (wie bei Managed)
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD create.
" Daten aus entities-Parameter extrahieren
DATA lt_travel TYPE TABLE FOR CREATE zi_travel.
lt_travel = entities.
" Nummernvergabe (MANUELL, da unmanaged!)
LOOP AT lt_travel ASSIGNING FIELD-SYMBOL(<fs_travel>).
" Nummer aus Nummernkreis holen
TRY.
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number(
nr_range_nr = '01'
object = 'ZTRAVEL'
).
CATCH cx_number_ranges INTO DATA(lx_nr).
" Fehler behandeln
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
CONTINUE.
ENDTRY.
" Defaults setzen
<fs_travel>-CreatedBy = sy-uname.
<fs_travel>-CreatedAt = cl_abap_context_info=>get_system_date( ).
<fs_travel>-Status = 'O'.
" In DB schreiben (MANUELL!)
INSERT ztravel FROM @( CORRESPONDING #( <fs_travel> ) ).
IF sy-subrc = 0.
" Erfolgreiches Mapping zurückgeben
APPEND VALUE #(
%cid = <fs_travel>-%cid
TravelId = <fs_travel>-TravelId
) TO mapped-travel.
ELSE.
" Fehler
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
" Zu aktualisierende Felder extrahieren
LOOP AT entities INTO DATA(ls_entity).
" %control prüft, welche Felder geändert werden sollen
IF ls_entity-%control-Status = if_abap_behv=>mk-on.
" Status wurde geändert → DB-Update
UPDATE ztravel
SET status = @ls_entity-Status,
last_changed_by = @sy-uname,
last_changed_at = @cl_abap_context_info=>get_system_date( )
WHERE travel_id = @ls_entity-TravelId.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_entity-%tky
%fail-cause = if_abap_behv=>cause-not_found
) TO failed-travel.
ENDIF.
ENDIF.
" Weitere Felder analog...
IF ls_entity-%control-Description = if_abap_behv=>mk-on.
UPDATE ztravel SET description = @ls_entity-Description
WHERE travel_id = @ls_entity-TravelId.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD delete.
" Löschen aus DB
DELETE FROM ztravel
WHERE travel_id IN ( SELECT TravelId FROM @keys AS k ).
IF sy-dbcnt < lines( keys ).
" Nicht alle gelöscht → Fehler
LOOP AT keys INTO DATA(ls_key).
SELECT SINGLE @abap_true FROM ztravel
WHERE travel_id = @ls_key-TravelId
INTO @DATA(lv_exists).
IF lv_exists = abap_true.
" Existiert noch → konnte nicht gelöscht werden
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD read.
" Daten aus DB lesen
SELECT * FROM ztravel
FOR ALL ENTRIES IN @keys
WHERE travel_id = @keys-TravelId
INTO TABLE @DATA(lt_db_travel).
" In RAP-Struktur konvertieren
result = CORRESPONDING #( lt_db_travel MAPPING TO ENTITY ).
ENDMETHOD.
METHOD lock.
" Lock-Objekt sperren (via ENQUEUE)
LOOP AT keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZTRAVEL'
EXPORTING
mode_ztravel = 'E'
mandt = sy-mandt
travel_id = ls_key-TravelId
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Lock fehlgeschlagen
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD get_instance_features.
" Feature Control (wie bei Managed)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
result = VALUE #( FOR travel IN lt_travel
( %tky = travel-%tky
%features-%action-acceptTravel = COND #(
WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled
)
)
).
ENDMETHOD.
METHOD validateDates.
" Identisch zu Managed
" ... (siehe Managed-Beispiel oben)
ENDMETHOD.
METHOD setStatusNew.
" Identisch zu Managed
" ... (siehe Managed-Beispiel oben)
ENDMETHOD.
METHOD acceptTravel.
" Identisch zu Managed
" ... (siehe Managed-Beispiel oben)
ENDMETHOD.
ENDCLASS.

Was Sie SELBST schreiben müssen:

  • SELECT * FROM ztravel – Sie müssen lesen
  • INSERT ztravel FROM ... – Sie müssen schreiben
  • UPDATE ztravel SET ... – Sie müssen updaten
  • DELETE FROM ztravel – Sie müssen löschen
  • ✅ Lock-Handling – Sie müssen sperren/entsperren
  • ✅ Nummernvergabe – Sie müssen Nummernkreise aufrufen

Vergleich: Managed vs. Unmanaged

AspektManagedUnmanaged
CRUD-ImplementierungAutomatisch durch FrameworkManuell implementieren
DB-ZugriffeFrameworkSie selbst (SELECT, INSERT, etc.)
Nummernvergabefield ( numbering : managed )Manuell via cl_numberrange_runtime
Lock-HandlingAutomatischENQUEUE/DEQUEUE manuell
Draft-Support✅ Ja (out-of-the-box)❌ Nein
Entwicklungsaufwand🟢 Gering (nur Business Logic)🔴 Hoch (alles selbst)
Flexibilität🔴 Eingeschränkt (Framework-Regeln)🟢 Maximum (volle Kontrolle)
Legacy-Integration🔴 Schwierig🟢 Einfach (BAPIs etc.)
Performance-Tuning🔴 Limitiert🟢 Volle Kontrolle
Best forNeue Cloud-AppsLegacy-Migration
Lernkurve🟢 Flach (weniger Code)🔴 Steil (viel Code)

Hybrides Szenario: Managed save + Unmanaged side effects

Problem: Sie wollen Managed nutzen, aber brauchen Custom-Logik nach dem Save (z.B. externe API aufrufen).

Lösung: save_modified Hook in Managed Szenario:

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with additional save; " ← Hook aktivieren
define behavior for ZI_Travel alias Travel
persistent table ztravel
{
create;
update;
delete;
// ...
}

Implementation:

CLASS lsc_travel DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS:
" save_modified: NACH Framework-Save, VOR finalem COMMIT
save_modified REDEFINITION,
" cleanup_finalize: NACH COMMIT (oder ROLLBACK)
cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_travel IMPLEMENTATION.
METHOD save_modified.
" Framework hat BEREITS in DB geschrieben (aber noch nicht committed)
" Jetzt können Sie Side-Effects implementieren:
" Beispiel: Für jede neue Travel eine Email senden
IF create-travel IS NOT INITIAL.
LOOP AT create-travel INTO DATA(ls_created).
" Email-API aufrufen (siehe /email-sending/)
TRY.
cl_email_sender=>send_notification(
recipient = 'travel@company.com'
subject = |Neue Reise { ls_created-TravelId } erstellt|
body = |Reise von { ls_created-BeginDate } bis { ls_created-EndDate }|
).
CATCH cx_send_req_bcs INTO DATA(lx_email).
" Fehler loggen, aber Transaktion NICHT abbrechen
cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_email ) )->save( ).
ENDTRY.
ENDLOOP.
ENDIF.
" Beispiel: Externe API für Updates aufrufen
IF update-travel IS NOT INITIAL.
LOOP AT update-travel INTO DATA(ls_updated).
" HTTP-Call zu externem System (siehe /http-client/)
DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( ... ).
" ... HTTP Request senden
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD cleanup_finalize.
" NACH COMMIT oder ROLLBACK
" Hier können Sie Cleanup-Logik implementieren
" (z.B. temporäre Dateien löschen, Connections schließen)
ENDMETHOD.
ENDCLASS.

Wann nutzen?

  • Managed für Standard-CRUD + DB-Operationen
  • save_modified für Side-Effects (Email, externe APIs, Logging)
  • Volle Framework-Features (Draft, Numbering, etc.) bleiben erhalten

Entscheidungsbaum

┌─────────────────────────────────────────────────┐
│ Neue Anwendung (Greenfield)? │
└──┬────────────────────────────────────────┬─────┘
│ Ja │ Nein
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Standard-CRUD ausreichend? │ │ Legacy-System integrieren? │
└──┬──────────────────────┬───┘ └──┬──────────────────────┬───┘
│ Ja │ Nein │ Ja │ Nein
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ MANAGED │ │ Komplexe DB- │ │UNMANAGED │ │ Spezielle │
│ │ │ Operationen? │ │ │ │ Transaktion? │
│ ✅ │ └──┬───────┬───┘ │ ✅ │ └──┬───────┬───┘
└──────────┘ │ Ja │ Nein └──────────┘ │ Ja │ Nein
▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│UNMANAGED │ │ MANAGED + │ │UNMANAGED │ │ MANAGED │
│ │ │save_modified│ │ │ │ │
│ ✅ │ │ ✅ │ │ ✅ │ │ ✅ │
└──────────┘ └────────────┘ └──────────┘ └──────────┘

Migration: Von Unmanaged zu Managed

Szenario: Sie haben ein Unmanaged BO und wollen zu Managed migrieren.

Schritte:

  1. Behavior Definition anpassen:
" Vorher:
unmanaged implementation in class zbp_i_travel unique;
" Nachher:
managed implementation in class zbp_i_travel unique;
" Zusätzlich:
define behavior for ZI_Travel alias Travel
persistent table ztravel " ← Hinzufügen
// draft table zdraft_travel ← Optional: Draft aktivieren
lock master
{
create;
update;
delete;
field ( numbering : managed ) TravelId; " ← Statt manueller Nummernvergabe
// ...
}
  1. Behavior Implementation aufräumen:
" Vorher (Unmanaged): Alle Methoden
CLASS lhc_travel DEFINITION ...
METHODS:
create FOR MODIFY ...,
update FOR MODIFY ...,
delete FOR MODIFY ...,
read FOR READ ...,
lock FOR LOCK ...,
validateDates FOR VALIDATE ...,
// etc.
" Nachher (Managed): Nur Business Logic behalten
CLASS lhc_travel DEFINITION ...
METHODS:
" ❌ create, update, delete, read, lock → LÖSCHEN!
" ✅ Nur Business Logic:
validateDates FOR VALIDATE ...,
setStatusNew FOR DETERMINE ...,
acceptTravel FOR MODIFY ...
  1. DB-Zugriffe entfernen:
" ❌ Vorher (Unmanaged):
METHOD create.
INSERT ztravel FROM @( CORRESPONDING #( entities ) ).
" ...
ENDMETHOD.
" ✅ Nachher (Managed): Methode komplett löschen!
" Framework macht INSERT automatisch
  1. Nummernvergabe:
" ❌ Vorher (Unmanaged):
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number( ... ).
" ✅ Nachher (Managed):
" In BDEF: field ( numbering : managed ) TravelId;
" → Framework vergibt automatisch aus `persistent table ztravel`-Key-Sequenz

Wichtige Hinweise / Best Practice

  • Default = Managed: Nutzen Sie Managed, außer Sie haben einen guten Grund für Unmanaged
  • Draft benötigt Managed: Draft-Funktionalität ist NUR in Managed verfügbar
  • Unmanaged für Legacy: Wenn Sie BAPIs/FuBas einbinden müssen → Unmanaged
  • Hybrid möglich: managed + with additional save für Side-Effects
  • Performance: Managed ist NICHT langsamer – Framework ist optimiert
  • Testing: Managed ist einfacher zu testen (weniger Code = weniger Fehler)
  • Lock-Objekte: In Unmanaged müssen Sie Lock-Objekte (SE11: ENQUEUE_*) selbst erstellen
  • Nummernkreise: In Unmanaged müssen Sie Nummernkreise (SNRO) selbst verwalten
  • Transaktionen: Unmanaged gibt volle Kontrolle über COMMIT WORK und ROLLBACK
  • Migration: Von Unmanaged → Managed ist aufwändiger als andersherum
  • Dokumentation: Begründen Sie Unmanaged-Entscheidung (für künftige Entwickler)
  • IN LOCAL MODE: In BEIDEN Szenarien für Behavior-Implementation-Code verwenden
  • EML nutzen: Auch in Unmanaged sollten Sie EML für BO-Zugriffe nutzen (siehe EML Guide)

Weitere Ressourcen