RAP Basics
Architect / Cross-trackadvanced

RAP Build-Along Part 7: Clean Core, Security, Performance and Transport

Key takeaway

Part 7 of the RAP Bootcamp hardens the Travel + Booking app for production: DCL read access versus RAP modify authorization, released APIs and clean core, ATC, bulk and N+1 performance, draft/lock/concurrency and authorization negative tests, DEV→QAS→PRD transport and service-binding checks, production troubleshooting, a managed vs unmanaged decision table, and a capstone checklist.

Explanation

📌 KEY POINTS
✅ Clean core: released APIs, own tables, ATC cloud checks.

✅ Security: authorization master + DCL roles + service authorization.

✅ Performance: lean CDS, set-based handlers, no to-many expands in lists.

💡 Transport the whole chain together: tables, CDS, BDEF, classes, service, binding, roles.

▶ SAP RAP BEST PRACTICES: CLEAN CORE, SECURITY, PERFORMANCE AND TRANSPORT (PART 7)

━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU WILL BUILD

No new business features. You add what separates a demo from a production app:

  • Access control ZI_RAP_Travel (DCL) for read restriction, alongside the existing RAP modify authorization.
  • An authorization object ZRAP_TRVL with fields ACTVT and ZAGENCY and matching IAM/role assignments.
  • ATC run with a clean result for the package.
  • Performance-safe handlers and CDS.
  • A negative-test suite for drafts, locks, ETags and authorization.
  • A transport and go-live checklist.

━━━━━━━━━━━━━━━━━━━━
▶ PREREQUISITES / PREVIOUS PART

  • [Part 6](/lessons/rap-bootcamp-part-6-odata-v4-eml-testing): service definition ZUI_RAP_TRAVEL, binding ZUI_RAP_TRAVEL_O4, EML e2e test and ABAP Unit.

━━━━━━━━━━━━━━━━━━━━
▶ 1. DCL VERSUS RAP MODIFY AUTHORIZATION

Two independent layers:

| Layer | Artifact | Protects | Enforced when |
|---|---|---|---|

| Read access | DCL (define role) on ZI_RAP_Travel / ZC_RAP_Travel | Which rows a user can SELECT / read via OData | Every CDS read with #CHECK |

| Modify authorization | BDEF authorization master + handler methods | Create, update, delete, actions, draft Edit | Every EML/OData modify not in local mode |

DCL does not replace RAP modification authorization. A user who can read Travel 42 via DCL can only change it if get_instance_authorizations allows it. Conversely, removing a user's read access does not automatically block an EML MODIFY with a known key — the behavior authorization must check too.

The DCL in the code section restricts Travels by agency using the ZAGENCY field of ZRAP_TRVL. Projections inherit via inheriting conditions from entity ZI_RAP_Travel (or use @AccessControl.authorizationCheck: #CHECK with a projection role). The instance authorization in Part 4 is extended to check the same agency, so read and write rules agree.

IN LOCAL MODE skips authorization — correct inside the BO's own handlers, never use it in code called by an external consumer to "work around" a 403.

━━━━━━━━━━━━━━━━━━━━
▶ 2. CLEAN CORE AND RELEASED APIS

  • Keep the package in ABAP Cloud language version where available (BTP, S/4HANA Cloud, and S/4HANA 2022+ developer extensibility). The compiler then rejects unreleased objects.
  • Use only released APIs: cl_abap_context_info for date/user, cl_system_uuid, released CDS views like I_Currency. Check the API State tab in ADT (or the *Released Objects* tree).
  • No modifications, no implicit enhancements, no direct access to SAP tables. Own Z tables are fine.
  • When a needed SAP API is not released, prefer: a released alternative, a documented wrapper in a classic-ABAP tier (3-tier model, on-premise only), or raise an influence request — do not silently switch the package language version.

━━━━━━━━━━━━━━━━━━━━
▶ 3. ATC

Run ATC with the check variant your landscape mandates (for cloud readiness typically ABAP_CLOUD_READINESS or the S/4HANA readiness variant; in BTP the default cloud variant). Fix before transport:

  • Use of unreleased API / non-cloud-compliant statements.
  • COMMIT WORK / ROLLBACK WORK in behavior implementation.
  • Missing authorization checks (strict ( 2 ) already enforces declarations).
  • SELECT in loops, SELECT * without need.
  • Obsolete statements.

Enforce ATC in the transport release process (ATC on release, exemption workflow).

━━━━━━━━━━━━━━━━━━━━
▶ 4. PERFORMANCE: BULK AND N+1

RAP calls handlers with all keys at once. Performance bugs are almost always N+1:

| Anti-pattern | Fix |
|---|---|

| LOOP AT keys → READ ENTITIES per key | One READ ENTITIES ... WITH CORRESPONDING #( keys ) |

| Read Travel, then per Travel read Bookings | One read BY \_Booking with LINK table |

| SELECT SINGLE per booking to master data | One SELECT ... FOR ALL ENTRIES or join in CDS |

| Determination triggered on every field | Restrict triggers to the fields that matter |

| Calculated field doing heavy logic per row | Compute in determination and persist, or use CDS expressions |

Also: add secondary indexes on ZRAP_BOOKING-parent_uuid (the composition join), keep list-report selection fields indexed, and test with realistic volumes (for example 10,000 Travels, 50,000 Bookings). Use ABAP Cross Trace and SAT/ABAP Profiler to find hot spots, and SQL trace (ST05 on-premise) for DB access.

━━━━━━━━━━━━━━━━━━━━
▶ 5. DRAFT, LOCKING AND CONCURRENCY TESTS

| # | Test | Expected |
|---|---|---|

| C1 | User A edits Travel; User B presses Edit | B: "locked by A" |

| C2 | A edits draft; B changes active via EML (no draft) | B blocked by lock |

| C3 | A's draft expires; B edits and saves; A resumes | Total ETag mismatch → A must discard |

| C4 | Two OData PATCH with same stale ETag | Second gets 412 Precondition Failed |

| C5 | Discard draft | Lock released; active unchanged |

| C6 | Activate with invalid Booking | Blocked; message on Booking with path to Travel |

━━━━━━━━━━━━━━━━━━━━
▶ 6. AUTHORIZATION NEGATIVE TESTS

| # | User | Action | Expected |
|---|---|---|---|

| A1 | No ZRAP_TRVL | Open list | Empty list (DCL) |

| A2 | Read-only (ACTVT 03) | Edit | Unauthorized; Edit disabled |

| A3 | Agency 070001 only | Read Travel of 070002 via key | Not found (DCL) |

| A4 | Agency 070001 only | EML MODIFY Travel of 070002 with known key | Unauthorized (instance auth) |

| A5 | No ACTVT 01 | Create | Global auth denies; Create hidden |

A4 is the test that proves DCL is not enough.

━━━━━━━━━━━━━━━━━━━━
▶ 7. DEV → QAS → PRD TRANSPORT

Transport the complete artifact set in dependency order (a single transport request is safest):

  1. Tables ZRAP_TRAVEL, ZRAP_BOOKING, draft tables ZRAP_TRAV_D, ZRAP_BOOK_D
  2. CDS ZI_RAP_Travel, ZI_RAP_Booking, DCL
  3. BDEF ZI_RAP_Travel, behavior pool ZBP_I_RAP_TRAVEL, message class
  4. Authorization object ZRAP_TRVL, IAM app / business catalog (cloud) or PFCG roles (on-premise)
  5. Projections ZC_RAP_Travel, ZC_RAP_Booking, projection BDEF, metadata extensions
  6. Service definition ZUI_RAP_TRAVEL, service binding ZUI_RAP_TRAVEL_O4

Service-binding considerations:

  • BTP / S/4HANA Cloud: the binding is transported; publish/communication or IAM app assignment is done per system (via business roles). Deploy the Fiori app (BAS/ADT) to the target and assign the business catalog.
  • On-premise / private: after import, the published state of V4 bindings may need re-publishing or service group registration in /IWFND/V4_ADMIN per system (release-dependent); check the binding in each system and run the ICF node checks.
  • Don't transport test-data generators into PRD; keep them in a separate local/test package.

Post-import runtime checks in QAS/PRD: open $metadata; run a read-only smoke; confirm role assignment; check no inactive objects; check ST22/Feed Reader for dumps.

━━━━━━━━━━━━━━━━━━━━
▶ 8. PRODUCTION TROUBLESHOOTING

| Symptom | First check |
|---|---|

| App loads, list empty | DCL + role assignment (A1) |

| 403 on save | Instance/global auth, role, ACTVT |

| 412 errors | Concurrent change or stale browser; confirm ETag fields update |

| "Locked by" never clears | Orphaned draft; draft lifetime; SM12 (on-premise) |

| Action button missing after transport | Metadata extension or projection BDEF not transported; $metadata |

| Slow list report | Missing index, heavy calculated field, no $top paging, trace with Cross Trace |

| Dump in save sequence | ST22/Feed Reader; late numbering or unmanaged save code |

━━━━━━━━━━━━━━━━━━━━
▶ 9. MANAGED VS UNMANAGED DECISION TABLE

| Criterion | Managed | Managed with additional/unmanaged save | Unmanaged |
|---|---|---|---|

| New Z tables, greenfield | Best | — | No |

| Must call existing BAPI/legacy API on save | — | Good | Possible |

| Existing mature legacy logic owns buffer and save | No | Partial | Best |

| Draft support effort | Low | Low–medium | High |

| Code volume | Lowest | Medium | Highest |

| Framework locking/ETag | Automatic | Automatic | You implement |

The Travel app is a textbook managed case. Choose unmanaged only when reusing an existing transactional engine is the requirement.

━━━━━━━━━━━━━━━━━━━━
▶ CAPSTONE CHECKLIST

  • [ ] Data model: UUID keys, business ID, admin/ETag fields, currency links.
  • [ ] BDEF: strict, draft, lock/auth master, total+local ETag, field control.
  • [ ] Handlers: bulk-safe, %tky, idempotent determinations, messages with state areas, no commits.
  • [ ] Projection + metadata: only intended operations, complete UI.
  • [ ] Service: V4 UI binding, $metadata verified, EML e2e + ABAP Unit green.
  • [ ] Security: DCL for read, instance/global auth for modify, negative tests A1–A5 pass.
  • [ ] Clean core: ABAP Cloud language version, released APIs, ATC clean.
  • [ ] Performance: tested at volume, indexes, no N+1.
  • [ ] Transport: complete set, post-import checks, roles assigned.

━━━━━━━━━━━━━━━━━━━━
▶ TRY IT YOURSELF — FINAL MINI-PROJECT

Extend the app with a third level: Booking Supplements (ZRAP_BOOKSUPPL, ZI_RAP_BookingSuppl, ZC_RAP_BookingSuppl). Requirements: composition from Booking, lock/auth dependent by _Travel (via _Booking), supplement price included in TotalPrice, validation that supplement price > 0, a child table on the Booking object page, EML test creating Travel → Booking → Supplement in one call, DCL inheritance, and a transport list. Run the capstone checklist on your result.

━━━━━━━━━━━━━━━━━━━━
▶ INTERVIEW TAKEAWAY

"For production I separate read security — DCL — from modify security — RAP instance and global authorization — and prove it with a negative test where a user with a known key tries to modify another agency's data. I keep the package in ABAP Cloud with released APIs and a clean ATC run, write bulk-safe handlers without N+1, test draft locks and ETags with two users, and transport the full artifact chain from tables to service binding with post-import checks. I pick managed RAP for greenfield and unmanaged only to reuse an existing transactional engine."

━━━━━━━━━━━━━━━━━━━━
▶ NEXT PART

You have completed the series. Revisit [Part 1](/lessons/rap-basics-architecture-cds-behavior-service) for the architecture map, then explore the specialized RAP lessons in this topic for deeper dives into individual features.

Code example

ABAP Code
-- ============================================================-- 1) Access control (DCL) for ZI_RAP_Travel - READ restriction only--    Authorization object ZRAP_TRVL: fields ACTVT, ZAGENCY-- ============================================================@EndUserText.label: 'Travel read access by agency'@MappingRole: truedefine role ZI_RAP_TRAVEL {  grant select on ZI_RAP_Travel    where ( AgencyID ) = aspect pfcg_auth( ZRAP_TRVL, ZAGENCY, ACTVT = '03' );} @EndUserText.label: 'Booking read access via parent'@MappingRole: truedefine role ZI_RAP_BOOKING {  grant select on ZI_RAP_Booking    where inheriting conditions from entity ZI_RAP_Travel      replacing { root with _Travel };} @EndUserText.label: 'Travel projection read access'@MappingRole: truedefine role ZC_RAP_TRAVEL {  grant select on ZC_RAP_Travel    where inheriting conditions from entity ZI_RAP_Travel;} @EndUserText.label: 'Booking projection read access'@MappingRole: truedefine role ZC_RAP_BOOKING {  grant select on ZC_RAP_Booking    where inheriting conditions from entity ZI_RAP_Booking;} " ============================================================" 2) Hardened instance authorization (MODIFY) - agency-aware, bulk"    Replaces the Part 4 version in lhc_Travel" ============================================================METHOD get_instance_authorizations.  " one bulk read - no per-key reads (avoid N+1)  READ ENTITIES OF zi_rap_travel IN LOCAL MODE    ENTITY Travel FIELDS ( AgencyID OverallStatus )    WITH CORRESPONDING #( keys )    RESULT DATA(travels)    FAILED failed.   " cache auth results per agency - one AUTHORITY-CHECK per agency/activity  TYPES: BEGIN OF ty_auth, agency TYPE zi_rap_travel-AgencyID,                           upd TYPE abap_bool, del TYPE abap_bool, END OF ty_auth.  DATA auth_cache TYPE HASHED TABLE OF ty_auth WITH UNIQUE KEY agency.   LOOP AT travels INTO DATA(t).    DATA(cached) = VALUE #( auth_cache[ agency = t-AgencyID ] OPTIONAL ).    IF cached IS INITIAL.      cached-agency = t-AgencyID.      AUTHORITY-CHECK OBJECT 'ZRAP_TRVL' ID 'ZAGENCY' FIELD t-AgencyID ID 'ACTVT' FIELD '02'.      cached-upd = xsdbool( sy-subrc = 0 ).      AUTHORITY-CHECK OBJECT 'ZRAP_TRVL' ID 'ZAGENCY' FIELD t-AgencyID ID 'ACTVT' FIELD '06'.      cached-del = xsdbool( sy-subrc = 0 ).      INSERT cached INTO TABLE auth_cache.    ENDIF.     DATA(upd) = COND #( WHEN cached-upd = abap_true THEN if_abap_behv=>auth-allowed                                                    ELSE if_abap_behv=>auth-unauthorized ).    APPEND VALUE #( %tky                 = t-%tky                    %update              = upd                    %action-Edit         = upd                    %action-acceptTravel = upd                    %action-rejectTravel = upd                    %assoc-_Booking      = upd                    %delete              = COND #( WHEN cached-del = abap_true                                                   THEN if_abap_behv=>auth-allowed                                                   ELSE if_abap_behv=>auth-unauthorized ) )      TO result.     IF upd = if_abap_behv=>auth-unauthorized.      APPEND VALUE #( %tky = t-%tky                      %msg = new_message_with_text(                               severity = if_abap_behv_message=>severity-error                               text     = |No change authorization for agency { t-AgencyID }| ) )        TO reported-travel.    ENDIF.  ENDLOOP.ENDMETHOD. " ============================================================" 3) Negative authorization test A4 (run as restricted test user)"    Proves DCL alone does not block modification" ============================================================CLASS zcl_rap_travel_auth_neg DEFINITION PUBLIC FINAL CREATE PUBLIC.  PUBLIC SECTION.    INTERFACES if_oo_adt_classrun.ENDCLASS. CLASS zcl_rap_travel_auth_neg IMPLEMENTATION.  METHOD if_oo_adt_classrun~main.    " Key of a Travel belonging to an agency the user is NOT authorized for    DATA(foreign_uuid) = CONV sysuuid_x16( '00000000000000000000000000000000' ). " replace in test    MODIFY ENTITIES OF zi_rap_travel            " NOT in local mode: consumer view      ENTITY Travel UPDATE FIELDS ( Description )      WITH VALUE #( ( TravelUUID = foreign_uuid Description = 'should not save' ) )      FAILED DATA(failed)      REPORTED DATA(reported).     out->write( COND #( WHEN failed-travel IS NOT INITIAL                        THEN |PASS: blocked, cause { failed-travel[ 1 ]-%fail-cause }|                        ELSE 'FAIL: modification was allowed' ) ).    ROLLBACK ENTITIES.  ENDMETHOD.ENDCLASS. " ============================================================" 4) Performance: bulk read by association instead of N+1" ============================================================" BAD - N+1" LOOP AT keys INTO DATA(k)."   READ ENTITIES OF zi_rap_travel IN LOCAL MODE"     ENTITY Travel BY \_Booking ALL FIELDS WITH VALUE #( ( %tky = k-%tky ) )"     RESULT DATA(b)." ENDLOOP."" GOOD - one round trip, LINK table maps children to parentsREAD ENTITIES OF zi_rap_travel IN LOCAL MODE  ENTITY Travel BY \_Booking FIELDS ( FlightPrice CurrencyCode )  WITH CORRESPONDING #( keys )  LINK DATA(links)  RESULT DATA(bookings). -- ============================================================-- 5) Secondary index for the composition join (table ZRAP_BOOKING)--    Create in ADT: table -> Indexes -> New: ZPA on field PARENT_UUID-- ============================================================ " ============================================================" 6) Transport object list (dependency order)" ============================================================" TABL ZRAP_TRAVEL, ZRAP_BOOKING, ZRAP_TRAV_D, ZRAP_BOOK_D" DDLS ZI_RAP_TRAVEL, ZI_RAP_BOOKING" DCLS ZI_RAP_TRAVEL, ZI_RAP_BOOKING, ZC_RAP_TRAVEL, ZC_RAP_BOOKING" BDEF ZI_RAP_TRAVEL   CLAS ZBP_I_RAP_TRAVEL   MSAG (message class)" SUSO ZRAP_TRVL       (+ IAM app/business catalog or PFCG role)" DDLS ZC_RAP_TRAVEL, ZC_RAP_BOOKING   BDEF ZC_RAP_TRAVEL" DDLX ZC_RAP_TRAVEL, ZC_RAP_BOOKING" SRVD ZUI_RAP_TRAVEL  SRVB ZUI_RAP_TRAVEL_O4" (exclude ZCL_RAP_TRAVEL_DATA_GEN from PRD-bound transports)

Real project scenario

A security review found that an external integration user with no read access to a sales region could still update travels of that region by sending known UUIDs to the OData service. The app relied only on DCL. Adding agency-aware instance authorization in the BO, reusing the same authorization object field as the DCL, and adding negative test A4 to the regression suite closed the gap; the review re-test passed and the test became a go-live gate for every RAP app on the project.

Common mistakes

  • Relying on DCL alone and assuming it blocks modifications.
  • Using IN LOCAL MODE from consumer code to bypass a 403.
  • Switching a package to Standard ABAP to use an unreleased API instead of finding a released alternative.
  • Skipping ATC or exempting findings without review.
  • Testing with 5 records and discovering N+1 in production.
  • Transporting the BO but not the metadata extension, DCL, or authorization object/roles.
  • Forgetting post-import service checks in QAS/PRD.
  • Choosing unmanaged RAP for a greenfield app out of habit.

Best practices

  • Always implement both DCL (read) and behavior authorization (modify), aligned on the same authorization fields.
  • Keep ABAP Cloud language version and released APIs; ATC clean before release.
  • Design handlers for bulk; trace with ABAP Cross Trace and profiler at realistic volume.
  • Automate draft/lock/ETag and authorization negative tests.
  • Transport the complete artifact chain together; verify $metadata and roles after import.
  • Keep test-data generators out of production transports.
  • Default to managed RAP; justify unmanaged explicitly.

Interview angle

Senior-level questions: "How do DCL and RAP authorization differ?" "How would you prove the app is secure?" (negative test A4) "What does clean core mean for RAP development?" "How do you find performance issues in a RAP BO?" "What must be transported and checked after import?" "When would you choose unmanaged RAP?"

Practice this lesson

Quick check

1. A value help shows no entries for one user but works for others. What is the most likely cause?

2. In which phase must all errors be detected so the save phase cannot fail?

3. A user sees "Locked by" herself on a travel. What is the most likely explanation?

Interview questions

1. A RAP-based Fiori app suddenly shows an empty list after a transport. How do you localize the problem?

Answer it in your own words first, then compare.

2. You inherit a RAP application with frequent production incidents. What do you review first?

Answer it in your own words first, then compare.

Practice more with Interview AI →

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.

More lessons in this topic

Interview questions on this topic

Related SAP topics