RAP Business Objects Part 3: Transactional Buffer, Save Sequence and the RAP LUW
Follow one Fiori "Save" click through RAP: the interaction phase and transactional buffer, the save sequence phases, who commits, and the rules that keep a RAP transaction all-or-nothing.
Explanation
📌 KEY POINTS
✅ Interaction phase = buffer only; save sequence = persist.
✅ Validations run in check_before_save when committing.
✅ RAP owns the LUW: no COMMIT WORK in behavior code.
⚠️ Nothing may fail in save — check everything before.
▶ WHERE WE ARE
Parts 1–2 designed the BO and chose the implementation type. This part explains the runtime model that every RAP developer must understand: what happens between a user's change and the database commit.
━━━━━━━━━━━━━━━━━━━━
▶ TWO PHASES
Every RAP transaction has two phases.
- Interaction phase — the consumer sends requests: create, update, delete, actions, reads. RAP runs the operations and your handler methods (determinations on modify, actions, feature control, authorization). All changes are kept in the transactional buffer. Nothing is written to the database.
- Save sequence — triggered when the consumer commits: the user clicks Save (or Activate with draft), an OData change set completes, or ABAP code calls COMMIT ENTITIES. RAP then runs the saver phases and finally commits.
━━━━━━━━━━━━━━━━━━━━
▶ THE TRANSACTIONAL BUFFER
The buffer is the in-memory state of all changed instances in this transaction.
- Managed RAP: the framework owns it.
- Unmanaged RAP: you build it yourself (Unmanaged RAP Part 2).
Everything inside the BO reads through the buffer (READ ENTITIES IN LOCAL MODE), so determinations and validations see uncommitted changes. List queries from the UI, however, read committed data directly via CDS.
━━━━━━━━━━━━━━━━━━━━
▶ THE SAVE SEQUENCE STEP BY STEP
- finalize — on-save determinations run (e.g. draw a number from a number range, calculate final values). Errors can still be reported.
- check_before_save — validations run. If any instance fails, the sequence stops here: nothing is written, the buffer is kept, and the user gets messages to fix the data. (cleanup_finalize runs to undo anything finalize derived.)
- adjust_numbers — only with late numbering: final keys replace preliminary ones.
- save — the point of no return. Data is written: by the framework (managed), or by your code (unmanaged, unmanaged save, additional save).
- Commit — the framework performs the database commit, including update-task function modules registered in save.
- cleanup — buffers are cleared for the next transaction.
━━━━━━━━━━━━━━━━━━━━
▶ THE RAP LUW RULES
Because RAP owns the logical unit of work (LUW), your code must not interfere with it:
- No COMMIT WORK, ROLLBACK WORK or BAPI_TRANSACTION_COMMIT anywhere in behavior implementations.
- No direct INSERT/UPDATE/DELETE on the BO's tables in handlers — in managed RAP the framework writes; in unmanaged RAP only the saver writes.
- No errors in save — everything that can fail must fail in finalize or check_before_save.
- No synchronous calls that commit in another system during save; move them after the commit (bgRFC, background processing framework, business events).
- No dialogs, CALL TRANSACTION or SUBMIT.
Breaking these rules leads to partial saves — header saved, items not — which is exactly what the two-phase model prevents.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT THIS MEANS FOR A FIORI USER
With draft, the interaction phase can last hours: every change goes to draft tables, and "Save" triggers activation plus the save sequence. Without draft, each OData change set is its own short transaction.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT THIS MEANS FOR ABAP CODE
Outside a BO (reports, tests, jobs), you call MODIFY ENTITIES and then COMMIT ENTITIES, and read the FAILED and REPORTED responses of the commit to see validation errors. Inside a BO you never commit.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER PART 3
The runtime model behind every RAP object. Parts 4 and 5 cover the controls that act during these phases: locks and ETags, then authorization, feature control and messages.
Code example
// ===== Timeline of one transaction =====// INTERACTION PHASE (buffer only)// MODIFY create PurchaseRequest -> determination setInitialStatus (on modify)// MODIFY create-by-association Items -> determination calculateTotal (on modify)// EXECUTE action submit -> feature control + authorization checked// COMMIT (user clicks Save / COMMIT ENTITIES)// SAVE SEQUENCE// finalize -> on-save determinations (e.g. setRequestID)// check_before_save -> validations; any FAILED => stop, nothing written// adjust_numbers -> only with late numbering// save -> write (framework or your saver) - must not fail// <framework commit> -> database commit + update task// cleanup -> buffers cleared " ===== ABAP outside the BO: modify, then commit and read the response =====MODIFY ENTITIES OF zr_purchreq ENTITY PurchaseRequest CREATE FIELDS ( CurrencyCode ) " Description missing on purpose WITH VALUE #( ( %cid = 'R1' CurrencyCode = 'EUR' ) ) MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported). " Interaction phase succeeded - validations have NOT run yetCOMMIT ENTITIES RESPONSE OF zr_purchreq FAILED DATA(commit_failed) REPORTED DATA(commit_reported). IF commit_failed IS NOT INITIAL. " validateDescription failed in check_before_save: nothing was written LOOP AT commit_reported-purchaserequest INTO DATA(msg). out->write( msg-%msg->if_message~get_text( ) ). ENDLOOP.ENDIF. " ===== Never inside a behavior implementation =====" COMMIT WORK. <- breaks the RAP LUW" UPDATE zpr_head SET ... . <- bypasses buffer and validations" CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.Real project scenario
A developer added a helper in an approve action that called a legacy function module to create a follow-up document. The function module ended with COMMIT WORK. When a later validation failed for another request in the same mass approval, RAP rolled back — but the follow-up documents for the first requests were already committed, without an approved request behind them.
After tracing the problem, the team moved the follow-up creation into the save phase via an update-task function module without commit, so it only ran when the whole save succeeded. They also added an ATC check that flags COMMIT WORK in behavior pools.
Common mistakes
- Believing that MODIFY ENTITIES writes to the database immediately.
- Expecting validations to run during MODIFY — they run in check_before_save at commit.
- Calling COMMIT WORK or BAPI_TRANSACTION_COMMIT in behavior code.
- Writing to BO tables directly from handlers.
- Performing checks in save instead of check_before_save.
- Ignoring the FAILED/REPORTED response of COMMIT ENTITIES in ABAP callers.
Best practices
- Think in two phases: interaction (buffer) and save sequence (persist).
- Put everything that can fail before save: on-save determinations and validations.
- Read inside the BO with IN LOCAL MODE so uncommitted changes are visible.
- In ABAP callers, always evaluate COMMIT ENTITIES RESPONSE.
- Move remote or slow side effects after the commit.
- Add ATC checks for forbidden statements in behavior pools.
Interview angle
Questions: "Explain the RAP transaction model", "When do validations run?", "Why no COMMIT WORK in RAP?"
Strong answer: interaction phase fills the transactional buffer; commit triggers the save sequence finalize → check_before_save → adjust_numbers → save → commit → cleanup; validations run in check_before_save and stop the save if they fail. RAP owns the LUW, so manual commits or direct writes cause partial saves. Mention COMMIT ENTITIES RESPONSE for ABAP callers and moving remote calls after commit.
Practice this lesson
Quick check
1. When do RAP validations run in relation to MODIFY ENTITIES and COMMIT ENTITIES?
2. An action calls a legacy function that ends with COMMIT WORK. Which problem does this cause?
3. A validation appends an instance to FAILED but adds nothing to REPORTED. What does the user experience?
Interview questions
1. Explain the RAP transaction model: what happens between a user's change and the database commit?
Answer it in your own words first, then compare.
2. An action calls a legacy function module that ends with COMMIT WORK. What can go wrong, and how do you integrate it correctly?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.