RAP Basics
Architect / Cross-trackintermediate

RAP Build-Along Part 4: Behavior Implementation — Validations, Determinations and Actions

Key takeaway

Part 4 of the RAP Bootcamp implements the business rules declared in Part 3: initial status and total-price determinations, date and child validations, Accept/Reject actions, FAILED/REPORTED with %tky, %msg and %element, instance feature control and authorization — all with bulk-safe EML IN LOCAL MODE and no COMMIT WORK.

Explanation

📌 KEY POINTS
✅ Validations check and report; determinations set values; actions are explicit steps.

✅ Handler methods are set-based: READ once, MODIFY once, fill FAILED/REPORTED.

✅ Use IN LOCAL MODE for internal changes of your own BO.

⚠️ Never COMMIT WORK in behavior code — RAP owns the LUW.

▶ SAP RAP BEHAVIOR IMPLEMENTATION: VALIDATIONS, DETERMINATIONS AND ACTIONS (PART 4)

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

The complete behavior pool ZBP_I_RAP_TRAVEL with local handler classes lhc_Travel and lhc_Booking:

| Method | Kind | Rule |
|---|---|---|

| setInitialStatus | Determination on modify | New Travel gets status O (open) |

| calculateTravelID | Determination on save | Assign readable TravelID |

| recalcTotalPrice / calcTotalOnBooking | Determinations | TotalPrice = BookingFee + sum of FlightPrice |

| validateDates | Validation | Begin ≤ End, Begin not in the past |

| validateCustomer | Validation | Customer filled |

| validateFlightPrice | Child validation | Price > 0, currency set |

| acceptTravel / rejectTravel | Actions | Change status, return result |

| get_instance_features | Feature control | Disable actions/fields by status |

| get_instance_authorizations / get_global_authorizations | Authorization | Check authorization object |

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

  • [Part 3](/lessons/rap-bootcamp-part-3-managed-behavior-draft): active BDEF with declared determinations, validations, actions, features : instance, authorization master ( instance, global ), and generated behavior pool stubs.

━━━━━━━━━━━━━━━━━━━━
▶ HOW THE HANDLER FRAMEWORK CALLS YOU

The RAP runtime calls your methods with tables of keys, never single rows. One OData $batch or one EML statement can touch 1,000 Travels; your code must read them in one statement, process in memory, and write back in one statement.

Three rules you will see in every method:

  1. IN LOCAL MODE — EML inside the BO's own handlers bypasses feature control and authorization checks (the BO is changing itself, not a consumer).
  2. %tky (transactional key) — the key plus %is_draft. Always use %tky, not %key, in a draft BO; otherwise you mix draft and active instances.
  3. No COMMIT WORK, no ROLLBACK WORK, no direct UPDATE on BO tables. The RAP runtime owns the transaction. Handlers run in the *interaction phase*; saving happens later in the *save sequence*. Committing inside a handler breaks draft, locking and the all-or-nothing save.

━━━━━━━━━━━━━━━━━━━━
▶ RESPONSE PARAMETERS: FAILED, REPORTED, MAPPED, RESULT

| Parameter | Meaning |
|---|---|

| failed | Which instances failed (%tky, optional %fail-cause) |

| reported | Messages for the consumer: %tky, %msg, %element, %state_area |

| mapped | Key mapping for created instances (%cid → key) |

| result | Action result, feature or authorization result per instance |

%msg is created with new_message_with_text( ) (quick prototype) or, better, with a message class via new_message( id = ... number = ... severity = ... ). %element-FieldName = if_abap_behv=>mk-on highlights the field in Fiori. %state_area lets you clear old messages from a validation before re-running it — without it, users see stale errors after fixing input.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 1 — DETERMINATION: INITIAL STATUS

Determination on modify { create; } runs right after create, in the same interaction. It reads the new instances, keeps only those without status, and sets OverallStatus = 'O'. Determinations must be idempotent: running twice produces the same result.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 2 — DETERMINATION: TRAVELID ON SAVE

on save { create; } runs once in the save sequence, just before data is written. It reads the maximum existing TravelID and numbers new Travels. This is acceptable for a bootcamp. For gap-free or high-concurrency numbering use a number range object in late numbering; SELECT MAX can collide under parallel saves.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 3 — DETERMINATIONS: TOTAL PRICE

recalcTotalPrice (root, triggered by BookingFee/CurrencyCode) and calcTotalOnBooking (child, triggered by FlightPrice/CurrencyCode/create) both call the internal action reCalcTotalPrice. The internal action reads Travels with their Bookings by association in one READ, sums prices in matching currency, and updates TotalPrice. Using one internal action avoids duplicate logic. Currency conversion is out of scope here; in real projects use released currency conversion APIs for your release rather than ignoring mismatched currencies.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 4 — VALIDATION: DATES

Validations only report; they never modify. For each Travel:

  • Clear previous messages for state area VALIDATE_DATES.
  • If EndDate < BeginDate → append to failed-travel and to reported-travel with %element-BeginDate and %element-EndDate on.
  • If BeginDate < today → same.

In draft, a failing validation does not block saving the draft; it blocks Activate (because Prepare runs it). On an active-only create via EML, it causes COMMIT ENTITIES to fail.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 5 — CHILD VALIDATION

validateFlightPrice on Booking reports on the Booking entity and also passes %path-travel so Fiori can place the message under the right parent. It is listed in the root's Prepare as Booking~validateFlightPrice.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 6 — ACTIONS: ACCEPT AND REJECT

acceptTravel modifies OverallStatus = 'A' for all keys, re-reads the instances, and fills result with %param. Returning $self lets Fiori refresh the object page without another request. Actions are consumer-facing: they respect feature control and authorization, so the handler doesn't re-check status — get_instance_features already disabled it.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 7 — INSTANCE FEATURE CONTROL

get_instance_features returns, per %tky:

  • %action-acceptTravel = disabled if already accepted.
  • %action-rejectTravel = disabled if already rejected.
  • %field-BookingFee = read-only if accepted.
  • %assoc-_Booking = disabled if rejected.

This drives button enablement in Fiori *and* is enforced by the runtime for any consumer.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 8 — AUTHORIZATION

get_global_authorizations answers "may this user create at all?" get_instance_authorizations answers "may this user update/delete/act on *this* Travel?" Use AUTHORITY-CHECK OBJECT against your own authorization object (for example ZRAP_TRVL with field ACTVT) — in ABAP Cloud create it as an authorization object in the package and assign it via IAM app/business catalog. Return if_abap_behv=>auth-unauthorized per instance and add a message.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 9 — DEBUGGING YOUR HANDLERS

  1. Set a breakpoint in the handler method (ADT: double-click the ruler).
  2. Trigger via the smoke-test classrun (F9) or later via Fiori preview.
  3. Inspect keys — how many instances arrived? Are they draft (%is_draft = 01)?
  4. Step over the READ ENTITIES and check the result is non-empty; empty usually means you used %key instead of %tky.
  5. After the method, inspect failed/reported.
  6. For save-sequence problems, breakpoints in on save determinations and validations fire at COMMIT ENTITIES/Activate, not at modify.

━━━━━━━━━━━━━━━━━━━━
▶ STEP 10 — NEGATIVE TESTS TO RUN NOW

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

| Create Travel with End < Begin, commit | commit_failed-travel has 1 row, message on dates |

| Booking with price 0 | failed on Booking with path to Travel |

| Accept an accepted Travel | action disabled by features, failed |

| User without ZRAP_TRVL ACTVT 02 updates | unauthorized, failed |

| 200 Travels created in one EML | each handler called once with 200 keys |

━━━━━━━━━━━━━━━━━━━━
▶ CHECKPOINT

  • [ ] All handler methods implemented and active; ATC shows no "COMMIT in behavior implementation".
  • [ ] New Travel gets status O and a TravelID on save.
  • [ ] TotalPrice updates when fee or booking price changes.
  • [ ] Invalid dates produce highlighted field messages.
  • [ ] Accept/Reject change status and toggle availability.
  • [ ] Unauthorized users are blocked on update even if they can read.

━━━━━━━━━━━━━━━━━━━━
▶ TRY IT YOURSELF

Implement cancelBooking from Part 3's exercise: set BookingStatus = 'X', disable it in get_instance_features for cancelled bookings, and trigger reCalcTotalPrice so cancelled bookings are excluded from the total.

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

"Handlers get key tables, so I read with one READ ENTITIES ... IN LOCAL MODE, work on %tky, and write back with one MODIFY ENTITIES. Determinations are idempotent and change data; validations only fill failed and reported with %msg, %element and a state area. Actions return $self. Feature control and authorization are separate methods the runtime enforces for every consumer. I never commit inside a handler — the runtime saves in the save sequence."

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

[Part 5: Projection, Metadata Extensions and Fiori Elements UI](/lessons/rap-bootcamp-part-5-projection-fiori-elements)

Code example

ABAP Code
" ============================================================" Behavior pool ZBP_I_RAP_TRAVEL - local types (Local Types tab)" ============================================================CLASS lhc_Travel DEFINITION INHERITING FROM cl_abap_behavior_handler.  PRIVATE SECTION.    CONSTANTS:      BEGIN OF status,        open     TYPE c LENGTH 1 VALUE 'O',        accepted TYPE c LENGTH 1 VALUE 'A',        rejected TYPE c LENGTH 1 VALUE 'X',      END OF status.     METHODS get_instance_features FOR INSTANCE FEATURES      IMPORTING keys REQUEST requested_features FOR Travel RESULT result.    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION      IMPORTING keys REQUEST requested_authorizations FOR Travel RESULT result.    METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION      IMPORTING REQUEST requested_authorizations FOR Travel RESULT result.     METHODS setInitialStatus FOR DETERMINE ON MODIFY      IMPORTING keys FOR Travel~setInitialStatus.    METHODS calculateTravelID FOR DETERMINE ON SAVE      IMPORTING keys FOR Travel~calculateTravelID.    METHODS recalcTotalPrice FOR DETERMINE ON MODIFY      IMPORTING keys FOR Travel~recalcTotalPrice.    METHODS reCalcTotalPriceInternal FOR MODIFY      IMPORTING keys FOR ACTION Travel~reCalcTotalPrice.     METHODS validateDates FOR VALIDATE ON SAVE      IMPORTING keys FOR Travel~validateDates.    METHODS validateCustomer FOR VALIDATE ON SAVE      IMPORTING keys FOR Travel~validateCustomer.     METHODS acceptTravel FOR MODIFY      IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.    METHODS rejectTravel FOR MODIFY      IMPORTING keys FOR ACTION Travel~rejectTravel RESULT result.ENDCLASS. CLASS lhc_Travel IMPLEMENTATION.   METHOD setInitialStatus.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( OverallStatus ) WITH CORRESPONDING #( keys )      RESULT DATA(travels).     DELETE travels WHERE OverallStatus IS NOT INITIAL.   " idempotent    CHECK travels IS NOT INITIAL.     MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel UPDATE FIELDS ( OverallStatus )      WITH VALUE #( FOR t IN travels ( %tky = t-%tky OverallStatus = status-open ) ).  ENDMETHOD.   METHOD calculateTravelID.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( TravelID ) WITH CORRESPONDING #( keys )      RESULT DATA(travels).    DELETE travels WHERE TravelID IS NOT INITIAL.    CHECK travels IS NOT INITIAL.     " Bootcamp simplification. Production: number range in late numbering.    SELECT SINGLE FROM zrap_travel FIELDS MAX( travel_id ) INTO @DATA(max_id).     MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel UPDATE FIELDS ( TravelID )      WITH VALUE #( FOR t IN travels INDEX INTO i                    ( %tky = t-%tky TravelID = max_id + i ) ).  ENDMETHOD.   METHOD recalcTotalPrice.    MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel EXECUTE reCalcTotalPrice      FROM CORRESPONDING #( keys ).  ENDMETHOD.   METHOD reCalcTotalPriceInternal.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( BookingFee CurrencyCode ) WITH CORRESPONDING #( keys )      RESULT DATA(travels)      ENTITY Travel BY \_Booking FIELDS ( FlightPrice CurrencyCode BookingStatus )      WITH CORRESPONDING #( keys )      LINK DATA(links)      RESULT DATA(bookings).     LOOP AT travels ASSIGNING FIELD-SYMBOL(<t>).      <t>-TotalPrice = <t>-BookingFee.      LOOP AT links INTO DATA(link) USING KEY id WHERE source-%tky = <t>-%tky.        DATA(b) = bookings[ KEY id %tky = link-target-%tky ].        IF b-CurrencyCode = <t>-CurrencyCode AND b-BookingStatus <> 'X'.          <t>-TotalPrice += b-FlightPrice.        ENDIF.      ENDLOOP.    ENDLOOP.     MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel UPDATE FIELDS ( TotalPrice )      WITH CORRESPONDING #( travels ).  ENDMETHOD.   METHOD validateDates.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( BeginDate EndDate ) WITH CORRESPONDING #( keys )      RESULT DATA(travels).     DATA(today) = cl_abap_context_info=>get_system_date( ).     LOOP AT travels INTO DATA(t).      " clear previous messages of this validation (state area)      APPEND VALUE #( %tky = t-%tky %state_area = 'VALIDATE_DATES' ) TO reported-travel.       IF t-EndDate < t-BeginDate.        APPEND VALUE #( %tky = t-%tky ) TO failed-travel.        APPEND VALUE #( %tky        = t-%tky                        %state_area = 'VALIDATE_DATES'                        %msg        = new_message_with_text(                                        severity = if_abap_behv_message=>severity-error                                        text     = 'End date must not be before begin date' )                        %element-BeginDate = if_abap_behv=>mk-on                        %element-EndDate   = if_abap_behv=>mk-on ) TO reported-travel.      ELSEIF t-BeginDate < today.        APPEND VALUE #( %tky = t-%tky ) TO failed-travel.        APPEND VALUE #( %tky        = t-%tky                        %state_area = 'VALIDATE_DATES'                        %msg        = new_message_with_text(                                        severity = if_abap_behv_message=>severity-error                                        text     = 'Begin date must be today or later' )                        %element-BeginDate = if_abap_behv=>mk-on ) TO reported-travel.      ENDIF.    ENDLOOP.  ENDMETHOD.   METHOD validateCustomer.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( CustomerID ) WITH CORRESPONDING #( keys )      RESULT DATA(travels).    LOOP AT travels INTO DATA(t).      APPEND VALUE #( %tky = t-%tky %state_area = 'VALIDATE_CUSTOMER' ) TO reported-travel.      IF t-CustomerID IS INITIAL.        APPEND VALUE #( %tky = t-%tky ) TO failed-travel.        APPEND VALUE #( %tky = t-%tky %state_area = 'VALIDATE_CUSTOMER'                        %msg = new_message_with_text(                                 severity = if_abap_behv_message=>severity-error                                 text     = 'Customer is required' )                        %element-CustomerID = if_abap_behv=>mk-on ) TO reported-travel.      ENDIF.    ENDLOOP.  ENDMETHOD.   METHOD acceptTravel.    MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel UPDATE FIELDS ( OverallStatus )      WITH VALUE #( FOR k IN keys ( %tky = k-%tky OverallStatus = status-accepted ) ).     READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys )      RESULT DATA(travels).    result = VALUE #( FOR t IN travels ( %tky = t-%tky %param = t ) ).  ENDMETHOD.   METHOD rejectTravel.    MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel UPDATE FIELDS ( OverallStatus )      WITH VALUE #( FOR k IN keys ( %tky = k-%tky OverallStatus = status-rejected ) ).     READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys )      RESULT DATA(travels).    result = VALUE #( FOR t IN travels ( %tky = t-%tky %param = t ) ).  ENDMETHOD.   METHOD get_instance_features.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel FIELDS ( OverallStatus ) WITH CORRESPONDING #( keys )      RESULT DATA(travels) FAILED failed.     result = VALUE #( FOR t IN travels      ( %tky = t-%tky        %action-acceptTravel = COND #( WHEN t-OverallStatus = status-accepted                                       THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled )        %action-rejectTravel = COND #( WHEN t-OverallStatus = status-rejected                                       THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled )        %field-BookingFee    = COND #( WHEN t-OverallStatus = status-accepted                                       THEN if_abap_behv=>fc-f-read_only ELSE if_abap_behv=>fc-f-unrestricted )        %assoc-_Booking      = COND #( WHEN t-OverallStatus = status-rejected                                       THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled ) ) ).  ENDMETHOD.   METHOD get_instance_authorizations.    " ZRAP_TRVL = own authorization object in the package, field ACTVT    AUTHORITY-CHECK OBJECT 'ZRAP_TRVL' ID 'ACTVT' FIELD '02'.    DATA(update_ok) = xsdbool( sy-subrc = 0 ).    AUTHORITY-CHECK OBJECT 'ZRAP_TRVL' ID 'ACTVT' FIELD '06'.    DATA(delete_ok) = xsdbool( sy-subrc = 0 ).     LOOP AT keys INTO DATA(k).      APPEND VALUE #( %tky = k-%tky                      %update              = COND #( WHEN update_ok = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized )                      %action-acceptTravel = COND #( WHEN update_ok = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized )                      %action-rejectTravel = COND #( WHEN update_ok = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized )                      %action-Edit         = COND #( WHEN update_ok = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized )                      %delete              = COND #( WHEN delete_ok = abap_true THEN if_abap_behv=>auth-allowed ELSE if_abap_behv=>auth-unauthorized ) )        TO result.    ENDLOOP.  ENDMETHOD.   METHOD get_global_authorizations.    IF requested_authorizations-%create = if_abap_behv=>mk-on.      AUTHORITY-CHECK OBJECT 'ZRAP_TRVL' ID 'ACTVT' FIELD '01'.      result-%create = COND #( WHEN sy-subrc = 0 THEN if_abap_behv=>auth-allowed                                                 ELSE if_abap_behv=>auth-unauthorized ).    ENDIF.  ENDMETHOD.ENDCLASS. CLASS lhc_Booking DEFINITION INHERITING FROM cl_abap_behavior_handler.  PRIVATE SECTION.    METHODS setBookingDefaults FOR DETERMINE ON MODIFY      IMPORTING keys FOR Booking~setBookingDefaults.    METHODS calcTotalOnBooking FOR DETERMINE ON MODIFY      IMPORTING keys FOR Booking~calcTotalOnBooking.    METHODS validateFlightPrice FOR VALIDATE ON SAVE      IMPORTING keys FOR Booking~validateFlightPrice.ENDCLASS. CLASS lhc_Booking IMPLEMENTATION.  METHOD setBookingDefaults.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Booking FIELDS ( BookingStatus BookingDate ) WITH CORRESPONDING #( keys )      RESULT DATA(bookings).    DELETE bookings WHERE BookingStatus IS NOT INITIAL.    CHECK bookings IS NOT INITIAL.    MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Booking UPDATE FIELDS ( BookingStatus BookingDate )      WITH VALUE #( FOR b IN bookings ( %tky = b-%tky BookingStatus = 'N'                                        BookingDate = cl_abap_context_info=>get_system_date( ) ) ).  ENDMETHOD.   METHOD calcTotalOnBooking.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Booking BY \_Travel FIELDS ( TravelUUID ) WITH CORRESPONDING #( keys )      RESULT DATA(travels).    MODIFY ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Travel EXECUTE reCalcTotalPrice      FROM CORRESPONDING #( travels ).  ENDMETHOD.   METHOD validateFlightPrice.    READ ENTITIES OF zi_rap_travel IN LOCAL MODE      ENTITY Booking FIELDS ( FlightPrice CurrencyCode ParentUUID ) WITH CORRESPONDING #( keys )      RESULT DATA(bookings).    LOOP AT bookings INTO DATA(b).      APPEND VALUE #( %tky = b-%tky %state_area = 'VALIDATE_PRICE' ) TO reported-booking.      IF b-FlightPrice <= 0 OR b-CurrencyCode IS INITIAL.        APPEND VALUE #( %tky = b-%tky ) TO failed-booking.        APPEND VALUE #( %tky        = b-%tky                        %state_area = 'VALIDATE_PRICE'                        %path       = VALUE #( travel-%is_draft   = b-%is_draft                                               travel-TravelUUID  = b-ParentUUID )                        %msg        = new_message_with_text(                                        severity = if_abap_behv_message=>severity-error                                        text     = 'Flight price must be positive with a currency' )                        %element-FlightPrice = if_abap_behv=>mk-on ) TO reported-booking.      ENDIF.    ENDLOOP.  ENDMETHOD.ENDCLASS.

Real project scenario

A mass-upload job created 5,000 bookings through EML and ran for 40 minutes. The determination looped over keys and executed one READ ENTITIES and one MODIFY ENTITIES per key, and it also called COMMIT WORK "to be safe", which caused partially saved drafts. Rewriting it to one bulk read-by-association, in-memory calculation, one MODIFY ... IN LOCAL MODE, and removing the commit brought runtime under a minute and restored all-or-nothing saves.

Common mistakes

  • Looping over keys with one EML call per key instead of bulk reads/modifies.
  • Using %key instead of %tky in a draft BO — reads return nothing or the wrong instance.
  • Modifying data in a validation; validations only report.
  • Forgetting %state_area so old error messages stay after the user fixes input.
  • COMMIT WORK, ROLLBACK WORK or direct UPDATE zrap_travel inside handlers.
  • Non-idempotent determinations that overwrite user input on every run.
  • Re-checking status inside an action instead of relying on instance feature control.
  • Assuming DCL protects modifications; implement instance authorization.

Best practices

  • Always bulk: one read, in-memory processing, one modify per handler.
  • Use %tky everywhere in draft BOs.
  • Keep determinations idempotent; validations side-effect-free.
  • Use message classes and state areas in production.
  • Centralize shared calculations in an internal action.
  • Put status-based enablement in get_instance_features, not in action code.
  • Use your own authorization object; combine with DCL for reads.
  • Never commit in behavior implementation; let ATC confirm it.

Interview angle

Classic questions: "Determination vs validation?" "What is %tky and why not %key?" "Why IN LOCAL MODE?" "How do you return messages bound to a field?" "Where does commit happen in RAP?" — interaction phase in handlers, save sequence (finalize, check_before_save, save) triggered by COMMIT ENTITIES at the outer boundary.

Practice this lesson

Quick check

1. The total price must be recalculated whenever a booking's price changes. What should be used?

2. A validation finds an invalid date but users see only a generic error. What is missing?

3. Where is the ABAP code for RAP validations and actions implemented?

Interview questions

1. A validation does not seem to run. What are the typical reasons?

Answer it in your own words first, then compare.

2. Explain the RAP transactional buffer and save sequence, and what must not happen in the save phase.

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