RAP Basics Part 6: Behavior Definition — CRUD, Field Control and Features
Control what a RAP BO allows with the behavior definition: CRUD operations, static field properties (readonly, mandatory, readonly:update), dynamic field and operation control via features: instance, global features, field mapping, and strict mode.
Explanation
📌 KEY POINTS
✅ The BDEF is the contract of the BO: which operations exist, which fields are editable, which actions are available, and under which conditions.
✅ Static field control: field ( readonly ), ( mandatory ), ( readonly : update ) — applies always, no code.
✅ Dynamic control: features : instance on operations/actions/fields → implemented in get_instance_features, evaluated per instance (e.g. no update when status = Accepted).
✅ mapping for <table> maps CDS aliases to table fields when names differ (needed for some scenarios, e.g. unmanaged save or when the framework cannot derive mapping).
⚠️ Hiding a button in the UI is not protection — only BDEF feature control (and authorization) enforces it for every consumer, including APIs and EML.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Part 5 built the composition tree. Now we control what each entity allows.
━━━━━━━━━━━━━━━━━━━━
▶ 1. OPERATIONS
- create; update; delete; — standard operations.
- Leave out delete if the BO must never be deleted.
- Children: create via association on the parent; update/delete on the child.
━━━━━━━━━━━━━━━━━━━━
▶ 2. STATIC FIELD CONTROL
- field ( readonly ) TravelID, OverallStatus; — set only by logic.
- field ( mandatory ) AgencyID, CustomerID; — must be filled (checked on save, marked in UI).
- field ( readonly : update ) CustomerID; — editable on create only.
- field ( readonly, numbering : managed ) TravelUUID;
━━━━━━━━━━━━━━━━━━━━
▶ 3. DYNAMIC FEATURE CONTROL
- update ( features : instance ); delete ( features : instance );
- action ( features : instance ) acceptTravel …;
- field ( features : instance ) BookingFee;
- Implement get_instance_features: read the instance, return enabled/disabled per operation.
━━━━━━━━━━━━━━━━━━━━
▶ 4. GLOBAL FEATURES
- ( features : global ) — independent of an instance, e.g. disable create outside business hours or by configuration.
━━━━━━━━━━━━━━━━━━━━
▶ 5. STRICT MODE AND MAPPING
- strict ( 2 ); enforces current best practice syntax checks — always use it for new BOs.
- mapping for ztravel_a { TravelID = travel_id; AgencyID = agency_id; … }
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
- List fields: editable, readonly, mandatory, create-only.
- Add static field control in the BDEF.
- Add features : instance for status-dependent update/delete/actions.
- Implement get_instance_features in the handler class.
- Test in the Fiori preview and via EML.
✅ CHECKPOINT: an accepted travel shows no Edit/Delete buttons in Fiori and an EML UPDATE on it is rejected.
Code example
// ===== STEP 1: BDEF with static and dynamic control =====define behavior for ZI_Travel alias Travelpersistent table ztravel_alock masterauthorization master ( instance )etag master LocalLastChangedAt{ create; update ( features : instance ); delete ( features : instance ); field ( readonly, numbering : managed ) TravelUUID; field ( readonly ) TravelID, OverallStatus, LocalLastChangedAt; field ( mandatory ) AgencyID, CustomerID; field ( readonly : update ) CustomerID; action ( features : instance ) acceptTravel result [1] $self; mapping for ztravel_a { TravelUUID = travel_uuid; TravelID = travel_id; AgencyID = agency_id; CustomerID = customer_id; OverallStatus = overall_status; LocalLastChangedAt = local_last_changed_at; }} * ===== STEP 2: dynamic feature control in the handler class ===== METHOD get_instance_features. DATA lt_travel TYPE TABLE FOR READ RESULT zi_travel\travel. DATA ls_travel LIKE LINE OF lt_travel. DATA ls_result LIKE LINE OF result. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( OverallStatus ) WITH CORRESPONDING #( keys ) RESULT lt_travel. * ===== STEP 3: accepted travels cannot be changed, deleted or accepted again ===== LOOP AT lt_travel INTO ls_travel. CLEAR ls_result. ls_result-%tky = ls_travel-%tky. IF ls_travel-OverallStatus = 'A'. ls_result-%update = if_abap_behv=>fc-o-disabled. ls_result-%delete = if_abap_behv=>fc-o-disabled. ls_result-%action-acceptTravel = if_abap_behv=>fc-o-disabled. ELSE. ls_result-%update = if_abap_behv=>fc-o-enabled. ls_result-%delete = if_abap_behv=>fc-o-enabled. ls_result-%action-acceptTravel = if_abap_behv=>fc-o-enabled. ENDIF. APPEND ls_result TO result. ENDLOOP. ENDMETHOD.Real project scenario
On a pricing-agreement BO, the UI team hid the Edit button for approved agreements using a UI5 extension. Three months later, an integration partner used the Web API binding of the same BO and changed approved agreements, which triggered a compliance finding. The fix was to move the rule into the BDEF: update ( features : instance ) with get_instance_features returning disabled for status Approved. Now the Fiori app, the API and every EML caller are blocked consistently, and the UI extension was deleted.
Common mistakes
- Enforcing editability only in the UI instead of feature control in the BDEF.
- Marking fields readonly that a determination must set, then being surprised the user cannot set them — readonly applies to the consumer, not IN LOCAL MODE logic.
- Forgetting features : instance in the BDEF after implementing get_instance_features (method never called).
- Using mandatory without also validating business rules on save.
- Not using strict ( 2 ) for new BOs.
Best practices
- Put every "may the user do this?" rule in the BDEF (features/authorization), never only in the UI.
- Use static field control where possible; dynamic control only for state-dependent rules.
- Read only the fields you need in get_instance_features.
- Always start new BDEFs with strict ( 2 ).
- Test feature control through EML as well as through Fiori.
Interview angle
Questions: "How do you make a RAP field read-only depending on status?", "Difference between static and dynamic feature control?", "Why is hiding a button not enough?"
Strong answer: static control (readonly, mandatory, readonly : update) is declared in the BDEF and needs no code; dynamic control uses features : instance and get_instance_features, returning enabled/disabled per instance. Because every consumer — Fiori, Web API, EML — goes through the BO, only BDEF control is real enforcement; UI hiding is cosmetic.
Practice this lesson
Quick check
1. Which RAP artifact declares create, update, delete, field control, actions, determinations and validations?
2. CustomerID must be entered when a travel is created but must not change afterwards. Which BDEF statement fits?
3. Travels with status Accepted must not be updatable by any consumer (Fiori, Web API, EML). What is the correct approach?
Interview questions
1. What is the role of behavior definition in RAP?
Answer it in your own words first, then compare.
2. A Fiori app hid the Approve button for approved or foreign-region requests, but an integration partner using the OData API was still able to approve such requests. Where should this rule have been implemented?
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.
More lessons in this topic
- RAP Basics Part 1: RAP Architecture — Layers, Artifacts and When RAP Fits
- RAP Basics Part 10: EML — Entity Manipulation Language
- RAP Basics Part 11: Draft, Locking and Authorization
- RAP Basics Part 2: Data Model Design — Tables, Keys, Administrative Fields and Interface Views
- RAP Basics Part 3: Projection Layer and Metadata Extensions
- RAP Basics Part 4: Implementation Types — Managed, Unmanaged, Additional Save and Unmanaged Save
- RAP Basics Part 5: Root Entity, Child Entity and Composition
- RAP Basics Part 7: Actions, Determinations and Validations — Design Decisions