Draft Handling in RAP: Temporary Storage for Complex Workflows

Category
RAP
Published
Author
Johannes

Draft Handling enables users to temporarily save changes to Business Objects without immediately writing them to the database. This is particularly important for complex input forms in Fiori applications. With Draft Handling, users can interrupt their work, continue at a later time, and only activate the changes when all inputs are complete and validated.

In this article, you will learn how Draft Handling works in RAP, how to create draft-enabled CDS Views and Behavior Definitions, and which Draft Actions are available for typical workflows.

The Draft Concept: Active vs. Draft Instances

The central concept in Draft Handling is the distinction between active instances and draft instances:

  • Active Instances (Active Entities): The persistent data in the database table. These represent the current, valid state of the Business Object and are visible to all users.

  • Draft Instances (Draft Entities): Temporary working copies stored in a separate draft table. A draft is only visible to the editing user and contains changes that are not yet validated or complete.

The lifecycle of a draft typically looks like this:

  1. Edit: User starts editing → System creates draft copy
  2. Modify: User changes data → Changes are automatically saved in the draft
  3. Prepare: System executes validations → Errors are displayed, but draft remains
  4. Activate: User activates → Draft becomes the active instance, draft entry is deleted

Alternatively, the user can choose Discard at any time to discard the draft without changing the active instance.

When Do I Need Draft?

Draft Handling makes sense for:

  • Complex input forms with many fields
  • Multi-step workflows where users want to save intermediate states
  • Long-running transactions that cannot be completed in a single session
  • Validations that should only be executed upon activation

Draft vs. Non-Draft Scenarios

AspectDraftNon-Draft
StorageSeparate draft tableDirectly in database table
Intermediate savingYes, automaticNo
ValidationAt Prepare/ActivateAt every Save
ComplexityHigherLower
Fiori UXEnhanced (Auto-Save)Standard
Use CaseComplex formsSimple CRUD apps

Non-Draft Scenario (Standard)

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
}

Draft Scenario

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with draft;
define behavior for ZI_Travel alias Travel
persistent table ztravel
draft table zdraft_travel
lock master total etag LastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
}

Exclusive vs. Shared Locks

RAP supports two lock modes for draft scenarios:

Lock TypeDescriptionUse Case
ExclusiveOnly one user can edit the objectStandard for critical data
SharedMultiple users can create draftsCollaborative scenarios

Exclusive Lock (Standard)

With Exclusive Lock, only one user can edit a Business Object at a time:

define behavior for ZI_Travel alias Travel
persistent table ztravel
draft table zdraft_travel
lock master total etag LastChangedAt
authorization master ( instance )
{
// Exclusive Lock is the default
// Only one draft per instance allowed
}

Shared Lock

With Shared Lock, multiple users can create independent drafts:

define behavior for ZI_Travel alias Travel
persistent table ztravel
draft table zdraft_travel
lock master total etag LastChangedAt
authorization master ( instance )
late numbering
{
// Shared Draft: Multiple users can
// edit different versions
with unmanaged save;
}

Creating Draft-Enabled CDS Views

To use Draft Handling, your CDS Views must meet certain requirements. The most important requirement is a Total ETag field that tracks changes to the entire entity.

Interface View with Draft Support

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Travel - Interface View'
define root view entity ZI_Travel
as select from ztravel
association [0..*] to ZI_Booking as _Booking
on $projection.TravelUUID = _Booking.TravelUUID
{
key travel_uuid as TravelUUID,
travel_id as TravelId,
agency_id as AgencyId,
customer_id as CustomerId,
begin_date as BeginDate,
end_date as EndDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_price as TotalPrice,
currency_code as CurrencyCode,
description as Description,
overall_status as OverallStatus,
// Administrative fields
@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,
// Important for Draft: Total ETag for Concurrency Control
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
// Associations
_Booking
}

The field LocalLastChangedAt with the annotation @Semantics.systemDateTime.localInstanceLastChangedAt is used as the Total ETag. It enables the RAP framework to detect conflicts during concurrent access.

Projection View for Draft

The Projection View must expose the draft-relevant fields:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Travel - Projection View'
@Metadata.allowExtensions: true
define root view entity ZC_Travel
provider contract transactional_query
as projection on ZI_Travel
{
key TravelUUID,
TravelId,
AgencyId,
CustomerId,
BeginDate,
EndDate,
TotalPrice,
CurrencyCode,
Description,
OverallStatus,
CreatedBy,
CreatedAt,
LastChangedBy,
LastChangedAt,
LocalLastChangedAt,
_Booking : redirected to composition child ZC_Booking
}

Draft Table Definition

For each entity with draft support, you need a separate draft table. This table stores the temporary working copies and must have the same structure as the main table, supplemented with draft-specific administrative fields:

@EndUserText.label : 'Travel Draft'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
define table zdraft_travel {
key client : abap.clnt not null;
key travel_uuid : sysuuid_x16 not null; // Draft UUID
travel_id : abap.numc(8);
agency_id : abap.numc(6);
customer_id : abap.numc(6);
begin_date : abap.dats;
end_date : abap.dats;
total_price : abap.curr(16,2);
currency_code : abap.cuky;
description : abap.string(256);
status : abap.char(1);
// Draft-specific fields (automatically used by the framework)
"%admin" : include sych_bdl_draft_admin_inc;
}

Draft-Admin Include

The include sych_bdl_draft_admin_inc contains important administrative fields:

FieldDescription
draftentityuuidUnique draft ID
draftentitycreationdatetimeCreation timestamp
draftentitylastchangedatetimeLast modification
draftentitycreatedbyCreated by
draftentitylastchangedbyChanged by
draftentityownershipstateDraft ownership
hasactiveentityHas active version
isactiveentityIs active version

Draft Actions

RAP automatically provides Draft Actions:

Edit Action

Starts edit mode and creates a draft:

" In the Behavior Definition
draft action Edit;
" EML call
MODIFY ENTITIES OF zi_travel
ENTITY Travel
EXECUTE Edit FROM VALUE #( ( TravelId = '00000001' ) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).

Activate Action

Activates the draft and writes the changes to the database:

" In the Behavior Definition
draft action Activate optimized;
" EML call
MODIFY ENTITIES OF zi_travel
ENTITY Travel
EXECUTE Activate FROM VALUE #( ( %key-TravelId = '00000001' ) )
MAPPED mapped
FAILED failed
REPORTED reported.
COMMIT ENTITIES.

The optimized keyword ensures that only changed fields are written.

Discard Action

Discards the draft without saving:

" In the Behavior Definition
draft action Discard;
" EML call
MODIFY ENTITIES OF zi_travel
ENTITY Travel
EXECUTE Discard FROM VALUE #( ( %key-TravelId = '00000001' ) )
FAILED failed
REPORTED reported.

Resume Action

Continues editing an existing draft:

" In the Behavior Definition
draft action Resume;
" EML call
MODIFY ENTITIES OF zi_travel
ENTITY Travel
EXECUTE Resume FROM VALUE #( ( %key-TravelId = '00000001' ) )
RESULT DATA(result)
FAILED failed
REPORTED reported.

Prepare Action

Executes validations without activating:

" In the Behavior Definition
draft determine action Prepare {
validation validateDates;
validation validateCustomer;
}
" EML call - checks all draft validations
MODIFY ENTITIES OF zi_travel
ENTITY Travel
EXECUTE Prepare FROM VALUE #( ( %key-TravelId = '00000001' ) )
FAILED failed
REPORTED reported.

Complete Draft Example

1. Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with draft;
define behavior for ZI_Travel alias Travel
persistent table ztravel
draft table zdraft_travel
lock master total etag LastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
// Standard operations
create;
update;
delete;
// Field mapping
field ( readonly ) TravelId;
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
field ( numbering : managed ) TravelId;
// Actions
action ( features : instance ) acceptTravel result [1] $self;
action ( features : instance ) rejectTravel result [1] $self;
// Validations - executed at Prepare/Activate
validation validateDates on save { field BeginDate, EndDate; }
validation validateCustomer on save { field CustomerId; }
// Determinations
determination setStatusNew on modify { create; }
// Draft Actions
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare {
validation validateDates;
validation validateCustomer;
}
}

2. Projection with Draft

projection;
strict ( 2 );
use draft;
define behavior for ZC_Travel alias Travel
{
use create;
use update;
use delete;
use action acceptTravel;
use action rejectTravel;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
}

3. Implementing Draft Validation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates.
" Read draft data (not active data!)
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).
" Draft validation: Warning instead of error possible
IF ls_travel-EndDate < ls_travel-BeginDate.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%state_area = 'VALIDATE_DATES'
%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.
ENDCLASS.

Draft in Fiori Elements

When Draft is activated, Fiori Elements automatically shows:

  • Auto-Save: Changes are automatically saved
  • Draft Indicator: Shows that unsaved changes exist
  • Discard Button: Discards all changes
  • Save Button: Activates the draft (executes validations)

Concurrent Access and Locking in Detail

The RAP framework automatically manages locks to ensure data consistency. With Draft Handling, there are two critical scenarios:

Scenario 1: Two Users Editing the Same Instance

With Exclusive Lock (Standard):

  1. User A starts Edit → Draft is created, lock is set
  2. User B tries Edit → Error message “Object is being edited by User A”
  3. User A activates or discards → Lock is released
  4. User B can now edit

Scenario 2: Draft Timeout

When a user creates a draft and leaves the session:

  • The draft remains in the draft table
  • On renewed access, editing can be resumed with Resume
  • Other users remain blocked (with Exclusive Lock)

To avoid orphaned drafts, you can set up a background job that automatically deletes old drafts:

" Example: Delete drafts older than 7 days
DELETE FROM zdraft_travel
WHERE draftentitylastchangedatetime < @lv_cutoff_timestamp.

Best Practices

  1. Draft only when necessary - For simple CRUD apps without complex forms, Draft is often unnecessary and increases complexity
  2. Validations in Prepare - Enables early feedback without activation, improves user experience
  3. Define Draft Table correctly - Always use the admin include sych_bdl_draft_admin_inc
  4. Optimized Activate - The optimized keyword with Activate improves performance as only changed fields are written
  5. Use State Areas - Group validation messages for better UX with %state_area
  6. Set up Draft Timeout - Configure a background job for automatic deletion of old drafts
  7. Don’t forget Testing - Test all draft scenarios: Edit, Activate, Discard, Resume, and Concurrent Access

Summary

Draft Handling is a powerful feature of the RESTful ABAP Programming Model that significantly improves the user experience for complex data entry scenarios. The key points:

  • Draft vs. Active: Drafts are temporary working copies that only become persistent data upon activation
  • Draft Table: A separate table stores the draft instances with the admin include for administrative fields
  • Draft Actions: Edit, Activate, Discard, Resume, and Prepare enable the complete draft workflow
  • Locking: Exclusive Lock prevents simultaneous editing, Shared Lock enables collaborative scenarios
  • Validations: With Prepare, validations can be executed without activating the draft

With the proper use of Draft Handling, you create professional Fiori applications that provide an optimal user experience even in complex input scenarios.