Managed vs. Unmanaged is the central architectural decision in RAP (RESTful ABAP Programming). It determines who handles transaction control and CRUD operations: the RAP Framework (Managed) or you (Unmanaged).
The Basic Question
" Who performs CREATE, UPDATE, DELETE?
" Managed: "SAP, you do it!"managed implementation in class zbp_i_travel unique;
" Unmanaged: "I'll do it myself!"unmanaged implementation in class zbp_i_travel unique;Managed Scenario: Framework Does the Work
When to Use Managed?
Perfect for:
- New applications (Greenfield development)
- Standard business processes without complex legacy logic
- Transactional Fiori apps with CRUD operations
- When you need a working BO quickly
- Draft functionality (intermediate saving)
Not suitable for:
- Integration with legacy code (function modules, BAPIs)
- Complex transaction logic outside of RAP
- When you need full control over DB access
- Migration from Dynpro/Web Dynpro with existing behavior
Managed: Behavior Definition
managed implementation in class zbp_i_travel unique;strict ( 2 );with draft; " Draft only available in Managed!
define behavior for ZI_Travel alias Travelpersistent table ztravel " Framework writes here automaticallydraft table zdraft_travel " For draft datalock master " Framework manages lockstotal etag LastChangedAt " Optimistic locking via ETagauthorization master ( instance ){ // CRUD: Only declare, no implementation needed! create; update; delete;
// Fields: Framework handles mapping field ( readonly ) TravelId; field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt; field ( numbering : managed ) TravelId; " Auto number assignment!
// Business logic: Implement here validation validateDates on save { field BeginDate, EndDate; } determination setStatusNew on modify { create; } action acceptTravel result [1] $self;
// Draft Actions: Framework provides them automatically draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume;
// Associations association _Bookings { create; with draft; }}
define behavior for ZI_Booking alias Bookingpersistent table zbookingdraft table zdraft_bookinglock dependent by _Travel " Lock from parentauthorization 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: " Only implement business logic!
" Validation: Executed on save validateDates FOR VALIDATE ON SAVE IMPORTING keys FOR Travel~validateDates,
" Determination: Set automatic values setStatusNew FOR DETERMINE ON MODIFY IMPORTING keys FOR Travel~setStatusNew,
" Action: Business operation acceptTravel FOR MODIFY IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
" DO NOT implement: get_global_authorizations, read, create, update, delete " -> Framework does this automatically!ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates. " Framework has ALREADY read data -> only validate 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). " Check business rule IF ls_travel-EndDate < ls_travel-BeginDate. " Fill framework structure for errors 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 = 'End date must be after begin date' ) ) TO reported-travel. ENDIF. ENDLOOP. ENDMETHOD.
METHOD setStatusNew. " Framework has created entity -> set default values READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" Only set new ones (initial status) to 'O' (Open) 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 (not just an 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.
" Return result (result [1] $self) READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT result. ENDMETHOD.
ENDCLASS.What you DON’T need to write:
SELECT * FROM ztravel- Framework does thisINSERT ztravel FROM ...- Framework does thisUPDATE ztravel SET ...- Framework does thisDELETE FROM ztravel- Framework does this- Lock handling - Framework does this
- Number assignment - Framework does this (with
numbering : managed)
Unmanaged Scenario: You Have Full Control
When to Use Unmanaged?
Perfect for:
- Legacy integration (include BAPIs, function modules)
- Complex transaction logic (multi-stage commits)
- Migrating from existing Dynpro/Web Dynpro programs
- When you need special DB operations (e.g., Native SQL)
- Custom lock mechanisms
Not suitable for:
- Quick prototyping
- Standard CRUD without special requirements
- Draft functionality (not available in Unmanaged!)
Unmanaged: Behavior Definition
unmanaged implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travellock masterauthorization master ( instance )etag master LastChangedAt{ // CRUD: Everything must be implemented! create; update; delete;
// Read MUST also be implemented (unlike Managed!)
// Business logic like in Managed validation validateDates on save { field BeginDate, EndDate; } determination setStatusNew on modify { create; } action acceptTravel result [1] $self;
// Lock handling must be implemented by YOU lock ( lock_key );}Unmanaged: Behavior Implementation
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS: " Implement EVERYTHING yourself:
" Create: Create new entities create FOR MODIFY IMPORTING entities FOR CREATE Travel,
" Update: Modify existing entities update FOR MODIFY IMPORTING entities FOR UPDATE Travel,
" Delete: Delete entities delete FOR MODIFY IMPORTING keys FOR DELETE Travel,
" Read: Read entities read FOR READ IMPORTING keys FOR READ Travel RESULT result,
" Lock: Manage locks 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,
" Business logic (like in 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. " Extract data from entities parameter DATA lt_travel TYPE TABLE FOR CREATE zi_travel. lt_travel = entities.
" Number assignment (MANUAL, since unmanaged!) LOOP AT lt_travel ASSIGNING FIELD-SYMBOL(<fs_travel>). " Get number from number range TRY. <fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number( nr_range_nr = '01' object = 'ZTRAVEL' ). CATCH cx_number_ranges INTO DATA(lx_nr). " Handle error APPEND VALUE #( %cid = <fs_travel>-%cid %fail-cause = if_abap_behv=>cause-unspecific ) TO failed-travel. CONTINUE. ENDTRY.
" Set defaults <fs_travel>-CreatedBy = sy-uname. <fs_travel>-CreatedAt = cl_abap_context_info=>get_system_date( ). <fs_travel>-Status = 'O'.
" Write to DB (MANUAL!) INSERT ztravel FROM @( CORRESPONDING #( <fs_travel> ) ).
IF sy-subrc = 0. " Return successful mapping APPEND VALUE #( %cid = <fs_travel>-%cid TravelId = <fs_travel>-TravelId ) TO mapped-travel. ELSE. " Error APPEND VALUE #( %cid = <fs_travel>-%cid %fail-cause = if_abap_behv=>cause-unspecific ) TO failed-travel. ENDIF. ENDLOOP. ENDMETHOD.
METHOD update. " Extract fields to update LOOP AT entities INTO DATA(ls_entity). " %control checks which fields should be changed IF ls_entity-%control-Status = if_abap_behv=>mk-on. " Status was changed -> 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.
" Handle additional fields similarly... 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. " Delete from DB DELETE FROM ztravel WHERE travel_id IN ( SELECT TravelId FROM @keys AS k ).
IF sy-dbcnt < lines( keys ). " Not all deleted -> error 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. " Still exists -> could not be deleted APPEND VALUE #( %tky = ls_key-%tky %fail-cause = if_abap_behv=>cause-locked ) TO failed-travel. ENDIF. ENDLOOP. ENDIF. ENDMETHOD.
METHOD read. " Read data from DB SELECT * FROM ztravel FOR ALL ENTRIES IN @keys WHERE travel_id = @keys-TravelId INTO TABLE @DATA(lt_db_travel).
" Convert to RAP structure result = CORRESPONDING #( lt_db_travel MAPPING TO ENTITY ). ENDMETHOD.
METHOD lock. " Lock the lock object (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 failed 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 (like in 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. " Identical to Managed " ... (see Managed example above) ENDMETHOD.
METHOD setStatusNew. " Identical to Managed " ... (see Managed example above) ENDMETHOD.
METHOD acceptTravel. " Identical to Managed " ... (see Managed example above) ENDMETHOD.
ENDCLASS.What you MUST write yourself:
SELECT * FROM ztravel- You must readINSERT ztravel FROM ...- You must writeUPDATE ztravel SET ...- You must updateDELETE FROM ztravel- You must delete- Lock handling - You must lock/unlock
- Number assignment - You must call number ranges
Comparison: Managed vs. Unmanaged
| Aspect | Managed | Unmanaged |
|---|---|---|
| CRUD implementation | Automatic by framework | Implement manually |
| DB access | Framework | You (SELECT, INSERT, etc.) |
| Number assignment | field ( numbering : managed ) | Manual via cl_numberrange_runtime |
| Lock handling | Automatic | ENQUEUE/DEQUEUE manually |
| Draft support | Yes (out-of-the-box) | No |
| Development effort | Low (only business logic) | High (everything yourself) |
| Flexibility | Limited (framework rules) | Maximum (full control) |
| Legacy integration | Difficult | Easy (BAPIs etc.) |
| Performance tuning | Limited | Full control |
| Best for | New cloud apps | Legacy migration |
| Learning curve | Flat (less code) | Steep (lots of code) |
Hybrid Scenario: Managed Save + Unmanaged Side Effects
Problem: You want to use Managed, but need custom logic after save (e.g., call external API).
Solution: save_modified hook in Managed scenario:
managed implementation in class zbp_i_travel unique;strict ( 2 );with additional save; " <- Activate hook
define behavior for ZI_Travel alias Travelpersistent table ztravel{ create; update; delete; // ...}Implementation:
CLASS lsc_travel DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS: " save_modified: AFTER framework save, BEFORE final COMMIT save_modified REDEFINITION,
" cleanup_finalize: AFTER COMMIT (or ROLLBACK) cleanup_finalize REDEFINITION.ENDCLASS.
CLASS lsc_travel IMPLEMENTATION.
METHOD save_modified. " Framework has ALREADY written to DB (but not yet committed) " Now you can implement side effects:
" Example: Send email for each new Travel IF create-travel IS NOT INITIAL. LOOP AT create-travel INTO DATA(ls_created). " Call email API (see /en/email-sending/) TRY. cl_email_sender=>send_notification( subject = |New travel { ls_created-TravelId } created| body = |Travel from { ls_created-BeginDate } to { ls_created-EndDate }| ). CATCH cx_send_req_bcs INTO DATA(lx_email). " Log error, but DO NOT abort transaction cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_email ) )->save( ). ENDTRY. ENDLOOP. ENDIF.
" Example: Call external API for updates IF update-travel IS NOT INITIAL. LOOP AT update-travel INTO DATA(ls_updated). " HTTP call to external system (see /en/http-client/) DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( ... ). " ... send HTTP request ENDLOOP. ENDIF. ENDMETHOD.
METHOD cleanup_finalize. " AFTER COMMIT or ROLLBACK " Here you can implement cleanup logic " (e.g., delete temporary files, close connections) ENDMETHOD.
ENDCLASS.When to use?
- Managed for standard CRUD + DB operations
save_modifiedfor side effects (email, external APIs, logging)- Full framework features (Draft, Numbering, etc.) remain available
Decision Tree
+--------------------------------------------------+| New application (Greenfield)? |+---+------------------------------------------+----+ | Yes | No v v+------------------------------+ +------------------------------+| Standard CRUD sufficient? | | Integrate legacy system? |+---+-----------------------+--+ +---+-----------------------+--+ | Yes | No | Yes | No v v v v+----------+ +--------------+ +----------+ +--------------+| MANAGED | | Complex DB | |UNMANAGED | | Special || | | operations? | | | | transaction? |+----------+ +---+------+---+ +----------+ +---+------+---+ | Yes | No | Yes | No v v v v +----------+ +------------+ +----------+ +----------+ |UNMANAGED | | MANAGED + | |UNMANAGED | | MANAGED | | | |save_modified| | | | | +----------+ +------------+ +----------+ +----------+Migration: From Unmanaged to Managed
Scenario: You have an Unmanaged BO and want to migrate to Managed.
Steps:
- Adjust behavior definition:
" Before:unmanaged implementation in class zbp_i_travel unique;
" After:managed implementation in class zbp_i_travel unique;
" Additionally:define behavior for ZI_Travel alias Travelpersistent table ztravel " <- Add// draft table zdraft_travel <- Optional: Activate draftlock master{ create; update; delete;
field ( numbering : managed ) TravelId; " <- Instead of manual number assignment // ...}- Clean up behavior implementation:
" Before (Unmanaged): All methodsCLASS lhc_travel DEFINITION ... METHODS: create FOR MODIFY ..., update FOR MODIFY ..., delete FOR MODIFY ..., read FOR READ ..., lock FOR LOCK ..., validateDates FOR VALIDATE ..., // etc.
" After (Managed): Keep only business logicCLASS lhc_travel DEFINITION ... METHODS: " create, update, delete, read, lock -> DELETE! " Only business logic: validateDates FOR VALIDATE ..., setStatusNew FOR DETERMINE ..., acceptTravel FOR MODIFY ...- Remove DB access:
" Before (Unmanaged):METHOD create. INSERT ztravel FROM @( CORRESPONDING #( entities ) ). " ...ENDMETHOD.
" After (Managed): Delete method completely!" Framework does INSERT automatically- Number assignment:
" Before (Unmanaged):<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number( ... ).
" After (Managed):" In BDEF: field ( numbering : managed ) TravelId;" -> Framework assigns automatically from `persistent table ztravel` key sequenceImportant Notes / Best Practices
- Default = Managed: Use Managed unless you have a good reason for Unmanaged
- Draft requires Managed: Draft functionality is ONLY available in Managed
- Unmanaged for Legacy: If you need to include BAPIs/function modules -> Unmanaged
- Hybrid possible:
managed+with additional savefor side effects - Performance: Managed is NOT slower - the framework is optimized
- Testing: Managed is easier to test (less code = fewer bugs)
- Lock objects: In Unmanaged you must create lock objects (SE11: ENQUEUE_*) yourself
- Number ranges: In Unmanaged you must manage number ranges (SNRO) yourself
- Transactions: Unmanaged gives full control over
COMMIT WORKandROLLBACK - Migration: From Unmanaged -> Managed is more effort than the other way around
- Documentation: Document your Unmanaged decision (for future developers)
- IN LOCAL MODE: Use in BOTH scenarios for behavior implementation code
- Use EML: Even in Unmanaged you should use EML for BO access (see EML Guide)
Further Resources
- RAP Basics: /en/rap-basics/
- EML Guide: /en/eml-entity-manipulation-language/
- ABAP Cloud: /en/abap-cloud-definition/