惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
NISL@THU
NISL@THU
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
Cyberwarzone
Cyberwarzone
D
Docker
The Hacker News
The Hacker News
C
CERT Recently Published Vulnerability Notes
Vercel News
Vercel News
Project Zero
Project Zero
S
Schneier on Security
aimingoo的专栏
aimingoo的专栏
I
Intezer
腾讯CDC
M
MIT News - Artificial intelligence
Hugging Face - Blog
Hugging Face - Blog
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
AWS News Blog
AWS News Blog
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Vulnerabilities – Threatpost
G
Google Developers Blog
N
Netflix TechBlog - Medium
The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
A
Arctic Wolf
S
Securelist
酷 壳 – CoolShell
酷 壳 – CoolShell
Cisco Talos Blog
Cisco Talos Blog
Recent Announcements
Recent Announcements
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 热门话题
T
Threatpost
Latest news
Latest news
Blog — PlanetScale
Blog — PlanetScale
Security Latest
Security Latest
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
The GitHub Blog
The GitHub Blog
T
Tor Project blog
P
Proofpoint News Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
ABAP RAP Deep Dive — Part 2: Implementing Business Logic, Validations, and Determinations in SAP S/4HANA
Oktay Ates · 2026-05-06 · via DEV Community

Oktay Ates

If you’ve already laid the groundwork with the ABAP RESTful Application Programming Model, you know that RAP isn’t just another framework — it’s a fundamental shift in how we build business applications on SAP S/4HANA. In Part 1 of this RAP series, we covered the architectural foundations: behavior definitions, business objects, and the basic anatomy of a RAP application. Now it’s time to get our hands dirty.

This article focuses on the parts of RAP that truly determine whether your application is production-ready: business logic implementation, validations, and determinations. These are the components where most projects either succeed gracefully or collapse under edge cases. I’ve watched both happen on real customer systems — and I’ll share what separates the two.

Why Business Logic Placement Matters More Than You Think

Before writing a single line of handler code, you need to make a deliberate architectural decision: where does your business logic actually live?

In classic ABAP, business logic was scattered across BAdIs, user-exits, function modules, and the occasional FORM routine from 2003 that nobody dares to touch. RAP gives you a structured answer to this problem. Your logic lives in handler classes — specifically in the ABAP behavior implementation (BDEF implementation class), and it’s organized into clearly defined hook points.

The three most critical hook points you need to master are:

  • Validations — Check data integrity, raise messages, prevent incorrect saves

  • Determinations — Derive and auto-calculate field values based on triggers

  • Actions — Trigger explicit business processes (we’ll cover actions in Part 3)

Get this separation right and your application will be maintainable. Get it wrong and you’ll be debugging mysterious side effects six months from now.

Setting Up the Behavior Implementation Class

Assuming you have a CDS-based business object (as detailed in our CDS consumption views article), your behavior definition (BDEF) already declares which validations and determinations exist. Now you need to implement them.

Here’s a realistic BDEF excerpt for a purchase requisition-like entity:


/** Behavior Definition: ZI_PurchaseRequisition **/
managed implementation in class zbp_i_purchase_requisition unique;

define behavior for ZI_PurchaseRequisition alias PurchReq
persistent table zpur_req_head
lock master
authorization master ( instance )
etag master LastChangedAt
{
  create;
  update;
  delete;

  field ( readonly ) PurchReqId, CreatedAt, CreatedBy;
  field ( mandatory ) MaterialNumber, Plant, Quantity, Unit;

  /** Validations **/
  validation validateMaterial on save { create; update; }
  validation validateQuantity  on save { create; update; }

  /** Determinations **/
  determination setDefaultPlant on modify { create; }
  determination calculateNetValue on modify { field Quantity, PricePerUnit; }

  mapping for zpur_req_head corresponding;
}

Enter fullscreen mode Exit fullscreen mode

The key phrases here are on save and on modify. These are your triggers. on save validations fire when the user attempts to persist data. on modify determinations fire reactively when specific fields change. Understanding this distinction prevents an entire class of bugs.

Implementing Validations: More Than Just IF Checks

A validation in RAP isn’t just an IF statement wrapped in a method. It’s a contract between your business object and its consumers. It must:

  • Read the relevant data for the affected instances

  • Evaluate the business rule

  • Report failures back via the RAP message framework — not via exceptions

Here’s a production-grade validation implementation:


CLASS lhc_PurchReq DEFINITION INHERITING FROM cl_abap_behavior_handler.

  PRIVATE SECTION.

    METHODS validateMaterial FOR VALIDATE ON SAVE
      IMPORTING keys FOR PurchReq~validateMaterial.

    METHODS validateQuantity FOR VALIDATE ON SAVE
      IMPORTING keys FOR PurchReq~validateQuantity.

ENDCLASS.

CLASS lhc_PurchReq IMPLEMENTATION.

  METHOD validateMaterial.
    " Step 1: Read current state of the affected instances
    READ ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        FIELDS ( MaterialNumber Plant )
        WITH CORRESPONDING #( keys )
      RESULT DATA(lt_purch_req)
      FAILED DATA(lt_failed).

    " Step 2: Collect unique material/plant combinations for DB check
    DATA lt_mat_plant TYPE SORTED TABLE OF mara
      WITH UNIQUE KEY matnr WITH HEADER LINE.

    LOOP AT lt_purch_req INTO DATA(ls_req).
      " Check if material number is initial - mandatory check
      IF ls_req-MaterialNumber IS INITIAL.
        APPEND VALUE #(
          %tky = ls_req-%tky
        ) TO failed-purchreq.

        APPEND VALUE #(
          %tky        = ls_req-%tky
          %state_area = 'VALIDATE_MATERIAL'
          %msg        = new_message(
                          id       = 'ZPUR_REQ_MSGS'
                          number   = '001'
                          severity = if_abap_behv_message=>severity-error
                          v1       = ls_req-MaterialNumber
                        )
          %element-MaterialNumber = if_abap_behv=>mk-on
        ) TO reported-purchreq.

        CONTINUE.
      ENDIF.

      " Validate material exists in MARA
      SELECT SINGLE matnr
        FROM mara
        WHERE matnr = @ls_req-MaterialNumber
        INTO @DATA(lv_matnr).

      IF sy-subrc <> 0.
        APPEND VALUE #( %tky = ls_req-%tky ) TO failed-purchreq.
        APPEND VALUE #(
          %tky        = ls_req-%tky
          %state_area = &#039;VALIDATE_MATERIAL&#039;
          %msg        = new_message(
                          id       = &#039;ZPUR_REQ_MSGS&#039;
                          number   = &#039;002&#039;
                          severity = if_abap_behv_message=>severity-error
                          v1       = ls_req-MaterialNumber
                        )
          %element-MaterialNumber = if_abap_behv=>mk-on
        ) TO reported-purchreq.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

  METHOD validateQuantity.
    READ ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        FIELDS ( Quantity )
        WITH CORRESPONDING #( keys )
      RESULT DATA(lt_purch_req).

    LOOP AT lt_purch_req INTO DATA(ls_req).
      IF ls_req-Quantity <= 0.
        APPEND VALUE #( %tky = ls_req-%tky ) TO failed-purchreq.
        APPEND VALUE #(
          %tky        = ls_req-%tky
          %state_area = &#039;VALIDATE_QUANTITY&#039;
          %msg        = new_message(
                          id       = &#039;ZPUR_REQ_MSGS&#039;
                          number   = &#039;003&#039;
                          severity = if_abap_behv_message=>severity-error
                        )
          %element-Quantity = if_abap_behv=>mk-on
        ) TO reported-purchreq.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

ENDCLASS.

Enter fullscreen mode Exit fullscreen mode

Notice the pattern: you always use READ ENTITIES IN LOCAL MODE inside a validation. This reads from the RAP transactional buffer — the state the user intends to save — not the database. This is critical. If you query the database directly, you’ll validate the old data, not the pending changes.

Also notice the %element mapping. Setting %element-FieldName = if_abap_behv=>mk-on tells the Fiori UI exactly which field is in error. The UI highlights it automatically. This is the difference between a professional application and a frustrating one.

Implementing Determinations: Reactive Business Logic Done Right

Determinations are where RAP really shines compared to legacy approaches. Think of them as reactive field derivations — when something changes, something else is automatically recalculated.

A common pitfall I see in projects: developers put too much logic in determinations, making them behave like mini workflows. Keep determinations focused on field value derivation. Complex orchestration belongs in actions.


  METHOD setDefaultPlant FOR DETERMINE ON MODIFY
    IMPORTING keys FOR PurchReq~setDefaultPlant.

    &quot; Read current instances
    READ ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        FIELDS ( Plant CreatedBy )
        WITH CORRESPONDING #( keys )
      RESULT DATA(lt_purch_req).

    DATA lt_update TYPE TABLE FOR UPDATE zi_purchaserequisition\PurchReq.

    LOOP AT lt_purch_req INTO DATA(ls_req).
      &quot; Only set default if Plant is empty
      IF ls_req-Plant IS INITIAL.

        &quot; Derive default plant from user parameters
        DATA(lv_plant) = CONV werks_d( cl_abap_context_info=>get_system_attribute(
                            if_abap_context_info=>system_attribute-logon_plant ) ).

        &quot; Fallback to hardcoded default if user has no parameter
        IF lv_plant IS INITIAL.
          lv_plant = &#039;1000&#039;.
        ENDIF.

        APPEND VALUE #(
          %tky  = ls_req-%tky
          Plant = lv_plant
          %control-Plant = if_abap_behv=>mk-on
        ) TO lt_update.
      ENDIF.
    ENDLOOP.

    &quot; Modify the transactional buffer
    MODIFY ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        UPDATE FIELDS ( Plant )
        WITH lt_update
      REPORTED DATA(lt_reported).

    rv_failed_reported = REDUCE #(
      INIT s = VALUE failed_reported( )
      FOR msg IN lt_reported
      NEXT s = VALUE #( BASE s
        reported-purchreq = VALUE #( BASE s-reported-purchreq
          ( LINES OF msg-purchreq ) ) ) ).

  ENDMETHOD.

  METHOD calculateNetValue FOR DETERMINE ON MODIFY
    IMPORTING keys FOR PurchReq~calculateNetValue.

    READ ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        FIELDS ( Quantity PricePerUnit )
        WITH CORRESPONDING #( keys )
      RESULT DATA(lt_purch_req).

    DATA lt_update TYPE TABLE FOR UPDATE zi_purchaserequisition\PurchReq.

    LOOP AT lt_purch_req INTO DATA(ls_req).
      DATA(lv_net_value) = ls_req-Quantity * ls_req-PricePerUnit.

      APPEND VALUE #(
        %tky      = ls_req-%tky
        NetValue  = lv_net_value
        %control-NetValue = if_abap_behv=>mk-on
      ) TO lt_update.
    ENDLOOP.

    MODIFY ENTITIES OF zi_purchaserequisition IN LOCAL MODE
      ENTITY PurchReq
        UPDATE FIELDS ( NetValue )
        WITH lt_update
      REPORTED DATA(lt_reported).

  ENDMETHOD.

Enter fullscreen mode Exit fullscreen mode

The MODIFY ENTITIES IN LOCAL MODE call is essential here. You’re writing back to the RAP transactional buffer, not the database. The framework handles the actual persistence during the save sequence. Never call direct database updates inside a determination — that breaks the RAP transaction model entirely.

The save_modified Method: Your Last Line of Defense

For unmanaged RAP scenarios (or hybrid scenarios with with additional save), you’ll implement the save_modified method. This is where you perform the actual database writes for legacy table structures that don’t map cleanly to CDS.


CLASS lsc_ZI_PURCHASEREQUISITION DEFINITION INHERITING FROM cl_abap_behavior_saver.

  PROTECTED SECTION.
    METHODS save_modified REDEFINITION.
    METHODS cleanup_finalize REDEFINITION.

ENDCLASS.

CLASS lsc_ZI_PURCHASEREQUISITION IMPLEMENTATION.

  METHOD save_modified.
    &quot; Handle CREATE operations
    IF create-purchreq IS NOT INITIAL.
      DATA lt_insert TYPE TABLE OF zpur_req_head.

      LOOP AT create-purchreq INTO DATA(ls_create).
        APPEND CORRESPONDING zpur_req_head( ls_create ) TO lt_insert.
      ENDLOOP.

      INSERT zpur_req_head FROM TABLE @lt_insert.
    ENDIF.

    &quot; Handle UPDATE operations  
    IF update-purchreq IS NOT INITIAL.
      LOOP AT update-purchreq INTO DATA(ls_update).
        UPDATE zpur_req_head
          SET material_number = CASE WHEN ls_update-%control-MaterialNumber = cl_abap_behv=>flag_changed
                                     THEN @ls_update-MaterialNumber
                                     ELSE material_number END,
              quantity        = CASE WHEN ls_update-%control-Quantity = cl_abap_behv=>flag_changed
                                     THEN @ls_update-Quantity
                                     ELSE quantity END,
              net_value       = CASE WHEN ls_update-%control-NetValue = cl_abap_behv=>flag_changed
                                     THEN @ls_update-NetValue
                                     ELSE net_value END
          WHERE purch_req_id = @ls_update-PurchReqId.
      ENDLOOP.
    ENDIF.

    &quot; Handle DELETE operations
    IF delete-purchreq IS NOT INITIAL.
      DELETE zpur_req_head
        FROM TABLE @( CORRESPONDING #( delete-purchreq MAPPING purch_req_id = PurchReqId ) ).
    ENDIF.
  ENDMETHOD.

  METHOD cleanup_finalize.
    &quot; Clean up any draft-related or temporary state here
  ENDMETHOD.

ENDCLASS.

Enter fullscreen mode Exit fullscreen mode

The %control structure is your best friend in save_modified. It tells you precisely which fields were actually changed in the current transaction, so you don’t accidentally overwrite fields with empty values.

Common Pitfalls and How to Avoid Them

Having reviewed and debugged RAP implementations across multiple S/4HANA projects, here are the mistakes I see most often:

1. Querying the Database Inside Validations Instead of Using READ ENTITIES

If you bypass READ ENTITIES IN LOCAL MODE and go directly to the database, you’ll validate stale data. The user could have changed a value that hasn’t been saved yet — your validation will see the old value and pass when it should fail. Always use the transactional buffer.

2. Raising Exceptions Instead of Reporting to the Framework

Classic ABAP developers instinctively throw exceptions when something goes wrong. In RAP, you populate the failed and reported structures. Raising an unhandled exception in a handler method will crash the entire OData call with a generic error — zero useful feedback for the user.

3. Over-triggering Determinations

If your determination triggers on create and update for all fields, it fires constantly. Be precise with your trigger conditions. Use field FieldName triggers to limit determination execution to only when relevant data changes. This has a direct performance impact — especially relevant if you’ve read our ABAP performance optimization guide.

4. Mixing Validation and Determination Responsibilities

A determination should never reject a save — that’s a validation’s job. A validation should never modify data — that’s a determination’s job. Keep the responsibilities clean. This principle aligns with the broader clean code practices we’ve discussed in earlier articles.

Testing Your Business Logic: Don’t Skip This

RAP business logic is fully testable using ABAP Unit Tests with the CL_ABAP_BEHV_TEST_ENVIRONMENT class. This is not optional — if you’re building production applications, you need automated tests. Our article on ABAP unit testing in S/4HANA covers the foundational approach, but for RAP specifically, the key is isolating the behavior implementation from the database.


CLASS ltc_validate_material DEFINITION FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    DATA mo_env TYPE REF TO if_abap_behv_test_environment.

    METHODS setup.
    METHODS teardown.
    METHODS test_invalid_material FOR TESTING.
    METHODS test_valid_material   FOR TESTING.

ENDCLASS.

CLASS ltc_validate_material IMPLEMENTATION.

  METHOD setup.
    mo_env = cl_abap_behv_test_environment=>create(
               entity_type = &#039;ZI_PURCHASEREQUISITION&#039; ).
  ENDMETHOD.

  METHOD teardown.
    mo_env->destroy( ).
  ENDMETHOD.

  METHOD test_invalid_material.
    &quot; Prepare test data with invalid material
    DATA lt_create TYPE TABLE FOR CREATE zi_purchaserequisition\PurchReq.
    APPEND VALUE #(
      %cid           = &#039;TEST_001&#039;
      MaterialNumber = &#039;NONEXISTENT&#039;
      Quantity       = 10
      Unit           = &#039;EA&#039;
    ) TO lt_create.

    &quot; Execute the create operation
    MODIFY ENTITIES OF zi_purchaserequisition
      ENTITY PurchReq
      CREATE FROM lt_create
      FAILED DATA(lt_failed)
      REPORTED DATA(lt_reported).

    COMMIT ENTITIES BEGIN
      RESPONSE OF zi_purchaserequisition
      FAILED DATA(lt_commit_failed)
      REPORTED DATA(lt_commit_reported).
    COMMIT ENTITIES END.

    &quot; Assert that the operation failed
    cl_abap_unit_assert=>assert_not_initial(
      act  = lt_commit_failed-purchreq
      msg  = &#039;Expected validation failure for nonexistent material&#039; ).
  ENDMETHOD.

  METHOD test_valid_material.
    &quot; This would mock MARA lookup and test the happy path
    &quot; Implementation omitted for brevity  see unit testing article for mocking patterns
  ENDMETHOD.

ENDCLASS.

Enter fullscreen mode Exit fullscreen mode

What’s Coming in Part 3

We’ve covered validations and determinations in depth. In Part 3 of this RAP series, we’ll tackle RAP Actions — both instance-bound and static — along with the draft handling mechanism. Draft is where RAP truly differentiates itself from any previous SAP programming model, and it’s where many implementations get architecturally wrong. You won’t want to miss it.

Also worth revisiting: if you’re uncertain about how CDS access control integrates with RAP’s authorization master concept, our CDS DCL and access control article provides exactly the foundation you need before diving into RAP authorization objects.

Key Takeaways

  • Always use READ ENTITIES IN LOCAL MODE inside validations and determinations — never query the database directly for pending changes

  • Validations report errors via failed and reported structures; they never modify data

  • Determinations derive field values via MODIFY ENTITIES IN LOCAL MODE; they never reject saves

  • Use %control in save_modified to process only actually changed fields

  • Keep determination triggers precise — over-triggering is a direct performance concern

  • Automated testing with CL_ABAP_BEHV_TEST_ENVIRONMENT is non-negotiable for production quality

If you found this article useful, drop a comment below with the specific RAP challenge you’re working through — I read every response and often use real questions as the basis for follow-up content. Share this with a colleague who’s still wrestling with legacy BAPI-based development. The migration path to RAP is worth every bit of the learning curve.