RAP Business Objects Part 2: Managed vs Unmanaged — A Decision Framework
Get the big picture of RAP implementation types — managed, managed with additional save, managed with unmanaged save and unmanaged — and a short decision checklist you can apply in design workshops.
Explanation
📌 KEY POINTS
✅ Four types: managed, + additional save, + unmanaged save, unmanaged.
✅ Ask who must persist the data — that decides the type.
✅ Business logic, services and consumers are the same for all types.
⚠️ Managed on legacy-owned tables bypasses legacy rules.
▶ WHERE WE ARE
Part 1 designed the tree. The next design decision is who implements the transactional work: the RAP framework or your code. This lesson is the overview; Unmanaged RAP Part 5 goes deeper with save_modified code and migration.
WHAT "TRANSACTIONAL WORK" MEANS
For every change, something must: keep changes in a buffer until save, apply partial updates correctly, lock the data, check ETags, assign keys, handle draft, and finally write to the database in one LUW. The implementation type decides how much of this the framework does.
━━━━━━━━━━━━━━━━━━━━
▶ THE FOUR TYPES
- Managed — the framework does everything, including writing your tables ("persistent table" in the BDEF). You implement only business logic: determinations, validations, actions, authorization, feature control. Best for new objects with own tables.
- Managed with additional save — like managed, plus a save_modified method in which you do extra work in the same LUW, e.g. write a change log or raise an event. The framework still writes the tables.
- Managed with unmanaged save — the framework still does buffer, partial updates, locks, ETags, draft and numbering, but does not write tables; it calls your save_modified with all creates, updates and deletes, and you call the persistence API. Ideal when an existing API must save the data.
- Unmanaged — you implement the buffer, CRUD and read methods, lock, numbering and the full saver. Needed when legacy code must control the whole interaction, e.g. it keeps its own in-memory state.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT STAYS THE SAME IN ALL FOUR
- The BDEF contract, EML, projections, services and Fiori Elements.
- Determinations, validations, actions, authorization and feature control.
- RAP owns the LUW: no COMMIT WORK in your code.
Consumers cannot tell which type you chose — which means you can change the type later without changing the service.
━━━━━━━━━━━━━━━━━━━━
▶ THE DECISION CHECKLIST
Ask in this order and stop at the first "yes":
- New object, own tables, no legacy persistence logic? → Managed.
- Same, but extra persistence or side effects needed at save? → Managed with additional save.
- An existing API must write the data, but editing behavior can be standard? → Managed with unmanaged save.
- Legacy logic must control reads, locks or in-memory state during editing? → Unmanaged.
━━━━━━━━━━━━━━━━━━━━
▶ COST OF THE WRONG CHOICE
- Choosing managed for data owned by a legacy API silently bypasses its business rules (budget checks, change documents).
- Choosing full unmanaged when unmanaged save would do costs hundreds of lines of buffer and CRUD code and a typical wave of PATCH and ETag bugs.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP (DESIGN WORKSHOP)
- List who writes the data today (tables directly, a BAPI, a class).
- List rules that run only inside that writer.
- Apply the checklist.
- Record the decision and the reason in the design document — it is the question every future developer will ask.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER PART 2
A fast, defensible decision. Build path: Managed RAP series for types 1 and 2, Unmanaged RAP series for types 3 and 4.
Code example
// ===== 1. Managed =====managed implementation in class zbp_r_purchreq unique;strict ( 2 );define behavior for ZR_PurchReq alias PurchaseRequestpersistent table zpr_head // framework writes this tablelock master authorization master ( global, instance ) etag master LocalLastChangedAt{ create; update; delete; } // ===== 2. Managed with additional save =====define behavior for ZR_PurchReq alias PurchaseRequestpersistent table zpr_head with additional save // framework writes + you add worklock master authorization master ( global, instance ) etag master LocalLastChangedAt{ create; update; delete; }// -> saver method save_modified: e.g. insert change-log rows // ===== 3. Managed with unmanaged save =====define behavior for ZR_PurchReq alias PurchaseRequestwith unmanaged save // framework does NOT write tableslock master authorization master ( global, instance ) etag master LocalLastChangedAt{ create; update; delete; }// -> save_modified receives create/update/delete and calls the existing API // ===== 4. Unmanaged =====unmanaged implementation in class zbp_r_purchreq_u unique;strict ( 2 );define behavior for ZR_PurchReqU alias PurchaseRequestlock master authorization master ( global, instance ) etag master LocalLastChangedAtearly numbering{ create; update; delete; }// -> you implement: buffer, create/update/delete/read, lock,// numbering, finalize/check_before_save/save/cleanup // ===== Who does what =====// Managed +AddSave +UnmgdSave Unmanaged// Buffer / %control RAP RAP RAP you// Locks / ETag check RAP RAP RAP you (lock) / RAP (ETag via read)// Draft handling RAP RAP RAP RAP (draft tables)// Write to database RAP RAP+you you you// Business logic you you you youReal project scenario
In a design workshop for a pricing-maintenance app, the developers proposed full unmanaged RAP "because the existing pricing API must save the data". Applying the checklist showed that the API was only needed at save time; during editing, the data behaved like simple fields and needed draft.
The team chose managed with unmanaged save. The framework handled draft, locks, ETags and partial updates; the team wrote validations, one action and a save_modified method calling the API's posting function. Compared with the original unmanaged estimate, the behavior pool was about one third of the size, and the first test cycle had no PATCH or ETag defects.
Common mistakes
- Deciding by habit ("we always do unmanaged") instead of by who must own persistence.
- Using managed on tables that a legacy API owns, bypassing its rules.
- Using additional save when the standard write must be replaced (that is unmanaged save).
- Believing unmanaged allows COMMIT WORK.
- Not documenting the decision and its reason.
Best practices
- Apply the four-question checklist and stop at the first yes.
- Prefer managed or managed with unmanaged save; use full unmanaged only when needed.
- Keep business logic identical regardless of type (determinations, validations, actions).
- Document the implementation type and reason in the design.
- Remember consumers do not see the type — you can change it later if needed.
Interview angle
Questions: "What implementation types exist in RAP?", "When do you use managed with unmanaged save?", "What stays the same regardless of the type?"
Strong answer: name the four types and what the framework does in each; explain that unmanaged save keeps framework buffer, %control, locks, ETags and draft while you persist via an existing API, which is often the best brownfield choice. Stress that services, projections and business logic are independent of the type and that RAP always owns the LUW.
Practice this lesson
Quick check
1. An existing API must persist the data, but editing, draft and partial updates can be handled by the framework. Which implementation type fits?
2. A mature pricing API contains validations that must always run. A team builds a managed BO directly on its tables. What is the main risk?
3. An action calls a legacy function that ends with COMMIT WORK. Which problem does this cause?
Interview questions
1. Walk through the four RAP implementation types and give one example scenario for each.
Answer it in your own words first, then compare.
2. In a design workshop, how do you decide between managed and unmanaged RAP? Which questions do you ask?
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.