Unmanaged RAP Part 5: Choosing the Implementation Type and Migrating BOPF/SEGW
Compare the four RAP implementation types — managed, managed with additional save, managed with unmanaged save and unmanaged — choose correctly, and follow a safe step-by-step path for moving BOPF objects and SEGW services to RAP.
Explanation
📌 KEY POINTS
✅ Decision order: managed → additional save → unmanaged save → unmanaged.
✅ Unmanaged save keeps framework buffer, locks, ETags and draft.
✅ BOPF/SEGW migration: redesign as RAP, reuse logic, not structures.
⚠️ Document the chosen implementation type and why.
▶ WHERE WE ARE
You have built both a managed (Managed series) and an unmanaged (Parts 1–4) version of the same Purchase Request. You have seen how much more code unmanaged needs. This final part shows the options in between, a decision guide, and how to migrate existing BOPF and SEGW implementations.
━━━━━━━━━━━━━━━━━━━━
▶ 1. THE FOUR IMPLEMENTATION TYPES
From most framework help to least:
- Managed — the framework does CRUD, buffer, locking and writes your tables. Use for new objects with your own tables.
- Managed with additional save — framework writes your tables as usual, AND you get a save_modified method to do something extra in the same LUW: write a change log, fill an extra table, raise an event. Declared with "persistent table zpr_head with additional save".
- Managed with unmanaged save — the framework still does CRUD, buffer, locking, ETag, draft and numbering, but instead of writing tables it calls your save_modified method with everything that changed. You call the persistence API. Declared with "with unmanaged save" instead of "persistent table".
- Unmanaged — you own the buffer and all CRUD handlers, plus the saver (this series).
━━━━━━━━━━━━━━━━━━━━
▶ 2. THE DECISION GUIDE
Ask these questions in order:
- Is the data in tables you own, with no legacy logic that must run? → Managed.
- Same, but you need extra persistence or side effects at save? → Managed with additional save.
- Must an existing API write the data, but the interaction logic (CRUD, buffer, draft) can be standard? → Managed with unmanaged save. This is often the best fit for brownfield: you get the framework's buffer and %control handling for free and only write the save.
- Must the existing application also control reads, locks, numbering or complex in-memory state during the interaction phase (e.g. the legacy API has its own buffer that must be filled on every change)? → Unmanaged.
Many teams pick full unmanaged when managed with unmanaged save would have been enough. That choice costs hundreds of lines of buffer and CRUD code and the bugs that come with it (see Part 3).\n\n━━━━━━━━━━━━━━━━━━━━\n▶ 3. HOW save_modified WORKS\nIn both managed variants, the saver's save_modified method receives three structures per entity:
- create-<entity> — complete new instances.
- update-<entity> — changed instances with %control flags showing which fields changed. Add "with full data" in the BDEF if you need all fields, not only the changed ones.
- delete-<entity> — keys of deleted instances.
You translate these into calls to your persistence API. The same rules as Part 4 apply: no commits, no dialogs, errors must already be caught (use validations, which the framework still runs before save).
━━━━━━━━━━━━━━━━━━━━
▶ 4. MIGRATING BOPF TO RAP
BOPF concepts map almost one-to-one:
- BOPF node → CDS view entity in a composition tree.
- Determination → determination. Validation → validation. Action → action.
- BOPF locking and ETag → lock master and etag master.
- BOPF draft (legacy draft 1.0) → RAP draft.
- BOPF queries → CDS views.
Path:
- Inventory the BOPF object: nodes, determinations, validations, actions, custom persistence.
- Extract business logic from BOPF classes into plain ABAP classes that do not depend on BOPF interfaces (/BOBF/IF_FRW_*). This is the most valuable step and can be done before any RAP code.
- Model CDS root and child view entities on the existing tables.
- Choose the type: if BOPF wrote tables through standard persistence → managed; if it had custom persistence → managed with unmanaged save or unmanaged.
- Recreate behavior in the BDEF and call the extracted classes from handlers.
- Run both side by side, compare results with automated tests, then switch the consumers.
Recent releases offer tool support in ADT for migrating CDS-based BOPF objects to RAP; check what your release provides before doing it by hand.
━━━━━━━━━━━━━━━━━━━━
▶ 5. MIGRATING SEGW SERVICES TO RAP
SEGW (Gateway Service Builder) projects implement OData services in DPC_EXT classes with methods like *_CREATE_ENTITY and *_GET_ENTITYSET.
- Entity types from SEGW → CDS projection views.
- GET_ENTITYSET logic → CDS views (filters, joins, calculated fields) — most read logic becomes declarative.
- CREATE/UPDATE/DELETE_ENTITY logic → RAP handlers and saver, or save_modified.
- The SEGW service registration → service definition + service binding (see the Service Bindings series).
Path: extract the business logic out of DPC_EXT into reusable classes first, build the RAP service in parallel under a new name, compare responses for the same requests, then move consumers. Keep the old service until every consumer has switched — the OData URLs differ.
━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER THE SERIES
You can decide between the four implementation types with reasons, build an unmanaged BO safely, and plan a migration from BOPF or SEGW. Continue with the Service Bindings series to expose any of these business objects.
Code example
// ===== Option A: managed WITH ADDITIONAL SAVE (framework writes + you add a change log) =====managed implementation in class zbp_r_purchreq unique;strict ( 2 ); define behavior for ZR_PurchReq alias PurchaseRequestpersistent table zpr_head with additional savelock masterauthorization master ( global, instance )etag master LocalLastChangedAt{ create; update; delete; /* ... */ } // ===== Option B: managed WITH UNMANAGED SAVE (framework does interaction, you persist) =====managed implementation in class zbp_r_purchreq_mu unique;strict ( 2 ); define behavior for ZR_PurchReqMU alias PurchaseRequestwith unmanaged save with full data // "with full data": all fields in update, not only changed oneslock masterauthorization master ( global, instance )etag master LocalLastChangedAt{ create; update; delete; field ( readonly, numbering : managed ) RequestUUID; /* determinations, validations, actions as in the Managed series */ mapping for zpr_head { /* same field mapping as Managed Part 1 - used by MAPPING FROM ENTITY */ }} " ===== Saver for Option B: persist through the existing API =====CLASS lsc_zr_purchreq_mu DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS save_modified REDEFINITION.ENDCLASS. CLASS lsc_zr_purchreq_mu IMPLEMENTATION. METHOD save_modified. " Validations already ran - only write here, never commit IF create-purchaserequest IS NOT INITIAL. CALL FUNCTION 'Z_PR_POST_REQUESTS' IN UPDATE TASK EXPORTING it_create = CORRESPONDING zpr_head_tt( create-purchaserequest MAPPING FROM ENTITY ). ENDIF. IF update-purchaserequest IS NOT INITIAL. CALL FUNCTION 'Z_PR_POST_REQUESTS' IN UPDATE TASK EXPORTING it_update = CORRESPONDING zpr_head_tt( update-purchaserequest MAPPING FROM ENTITY ). ENDIF. IF delete-purchaserequest IS NOT INITIAL. CALL FUNCTION 'Z_PR_POST_REQUESTS' IN UPDATE TASK EXPORTING it_delete = CORRESPONDING zpr_head_tt( delete-purchaserequest MAPPING FROM ENTITY ). ENDIF. ENDMETHOD.ENDCLASS." MAPPING FROM ENTITY uses the "mapping for zpr_head" block of the BDEF." zpr_head_tt = a table type of ZPR_HEAD. " ===== BOPF -> RAP mapping cheat sheet =====" BOPF node -> CDS view entity (composition tree)" BOPF determination -> RAP determination (on modify / on save)" BOPF validation -> RAP validation (on save)" BOPF action -> RAP action" BOPF lock / ETag -> lock master / etag master" BOPF draft (1.0) -> RAP draft (with draft)" /BOBF/IF_FRW_* logic -> extract into plain ABAP classes first"" ===== SEGW -> RAP mapping cheat sheet =====" SEGW entity type -> CDS projection view" *_GET_ENTITYSET -> CDS view (filters, joins, calculated fields)" *_CREATE/UPDATE/DELETE_ENTITY -> RAP handlers + saver (or save_modified)" Service registration -> service definition + service bindingReal project scenario
A team migrating a SEGW-based "maintenance notification lite" app chose full unmanaged RAP because "the data is saved through an existing BAPI". Six weeks in, they had 900 lines of buffer, read and CRUD code, and a steady flow of PATCH and ETag bugs.
In a design review they realized the BAPI only mattered at save time — during editing, the data behaved like simple fields. They switched to managed with unmanaged save. The framework took over buffering, %control handling, locking, ETags and draft; the team kept only the validations, the actions and one save_modified method that called the BAPI's update-task posting. The behavior pool shrank to about a third, and the PATCH bugs disappeared because the framework now handled partial updates.
The lesson: "an existing API saves the data" is a reason for unmanaged save, not automatically for full unmanaged.
Common mistakes
- Defaulting to full unmanaged whenever a legacy API is involved, when managed with unmanaged save would do.
- Using managed with additional save to replace the standard write — it is for extra work in addition to it.
- Forgetting "with full data" and then persisting only the changed fields as if they were a complete record.
- Migrating BOPF by rewriting logic inside RAP handlers instead of first extracting it into reusable classes.
- Switching consumers from a SEGW service to the RAP service without running both in parallel — URLs and payloads differ.
- Doing checks or commits in save_modified — the rules of the save phase still apply.
Best practices
- Use the decision guide: managed → additional save → unmanaged save → unmanaged, choosing the first that fits.
- Extract business logic out of framework-specific code (BOPF classes, DPC_EXT) before migrating.
- Keep save_modified thin: translate and call the persistence API only.
- Run old and new services in parallel with automated comparison tests before switching consumers.
- Document the implementation-type decision and its reason — it is the question every future developer will ask.
Interview angle
Architect-level questions: "What implementation types does RAP offer and when do you use each?", "What is the difference between additional save and unmanaged save?", "How would you migrate a BOPF object or a SEGW service to RAP?"
Strong answer: list the four types and the decision questions; explain that additional save adds work after the framework's standard write, while unmanaged save replaces the write but keeps the framework's interaction-phase handling, which is why it often beats full unmanaged in brownfield. For migration, map BOPF and SEGW concepts to RAP, and stress extracting business logic first and running in parallel before switching consumers. Interviewers value hearing that you would avoid unnecessary unmanaged code.
Practice this lesson
Quick check
1. An existing API must persist the data, but editing behavior (buffer, partial updates, locks, draft) can be standard. Which implementation type fits best?
2. What is the most valuable first step when migrating a BOPF business object to RAP?
3. Which situation is the strongest reason to implement a RAP business object as unmanaged?
Interview questions
1. Compare managed, managed with additional save, managed with unmanaged save and unmanaged RAP. How do you decide?
Answer it in your own words first, then compare.
2. You must migrate a SEGW-based OData service and the BOPF object behind it to RAP. What is your plan?
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.