RAP Managed vs Unmanaged: When to Use Which Scenario?

Category
ABAP-Statements
Published
Author
Johannes

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 Travel
persistent table ztravel " Framework writes here automatically
draft table zdraft_travel " For draft data
lock master " Framework manages locks
total etag LastChangedAt " Optimistic locking via ETag
authorization 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 Booking
persistent table zbooking
draft table zdraft_booking
lock dependent by _Travel " Lock from 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:
" 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 this
  • INSERT ztravel FROM ... - Framework does this
  • UPDATE ztravel SET ... - Framework does this
  • DELETE 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 Travel
lock master
authorization 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 read
  • INSERT ztravel FROM ... - You must write
  • UPDATE ztravel SET ... - You must update
  • DELETE FROM ztravel - You must delete
  • Lock handling - You must lock/unlock
  • Number assignment - You must call number ranges

Comparison: Managed vs. Unmanaged

AspectManagedUnmanaged
CRUD implementationAutomatic by frameworkImplement manually
DB accessFrameworkYou (SELECT, INSERT, etc.)
Number assignmentfield ( numbering : managed )Manual via cl_numberrange_runtime
Lock handlingAutomaticENQUEUE/DEQUEUE manually
Draft supportYes (out-of-the-box)No
Development effortLow (only business logic)High (everything yourself)
FlexibilityLimited (framework rules)Maximum (full control)
Legacy integrationDifficultEasy (BAPIs etc.)
Performance tuningLimitedFull control
Best forNew cloud appsLegacy migration
Learning curveFlat (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 Travel
persistent 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(
recipient = '[email protected]'
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_modified for 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:

  1. 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 Travel
persistent table ztravel " <- Add
// draft table zdraft_travel <- Optional: Activate draft
lock master
{
create;
update;
delete;
field ( numbering : managed ) TravelId; " <- Instead of manual number assignment
// ...
}
  1. Clean up behavior implementation:
" Before (Unmanaged): All methods
CLASS 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 logic
CLASS lhc_travel DEFINITION ...
METHODS:
" create, update, delete, read, lock -> DELETE!
" Only business logic:
validateDates FOR VALIDATE ...,
setStatusNew FOR DETERMINE ...,
acceptTravel FOR MODIFY ...
  1. Remove DB access:
" Before (Unmanaged):
METHOD create.
INSERT ztravel FROM @( CORRESPONDING #( entities ) ).
" ...
ENDMETHOD.
" After (Managed): Delete method completely!
" Framework does INSERT automatically
  1. 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 sequence

Important 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 save for 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 WORK and ROLLBACK
  • 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