Skip to content

Lesson 13 Companion — RAP Business Rules and Messages ​

This companion supports:

text
Lesson 13 — RAP Validations, Determinations, and Messages

The guided lesson turns the Reservation BO from:

text
managed CRUD

into:

text
managed CRUD
+
business rules
+
consumer-facing messages

1. Core architecture ​

Before Lesson 13:

text
Consumer
   ↓
EML
   ↓
Managed RAP CRUD
   ↓
Persistence

After Lesson 13:

text
Consumer
   ↓
EML
   ↓
RAP BO
   ├─ standard managed operations
   ├─ determinations
   ├─ validations
   └─ messages
   ↓
Persistence

The rules belong to the Business Object provider.


2. Business rule ownership ​

The backend BO should own authoritative rules such as:

text
required business data
valid business dates
acceptable amounts
automatic defaults
allowed state transitions

A UI may also perform convenience checks.

But UI checks are not a substitute for backend enforcement.


3. Determination definition ​

A determination is declared in the Behavior Definition.

Lesson 13 uses:

abap
determination setInitialStatus on modify { create; }

Meaning:

text
name    = setInitialStatus
timing  = on modify
trigger = create

4. Determination timing ​

on modify ​

Runs during the interaction phase when the trigger is met.

This is appropriate for values that should become available while the RAP transaction is still being built.

Examples:

text
default status
derived subtotal
calculated classification
automatically copied data

on save ​

Determinations can also be modeled for save processing.

Lesson 13 does not need that variant.


5. Determinations should be idempotent ​

A determination may execute more than once in a RAP transaction.

Its result should remain stable if the same conditions are processed repeatedly.

The Lesson 13 determination is safe because it only sets:

text
Status = N

when:

text
Status IS INITIAL

Once Status is filled, another execution does not change it again.


6. Validation definition ​

A validation is declared in the BDEF.

Example:

abap
validation validateCustomer on save
  { create; field CustomerName; }

This identifies:

text
implementation = validateCustomer
timing         = on save
triggers       = create or CustomerName change

7. Why validation is on save ​

A validation protects whether the current transactional state is allowed to reach persistence.

That is why the lesson tests it through:

text
COMMIT ENTITIES

Flow:

text
MODIFY ENTITIES
      ↓
transactional buffer
      ↓
COMMIT ENTITIES
      ↓
validation
      ↓
save or reject

8. Trigger fields ​

This declaration:

abap
validation validateFee on save
  { create; field Fee, CurrencyCode; }

does not mean the method receives only those fields.

It means those operations/field changes are relevant triggers.

The implementation still reads the fields it needs from BO state.


9. Complete Lesson 13 BDEF ​

abap
managed implementation in class zbp_r_resv_### unique;
strict ( 2 );

define behavior for ZR_Reservation_### alias Reservation
persistent table zresv_###
lock master
authorization master ( none )
etag master LocalLastChangedAt
{
  create;
  update;
  delete;

  field ( numbering : managed, readonly ) Uuid;

  field ( readonly )
    LocalCreatedBy,
    LocalCreatedAt,
    LocalLastChangedBy,
    LocalLastChangedAt,
    LastChangedAt;

  determination setInitialStatus on modify { create; }

  validation validateCustomer on save
    { create; field CustomerName; }

  validation validateDate on save
    { create; field ReservationDate; }

  validation validateFee on save
    { create; field Fee, CurrencyCode; }

  mapping for zresv_###
  {
    Uuid               = uuid;
    ReservationNo      = reservation_no;
    CustomerName       = customer_name;
    CustomerEmail      = customer_email;
    WorkshopCode       = workshop_code;
    ReservationDate    = reservation_date;
    Status             = status;
    Fee                = fee;
    CurrencyCode       = currency_code;
    Notes              = notes;
    LocalCreatedBy     = local_created_by;
    LocalCreatedAt     = local_created_at;
    LocalLastChangedBy = local_last_changed_by;
    LocalLastChangedAt = local_last_changed_at;
    LastChangedAt      = last_changed_at;
  }
}

10. Handler class reference ​

The Behavior Pool local implementation class should contain signatures equivalent to:

abap
CLASS lhc_Reservation DEFINITION
  INHERITING FROM cl_abap_behavior_handler.

  PRIVATE SECTION.

    METHODS setInitialStatus
      FOR DETERMINE ON MODIFY
      IMPORTING keys FOR Reservation~setInitialStatus.

    METHODS validateCustomer
      FOR VALIDATE ON SAVE
      IMPORTING keys FOR Reservation~validateCustomer.

    METHODS validateDate
      FOR VALIDATE ON SAVE
      IMPORTING keys FOR Reservation~validateDate.

    METHODS validateFee
      FOR VALIDATE ON SAVE
      IMPORTING keys FOR Reservation~validateFee.

ENDCLASS.

Prefer ADT-generated method declarations.

The exact generated local class name may vary.


11. Complete determination reference ​

abap
METHOD setInitialStatus.

  READ ENTITIES OF ZR_Reservation_### IN LOCAL MODE
    ENTITY Reservation
      FIELDS ( Status )
      WITH CORRESPONDING #( keys )
    RESULT DATA(reservations).

  DATA reservations_to_update
    TYPE TABLE FOR UPDATE ZR_Reservation_###.

  LOOP AT reservations INTO DATA(reservation)
    WHERE Status IS INITIAL.

    APPEND VALUE #(
      %tky   = reservation-%tky
      Status = 'N'
    ) TO reservations_to_update.

  ENDLOOP.

  IF reservations_to_update IS NOT INITIAL.

    MODIFY ENTITIES OF ZR_Reservation_### IN LOCAL MODE
      ENTITY Reservation
        UPDATE FIELDS ( Status )
        WITH reservations_to_update.

  ENDIF.

ENDMETHOD.

12. Why TYPE TABLE FOR UPDATE? ​

RAP exposes BDEF-derived types for BO operations.

This declaration:

abap
TYPE TABLE FOR UPDATE ZR_Reservation_###

creates an internal table shaped for an update request against that BO.

It is preferable to inventing an unrelated local structure.


13. Why %tky is used ​

Inside a RAP transaction:

text
%tky

represents the transactional key for an instance.

It is the correct identity to carry from the read result into provider-side update and response structures.

For today's active single-node UUID BO, it ultimately corresponds to the instance identity, but %tky is the durable RAP concept.


14. IN LOCAL MODE ​

Provider implementation uses:

abap
IN LOCAL MODE

to access the BO from inside its own implementation.

This is available only for access to the pool's own BO. It bypasses access, authorization, feature-control, and precheck restrictions where applicable. It does not switch off determinations or validations, so trigger design must still avoid repeated changes. See SAP's EML scenarios and local-mode effects.

Do not treat local mode as:

text
faster EML

or:

text
normal consumer syntax

It is provider-side syntax.


15. Customer validation reference ​

abap
METHOD validateCustomer.

  READ ENTITIES OF ZR_Reservation_### IN LOCAL MODE
    ENTITY Reservation
      FIELDS ( CustomerName )
      WITH CORRESPONDING #( keys )
    RESULT DATA(reservations).

  LOOP AT reservations INTO DATA(reservation)
    WHERE CustomerName IS INITIAL.

    APPEND VALUE #(
      %tky = reservation-%tky
    ) TO failed-reservation.

    APPEND VALUE #(
      %tky = reservation-%tky
      %msg = new_message_with_text(
        severity = if_abap_behv_message=>severity-error
        text     = 'Customer name is required'
      )
      %element-CustomerName = if_abap_behv=>mk-on
    ) TO reported-reservation.

  ENDLOOP.

ENDMETHOD.

16. Date validation reference ​

abap
METHOD validateDate.

  READ ENTITIES OF ZR_Reservation_### IN LOCAL MODE
    ENTITY Reservation
      FIELDS ( ReservationDate )
      WITH CORRESPONDING #( keys )
    RESULT DATA(reservations).

  DATA(today) = cl_abap_context_info=>get_system_date( ).

  LOOP AT reservations INTO DATA(reservation)
    WHERE ReservationDate < today.

    APPEND VALUE #(
      %tky = reservation-%tky
    ) TO failed-reservation.

    APPEND VALUE #(
      %tky = reservation-%tky
      %msg = new_message_with_text(
        severity = if_abap_behv_message=>severity-error
        text     = 'Reservation date cannot be in the past'
      )
      %element-ReservationDate = if_abap_behv=>mk-on
    ) TO reported-reservation.

  ENDLOOP.

ENDMETHOD.

17. Fee validation reference ​

A compact reference:

abap
METHOD validateFee.

  READ ENTITIES OF ZR_Reservation_### IN LOCAL MODE
    ENTITY Reservation
      FIELDS ( Fee CurrencyCode )
      WITH CORRESPONDING #( keys )
    RESULT DATA(reservations).

  LOOP AT reservations INTO DATA(reservation).

    DATA(instance_failed) = abap_false.

    IF reservation-Fee <= 0.

      instance_failed = abap_true.

      APPEND VALUE #(
        %tky = reservation-%tky
        %msg = new_message_with_text(
          severity = if_abap_behv_message=>severity-error
          text     = 'Fee must be greater than zero'
        )
        %element-Fee = if_abap_behv=>mk-on
      ) TO reported-reservation.

    ENDIF.

    IF reservation-CurrencyCode IS INITIAL.

      instance_failed = abap_true.

      APPEND VALUE #(
        %tky = reservation-%tky
        %msg = new_message_with_text(
          severity = if_abap_behv_message=>severity-error
          text     = 'Currency code is required'
        )
        %element-CurrencyCode = if_abap_behv=>mk-on
      ) TO reported-reservation.

    ENDIF.

    IF instance_failed = abap_true.

      APPEND VALUE #(
        %tky = reservation-%tky
      ) TO failed-reservation.

    ENDIF.

  ENDLOOP.

ENDMETHOD.

This version adds only one FAILED row per Reservation while still allowing multiple messages.


18. Why FAILED is authoritative ​

FAILED is the response structure that communicates unsuccessful instance processing.

Use it programmatically.

Do not write consumer logic such as:

text
if message text contains "required"
then assume failure

Message text is for explanation.

Failure state belongs to the failed response.


19. Why REPORTED still matters ​

REPORTED provides context such as:

text
error text
warning text
field target
instance relation
state/transition information

Without it, a consumer may know:

text
something failed

without knowing:

text
what the user should fix

20. %element ​

Example:

abap
%element-Fee = if_abap_behv=>mk-on

associates the message with:

text
Fee

This is especially valuable for service/UI consumers.

It enables a client to connect a backend error to a concrete input field.


21. Message severity ​

Lesson 13 uses:

abap
if_abap_behv_message=>severity-error

because all demonstrated validation violations block persistence.

Other scenarios may report:

text
information
warning
success

without failing the instance.

Do not use error severity merely for visual emphasis.


22. Transition messages in this lesson ​

Because the lesson does not set:

text
%state_area

the simple examples behave as transition-style messages.

This keeps the first message lesson focused.

State messages become more important for persistent UI state and draft scenarios.


23. State messages — reference only ​

A state message is associated with an instance and a state area.

The state area allows newer validation results to replace older messages for the same logical validation state.

This is useful for long-lived UI interactions.

Do not add state-message complexity unless students already understand:

text
FAILED
REPORTED
%MSG
%ELEMENT

24. Why validation should not modify data ​

A validation must inspect state without modifying it:

text
read
check
report
fail

EML modify statements are not allowed inside validations. Do not repair a value with direct SQL either. SAP documents this restriction in Validations.

If the value is genuinely derivable, use a determination.

Keeping those roles distinct makes behavior easier to reason about.


25. Why determination should not fail normal input ​

A determination is primarily about deriving data.

If a determination discovers a condition that makes the instance invalid, consider whether:

text
a validation

is the clearer model for rejecting the business state.

Do not overload one mechanism with every kind of rule.


26. Trigger design reference ​

Good trigger:

abap
validation validateDate on save
  { create; field ReservationDate; }

Less precise design:

text
run every validation after every update regardless of relevant fields

Trigger conditions communicate intent and can reduce unnecessary work.

Here, a date must be today or later when a Reservation is created or its date is changed. Existing Reservations can become historical; changing only Notes does not trigger validateDate.


27. Runtime sequence for valid create ​

text
MODIFY ENTITIES CREATE
        ↓
managed UUID numbering
        ↓
setInitialStatus on modify
        ↓
Status = N
        ↓
READ before commit sees N
        ↓
COMMIT ENTITIES
        ↓
validateCustomer
validateDate
validateFee
        ↓
all valid
        ↓
managed save
        ↓
row persisted

28. Runtime sequence for invalid create ​

text
MODIFY ENTITIES CREATE
        ↓
managed UUID assigned
        ↓
Status determination runs
        ↓
instance exists in buffer
        ↓
COMMIT ENTITIES
        ↓
on-save validations
        ↓
invalid fields found
        ↓
FAILED + REPORTED
        ↓
no successful persistence

This sequence is the conceptual center of Lesson 13.

The save fails for the current transaction as a whole. These tests commit the valid instance before starting the invalid create so that the earlier successful save remains intact.


29. Interaction response vs save response ​

The Lesson 12 distinction now becomes concrete.

Interaction phase ​

text
MODIFY ENTITIES

may return:

text
MAPPED
FAILED
REPORTED

for interaction processing.

Save phase ​

text
COMMIT ENTITIES RESPONSE OF ...

returns save-sequence responses.

An on-save validation can therefore reject work that reached the transaction successfully during MODIFY ENTITIES.


30. Why a generated UUID may exist for an invalid create ​

Managed UUID numbering is early numbering.

Therefore a create can receive:

text
generated UUID

before the final save validation succeeds.

A technical key being generated does not prove the instance was persisted.

This is a useful certification distinction.


31. Safe repeatable cleanup ​

For Lesson 13 reserved numbers:

text
94000001
94000002

a repeatable test can first discover old instances using a read-only query:

abap
SELECT FROM ZR_Reservation_###
  FIELDS Uuid
  WHERE ReservationNo = '94000001'
     OR ReservationNo = '94000002'
  INTO TABLE @DATA(old_rows).

Then delete only those UUIDs through EML:

abap
IF old_rows IS NOT INITIAL.

  MODIFY ENTITIES OF ZR_Reservation_###
    ENTITY Reservation
      DELETE FROM VALUE #(
        FOR row IN old_rows
        ( Uuid = row-Uuid )
      )
    FAILED DATA(failed_cleanup)
    REPORTED DATA(reported_cleanup).

  IF failed_cleanup IS NOT INITIAL.
    out->write( failed_cleanup ).
    out->write( reported_cleanup ).
    ROLLBACK ENTITIES.
    RETURN.
  ENDIF.

  COMMIT ENTITIES
    RESPONSE OF ZR_Reservation_###
      FAILED DATA(failed_cleanup_commit)
      REPORTED DATA(reported_cleanup_commit).
  DATA(cleanup_commit_subrc) = sy-subrc.

  IF cleanup_commit_subrc <> 0 OR failed_cleanup_commit IS NOT INITIAL.
    out->write( failed_cleanup_commit ).
    out->write( reported_cleanup_commit ).
    ROLLBACK ENTITIES.
    RETURN.
  ENDIF.

ENDIF.

The lookup is read-only.

The modification still goes through the BO.


32. Why the cleanup query may read the root CDS ​

The exercise is resetting known training data.

The read-only lookup does not bypass business behavior.

The actual delete remains:

text
EML

This keeps the architecture safe while making repeated class runs practical.


33. Defensive mapped-key lookup ​

Instead of:

abap
mapped_valid-reservation[ 1 ]-Uuid

prefer:

abap
DATA(uuid_valid) =
  mapped_valid-reservation[ %cid = 'VALID' ]-Uuid.

This explicitly correlates:

text
consumer ID
→ generated technical key

34. Guard mapped access ​

Before table-expression access, verify:

abap
IF failed_valid_modify IS NOT INITIAL.
  ...
ENDIF.

For a highly defensive implementation, also use:

text
line_exists( )

or:

text
OPTIONAL

before assuming a mapped row exists.


35. Reading message text in a console consumer ​

For learning/debugging, inspect the complete REPORTED response first.

If you want friendly console text, loop through reported entity messages and read the bound message object.

For the invalid-create save response:

abap
LOOP AT reported_invalid_commit-reservation INTO DATA(reported_row).
  IF reported_row-%msg IS BOUND.
    out->write( reported_row-%msg->if_message~get_text( ) ).
  ENDIF.
ENDLOOP.

Also output the complete response to inspect %tky and %element. Do not depend on validation or message order; SAP does not fix the execution order among validations.

Do not build business logic by parsing the resulting text.


36. Why direct message text is acceptable here ​

new_message_with_text is useful for a focused technical exercise.

For production-ready internationalized applications, prefer a design based on:

text
message classes
translatable texts
stable message identifiers

Lesson 13 teaches RAP message transport, not translation architecture.


37. Common activation error — handler missing ​

Symptom:

text
determination/validation declared
but implementation method missing

Fix:

text
place cursor on declaration
Ctrl+1
generate/add missing method

Do not remove the declaration merely to make activation green.


38. Common activation error — wrong behavior binding ​

BDEF:

text
validateCustomer

Incorrect behavior binding in the method signature:

abap
METHODS checkCustomer FOR VALIDATE ON SAVE
  IMPORTING keys FOR Reservation~validateCust.

The name after Reservation~ must match the validation declared in the BDEF:

abap
METHODS checkCustomer FOR VALIDATE ON SAVE
  IMPORTING keys FOR Reservation~validateCustomer.

The ABAP method name itself (checkCustomer here) can differ from the BDEF validation name. The FOR binding connects them, as shown in SAP's Validation Implementation reference.

Prefer ADT-generated signatures.


39. Common activation error — wrong entity after FOR ​

The handler signature must reference the correct behavior entity and behavior implementation name.

For example:

abap
IMPORTING keys FOR Reservation~validateCustomer

Do not use the persistence table name there.


40. Common runtime error — recursion ​

A determination that updates a field which itself retriggers the same determination can cause repeated processing.

Lesson 13 avoids this because:

text
trigger = create

and:

text
only update Status when initial

The determination is stable.


41. Common business-design error — overwrite consumer input ​

Bad defaulting logic:

text
always set Status = N

Better:

text
if Status is initial
→ set N

Defaulting should preserve valid explicit input unless the business contract says otherwise.


42. Common validation error — checking persistence instead of buffer ​

Do not validate the current transaction by selecting the persistence table and ignoring buffered values.

Use:

text
READ ENTITIES ... IN LOCAL MODE

to read the BO's current transactional state.

That is what the consumer is actually trying to save.


43. Common validation error — no field target ​

This works as a generic message:

text
Fee is invalid

But a better response also marks:

text
Fee

through %element.

Field targets become especially valuable when the same instance has multiple validation problems.


44. Common consumer error — inspect only sy-subrc ​

sy-subrc is useful after COMMIT ENTITIES.

But do not ignore:

text
FAILED
REPORTED

Those structures provide BO-specific details.

A certification-quality consumer inspects the RAP responses.


45. Common consumer error — not rolling back after failed test ​

After intentionally causing a save failure in a test class, explicitly reset the RAP transaction:

abap
ROLLBACK ENTITIES.

That keeps the next test independent and understandable.


46. Complete guided business rules ​

RuleMechanismTimingTarget
Default missing Status to NDeterminationon modifyStatus
Customer Name requiredValidationon saveCustomerName
Date cannot be pastValidationon saveReservationDate
Fee must be > 0Validationon saveFee
Currency requiredValidationon saveCurrencyCode

47. Why Status is a determination, not a validation ​

The application knows a reasonable default:

text
N = New

So the consumer does not need to provide it.

This is derivation/defaulting.


48. Why Customer Name is a validation, not a determination ​

The backend cannot truthfully invent:

text
CustomerName

for the consumer.

So missing Customer Name is invalid input.


49. Why past date is a validation ​

The date already has a value.

The problem is not:

text
missing derived data

The problem is:

text
business value violates a rule

Therefore validate it.


50. Practice BO reference ​

The Support Issue practice uses:

text
ZISSUE_###
    ↓
ZR_ISSUE_###
    ↓
Managed Behavior
    ↓
ZBP_R_ISSUE_###

Recommended Lesson 13 practice rules:

text
Determination
→ default missing Status to O

Validation
→ Title required

Validation
→ ReportedOn cannot be in the future

Validation
→ EstimatedCost cannot be negative
→ Currency required when EstimatedCost is nonzero

51. Practice determination BDEF reference ​

abap
determination setInitialStatus on modify { create; }

Status should become:

text
O

when initial.


52. Practice validation BDEF references ​

abap
validation validateTitle on save
  { create; field Title; }

validation validateReportedOn on save
  { create; field ReportedOn; }

validation validateCost on save
  { create; field EstimatedCost, CurrencyCode; }

53. Practice messages ​

Suitable messages include:

text
Title is required
Reported date cannot be in the future
Estimated cost cannot be negative
Currency code is required when cost is provided

Each error should target the relevant field where practical.


54. Practice cost rule nuance ​

A useful rule is:

text
EstimatedCost < 0
→ invalid

EstimatedCost <> 0 AND CurrencyCode is initial
→ invalid

This allows:

text
0 cost + no currency

if your course scenario treats zero as "no estimate yet."

Students should state the business assumption they implement.


55. Certification relevance ​

Practical RAP work commonly requires students to distinguish:

text
behavior declaration
behavior implementation
interaction phase
save phase
determination
validation
FAILED
REPORTED
field-targeted messages

The important skill is not remembering one exact sample.

It is tracing:

text
where the rule is declared
when it runs
where it is implemented
how failure reaches the consumer

56. Instructor demonstration sequence ​

Recommended classroom sequence:

text
1. Run Lesson 12 create without new rules
2. Add determination only
3. Create without Status
4. READ before commit → see N
5. Add one customer validation
6. Create blank CustomerName
7. MODIFY response looks acceptable
8. COMMIT → failure
9. Add field-targeted message
10. Add date and fee validations
11. Trigger several messages at once
12. F8 → prove invalid row absent

This incremental approach makes the framework behavior visible.


57. Final instructor validation ​

Before Lesson 14, students should be able to explain:

text
determination
=
automatic derivation

validation
=
business gate before save

FAILED
=
programmatic failure response

REPORTED
=
message/context response

%ELEMENT
=
message field target

IN LOCAL MODE
=
provider-side BO access

They should also be able to trigger a validation deliberately and diagnose the resulting EML responses.


58. Lesson 14 boundary ​

Do not add these yet:

text
actions
instance feature control
dynamic operation enablement
dynamic field control

Lesson 13 answers:

Is the state valid, and what data can the BO derive automatically?

Lesson 14 answers:

Which business operations are available, and when should operations/fields be enabled?