Skip to main content

The RAP Model: Building OData Services with RESTful ABAP Programming

RESTful ABAP Programming (RAP) represents the future of ABAP development on SAP S/4HANA and BTP.

If you're building Fiori apps, analytical applications, or any modern SAP UI, understanding RAP is no longer optional — it's the recommended approach from SAP.

This comprehensive guide will take you from zero to building a complete OData service using CDS views, behavior definitions, and the RAP framework.

What is RAP and Why Does It Matter?

RESTful ABAP Programming is SAP's modern development model for building:

  • Transactional applications (Create, Read, Update, Delete operations)
  • Analytical applications (Read-only data consumption)
  • OData services (For UI5, mobile, and external consumers)
  • Business services (Reusable business logic)

The Old Way (Classic ABAP + SEGW)

1. Create database tables
2. Write function modules for CRUD operations
3. Use Gateway Service Builder (SEGW) to define OData service
4. Map entities and implement DPC/MPC classes
5. Manually handle Create, Read, Update, Delete, Query
6. Write validation and authorization logic
7. Handle associations and navigation
8. Register and activate service

Result: 15-20 files, weeks of development

The RAP Way

1. Create CDS view (data model)
2. Create CDS projection (service model)
3. Create Behavior Definition (business logic)
4. Create Service Definition
5. Create Service Binding (OData exposure)

Result: 5 files, days of development

RAP Architecture: The Big Picture

RAP follows a layered architecture:

Layer 1: Data Model (CDS Views)

Core business data with associations and annotations

Layer 2: Behavior Definition

Business logic, validations, determinations, actions

Layer 3: Service Definition

Exposed entities for consumption

Layer 4: Service Binding

Protocol binding (OData V2/V4, UI, Web API)

Step-by-Step: Building a Complete RAP Application

Let's build a Travel Booking Application with full CRUD capabilities.

Step 1: Create Database Tables

@EndUserText.label : 'Travel Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table ztravel_db {
  key client            : abap.clnt not null;
  key travel_id         : /dmo/travel_id not null;
  agency_id             : /dmo/agency_id;
  customer_id           : /dmo/customer_id;
  begin_date            : /dmo/begin_date;
  end_date              : /dmo/end_date;
  @Semantics.amount.currencyCode : 'ztravel_db.currency_code'
  booking_fee           : /dmo/booking_fee;
  @Semantics.amount.currencyCode : 'ztravel_db.currency_code'
  total_price           : /dmo/total_price;
  currency_code         : /dmo/currency_code;
  description           : /dmo/description;
  status                : /dmo/travel_status;
  createdby             : abp_creation_user;
  createdat             : abp_creation_tstmpl;
  lastchangedby         : abp_locinst_lastchange_user;
  lastchangedat         : abp_locinst_lastchange_tstmpl;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
}

Step 2: Create CDS Interface View (Data Model)

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Travel Data'
define root view entity ZI_TRAVEL
  as select from ztravel_db
  association [0..1] to /DMO/I_Agency   as _Agency   on $projection.AgencyID = _Agency.AgencyID
  association [0..1] to /DMO/I_Customer as _Customer on $projection.CustomerID = _Customer.CustomerID
{
  key travel_id          as TravelID,
      agency_id          as AgencyID,
      customer_id        as CustomerID,
      begin_date         as BeginDate,
      end_date           as EndDate,
      @Semantics.amount.currencyCode: 'CurrencyCode'
      booking_fee        as BookingFee,
      @Semantics.amount.currencyCode: 'CurrencyCode'
      total_price        as TotalPrice,
      currency_code      as CurrencyCode,
      description        as Description,
      status             as Status,
      
      @Semantics.user.createdBy: true
      createdby          as CreatedBy,
      @Semantics.systemDateTime.createdAt: true
      createdat          as CreatedAt,
      @Semantics.user.lastChangedBy: true
      lastchangedby      as LastChangedBy,
      @Semantics.systemDateTime.lastChangedAt: true
      lastchangedat      as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,
      
      /* Associations */
      _Agency,
      _Customer
}

Step 3: Create CDS Projection View (Service Model)

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Travel Projection'
@Metadata.allowExtensions: true
@Search.searchable: true
define root view entity ZC_TRAVEL
  provider contract transactional_query
  as projection on ZI_TRAVEL
{
  key TravelID,
      
      @Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Agency', element: 'AgencyID'} }]
      @ObjectModel.text.element: ['AgencyName']
      @Search.defaultSearchElement: true
      AgencyID,
      _Agency.Name       as AgencyName,
      
      @Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Customer', element: 'CustomerID'} }]
      @ObjectModel.text.element: ['CustomerName']
      @Search.defaultSearchElement: true
      CustomerID,
      _Customer.LastName as CustomerName,
      
      BeginDate,
      EndDate,
      
      @Semantics.amount.currencyCode: 'CurrencyCode'
      BookingFee,
      
      @Semantics.amount.currencyCode: 'CurrencyCode'
      TotalPrice,
      
      @Consumption.valueHelpDefinition: [{ entity: {name: 'I_Currency', element: 'Currency'} }]
      CurrencyCode,
      
      Description,
      
      @ObjectModel.text.element: ['StatusText']
      Status,
      _Status._Text.Text as StatusText : localized,
      
      CreatedBy,
      CreatedAt,
      LastChangedBy,
      LastChangedAt,
      LocalLastChangedAt,
      
      /* Associations */
      _Agency,
      _Customer
}

Step 4: Create Behavior Definition (Business Logic)

managed implementation in class zbp_i_travel unique;
strict ( 2 );

define behavior for ZI_TRAVEL alias Travel
persistent table ztravel_db
lock master
authorization master ( instance )
etag master LocalLastChangedAt
{
  // Standard operations
  create;
  update;
  delete;
  
  // Fields
  field ( readonly ) TravelID, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt, LocalLastChangedAt;
  field ( mandatory ) AgencyID, CustomerID, BeginDate, EndDate, Status;
  
  // Validations
  validation validateAgency on save { field AgencyID; create; }
  validation validateCustomer on save { field CustomerID; create; }
  validation validateDates on save { field BeginDate, EndDate; create; }
  
  // Determinations
  determination calculateTotalPrice on modify { field BookingFee; create; }
  determination setInitialStatus on modify { create; }
  
  // Actions
  action ( features : instance ) acceptTravel result [1] $self;
  action ( features : instance ) rejectTravel result [1] $self;
  action ( features : instance ) deductDiscount parameter /dmo/a_travel_discount result [1] $self;
  
  // Associations
  association _Agency { create; }
  association _Customer { create; }
  
  mapping for ztravel_db
  {
    TravelID = travel_id;
    AgencyID = agency_id;
    CustomerID = customer_id;
    BeginDate = begin_date;
    EndDate = end_date;
    BookingFee = booking_fee;
    TotalPrice = total_price;
    CurrencyCode = currency_code;
    Description = description;
    Status = status;
    CreatedBy = createdby;
    CreatedAt = createdat;
    LastChangedBy = lastchangedby;
    LastChangedAt = lastchangedat;
    LocalLastChangedAt = local_last_changed_at;
  }
}

Step 5: Implement Behavior Logic (ABAP Class)

CLASS zbp_i_travel DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zi_travel.
ENDCLASS.

CLASS zbp_i_travel IMPLEMENTATION.
ENDCLASS.

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS validateAgency FOR VALIDATE ON SAVE
      IMPORTING keys FOR Travel~validateAgency.
      
    METHODS validateCustomer FOR VALIDATE ON SAVE
      IMPORTING keys FOR Travel~validateCustomer.
      
    METHODS validateDates FOR VALIDATE ON SAVE
      IMPORTING keys FOR Travel~validateDates.
      
    METHODS calculateTotalPrice FOR DETERMINE ON MODIFY
      IMPORTING keys FOR Travel~calculateTotalPrice.
      
    METHODS setInitialStatus FOR DETERMINE ON MODIFY
      IMPORTING keys FOR Travel~setInitialStatus.
      
    METHODS acceptTravel FOR MODIFY
      IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
      
    METHODS rejectTravel FOR MODIFY
      IMPORTING keys FOR ACTION Travel~rejectTravel RESULT result.
      
    METHODS deductDiscount FOR MODIFY
      IMPORTING keys FOR ACTION Travel~deductDiscount RESULT result.
      
    METHODS get_instance_features FOR INSTANCE FEATURES
      IMPORTING keys REQUEST requested_features FOR Travel RESULT result.
ENDCLASS.

CLASS lhc_travel IMPLEMENTATION.
  METHOD validateAgency.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( AgencyID )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    DATA agencies TYPE SORTED TABLE OF /dmo/agency WITH UNIQUE KEY agency_id.
    
    agencies = CORRESPONDING #( travels DISCARDING DUPLICATES MAPPING agency_id = AgencyID EXCEPT * ).
    DELETE agencies WHERE agency_id IS INITIAL.
    
    IF agencies IS NOT INITIAL.
      SELECT FROM /dmo/agency FIELDS agency_id
        FOR ALL ENTRIES IN @agencies
        WHERE agency_id = @agencies-agency_id
        INTO TABLE @DATA(valid_agencies).
    ENDIF.
    
    LOOP AT travels INTO DATA(travel).
      IF travel-AgencyID IS INITIAL OR NOT line_exists( valid_agencies[ agency_id = travel-AgencyID ] ).
        APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
        APPEND VALUE #( %tky = travel-%tky
                        %msg = new_message_with_text(
                                 severity = if_abap_behv_message=>severity-error
                                 text     = 'Invalid Agency ID' )
                        %element-AgencyID = if_abap_behv=>mk-on
                      ) TO reported-travel.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.
  
  METHOD validateCustomer.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( CustomerID )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    DATA customers TYPE SORTED TABLE OF /dmo/customer WITH UNIQUE KEY customer_id.
    
    customers = CORRESPONDING #( travels DISCARDING DUPLICATES MAPPING customer_id = CustomerID EXCEPT * ).
    DELETE customers WHERE customer_id IS INITIAL.
    
    IF customers IS NOT INITIAL.
      SELECT FROM /dmo/customer FIELDS customer_id
        FOR ALL ENTRIES IN @customers
        WHERE customer_id = @customers-customer_id
        INTO TABLE @DATA(valid_customers).
    ENDIF.
    
    LOOP AT travels INTO DATA(travel).
      IF travel-CustomerID IS INITIAL OR NOT line_exists( valid_customers[ customer_id = travel-CustomerID ] ).
        APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
        APPEND VALUE #( %tky = travel-%tky
                        %msg = new_message_with_text(
                                 severity = if_abap_behv_message=>severity-error
                                 text     = 'Invalid Customer ID' )
                        %element-CustomerID = if_abap_behv=>mk-on
                      ) TO reported-travel.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.
  
  METHOD validateDates.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( BeginDate EndDate )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    LOOP AT travels INTO DATA(travel).
      IF travel-EndDate < travel-BeginDate.
        APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
        APPEND VALUE #( %tky = travel-%tky
                        %msg = new_message_with_text(
                                 severity = if_abap_behv_message=>severity-error
                                 text     = 'End Date cannot be before Begin Date' )
                        %element-BeginDate = if_abap_behv=>mk-on
                        %element-EndDate   = if_abap_behv=>mk-on
                      ) TO reported-travel.
      ELSEIF travel-BeginDate < cl_abap_context_info=>get_system_date( ).
        APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
        APPEND VALUE #( %tky = travel-%tky
                        %msg = new_message_with_text(
                                 severity = if_abap_behv_message=>severity-error
                                 text     = 'Begin Date cannot be in the past' )
                        %element-BeginDate = if_abap_behv=>mk-on
                      ) TO reported-travel.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.
  
  METHOD calculateTotalPrice.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( BookingFee TotalPrice )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
      <travel>-TotalPrice = <travel>-BookingFee * '1.1'. " Add 10% markup
    ENDLOOP.
    
    MODIFY ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        UPDATE FIELDS ( TotalPrice )
        WITH CORRESPONDING #( travels ).
  ENDMETHOD.
  
  METHOD setInitialStatus.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( Status )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    DELETE travels WHERE Status IS NOT INITIAL.
    CHECK travels IS NOT INITIAL.
    
    MODIFY ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        UPDATE FIELDS ( Status )
        WITH VALUE #( FOR travel IN travels ( %tky = travel-%tky
                                              Status = 'N' ) ). " New
  ENDMETHOD.
  
  METHOD acceptTravel.
    MODIFY ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        UPDATE FIELDS ( Status )
        WITH VALUE #( FOR key IN keys ( %tky = key-%tky
                                        Status = 'A' ) )
      FAILED failed
      REPORTED reported.
      
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        ALL FIELDS WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    result = VALUE #( FOR travel IN travels ( %tky = travel-%tky
                                              %param = travel ) ).
  ENDMETHOD.
  
  METHOD rejectTravel.
    MODIFY ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        UPDATE FIELDS ( Status )
        WITH VALUE #( FOR key IN keys ( %tky = key-%tky
                                        Status = 'X' ) )
      FAILED failed
      REPORTED reported.
      
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        ALL FIELDS WITH CORRESPONDING #( keys )
      RESULT DATA(travels).
      
    result = VALUE #( FOR travel IN travels ( %tky = travel-%tky
                                              %param = travel ) ).
  ENDMETHOD.
  
  METHOD deductDiscount.
    DATA: discount_percent TYPE /dmo/discount.
    
    LOOP AT keys INTO DATA(key).
      discount_percent = key-%param-discount_percent.
      
      READ ENTITIES OF zi_travel IN LOCAL MODE
        ENTITY Travel
          FIELDS ( TotalPrice )
          WITH VALUE #( ( %tky = key-%tky ) )
        RESULT DATA(travels).
        
      DATA(travel) = travels[ 1 ].
      DATA(new_price) = travel-TotalPrice * ( 1 - discount_percent / 100 ).
      
      MODIFY ENTITIES OF zi_travel IN LOCAL MODE
        ENTITY Travel
          UPDATE FIELDS ( TotalPrice )
          WITH VALUE #( ( %tky = key-%tky
                          TotalPrice = new_price ) )
        FAILED failed
        REPORTED reported.
        
      READ ENTITIES OF zi_travel IN LOCAL MODE
        ENTITY Travel
          ALL FIELDS WITH VALUE #( ( %tky = key-%tky ) )
        RESULT travels.
        
      result = VALUE #( BASE result ( %tky = key-%tky
                                      %param = travels[ 1 ] ) ).
    ENDLOOP.
  ENDMETHOD.
  
  METHOD get_instance_features.
    READ ENTITIES OF zi_travel IN LOCAL MODE
      ENTITY Travel
        FIELDS ( Status )
        WITH CORRESPONDING #( keys )
      RESULT DATA(travels)
      FAILED failed.
      
    result = VALUE #( FOR travel IN travels
                       ( %tky = travel-%tky
                         %features-%action-acceptTravel = COND #( WHEN travel-Status = 'A' THEN if_abap_behv=>fc-o-disabled
                                                                    ELSE if_abap_behv=>fc-o-enabled )
                         %features-%action-rejectTravel = COND #( WHEN travel-Status = 'X' THEN if_abap_behv=>fc-o-disabled
                                                                    ELSE if_abap_behv=>fc-o-enabled )
                       ) ).
  ENDMETHOD.
ENDCLASS.

Step 6: Create Projection Behavior

projection;
strict ( 2 );

define behavior for ZC_TRAVEL alias Travel
{
  use create;
  use update;
  use delete;
  
  use action acceptTravel;
  use action rejectTravel;
  use action deductDiscount;
  
  use association _Agency { create; }
  use association _Customer { create; }
}

Step 7: Create Service Definition

@EndUserText.label: 'Travel Service'
define service ZUI_TRAVEL_SRV {
  expose ZC_TRAVEL as Travel;
  expose /DMO/I_Agency as Agency;
  expose /DMO/I_Customer as Customer;
}

Step 8: Create Service Binding

In ADT (ABAP Development Tools):

  1. Right-click on Service Definition → New Service Binding
  2. Name: ZUI_TRAVEL_O4
  3. Binding Type: OData V4 - UI
  4. Service Definition: ZUI_TRAVEL_SRV
  5. Click Publish

Testing Your OData Service

Method 1: SAP Gateway Client

GET /sap/opu/odata4/sap/zui_travel_o4/srvd/sap/zui_travel_srv/0001/Travel

Response:
{
  "@odata.context": "$metadata#Travel",
  "value": [
    {
      "TravelID": "00000001",
      "AgencyID": "070001",
      "CustomerID": "000001",
      "BeginDate": "2026-03-15",
      "EndDate": "2026-03-22",
      "BookingFee": 50.00,
      "TotalPrice": 1500.00,
      "CurrencyCode": "USD",
      "Description": "Spring Break Trip",
      "Status": "N"
    }
  ]
}

Method 2: Fiori Elements Preview

In Service Binding, click Preview to launch Fiori Elements app automatically.

Advanced RAP Features

Draft Handling

define behavior for ZI_TRAVEL alias Travel
persistent table ztravel_db
draft table ztravel_draft
lock master
authorization master ( instance )
etag master LocalLastChangedAt
{
  // Enable draft
  draft action Edit;
  draft action Activate;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;
  
  // Rest of behavior definition
}

Side Effects

side effects {
  field AgencyID affects field AgencyName;
  field CustomerID affects field CustomerName;
  field BookingFee affects field TotalPrice;
  action acceptTravel affects field Status;
}

Virtual Elements

@EndUserText.label: 'Travel with Calculated Fields'
define root view entity ZI_TRAVEL
  as select from ztravel_db
{
  key travel_id as TravelID,
  // ... other fields
  
  @ObjectModel.virtualElement: true
  @ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_TRAVEL_CALCULATIONS'
  cast( '' as abap.int4 ) as DaysUntilTravel,
  
  @ObjectModel.virtualElement: true
  @ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_TRAVEL_CALCULATIONS'
  cast( '' as abap.char(20) ) as PriorityLevel
}

RAP vs Traditional Development: Comparison

AspectTraditional (SEGW)RAP
Development Time2-4 weeks2-5 days
Lines of Code2000-5000500-1000
Files Created15-205-8
Learning CurveModerateSteep initially
MaintainabilityMediumHigh
SAP SupportLegacyActive
Cloud ReadyNoYes

Common RAP Patterns

Master-Detail Pattern

define behavior for ZI_TRAVEL alias Travel
{
  // Parent entity
  association _Booking { create; with draft; }
}

define behavior for ZI_BOOKING alias Booking
{
  // Child entity
  update;
  delete;
  association _Travel { with draft; }
}

Number Range Pattern

early numbering;

define behavior for ZI_TRAVEL alias Travel
{
  create;
  // TravelID will be assigned during create
  field ( numbering : managed, readonly ) TravelID;
}

Authorization Pattern

define behavior for ZI_TRAVEL alias Travel
authorization master ( global )
{
  // Global authorization check
}

CLASS zcl_travel_auth DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_abap_behv_authorization.
ENDCLASS.

CLASS zcl_travel_auth IMPLEMENTATION.
  METHOD if_abap_behv_authorization~get_global_authorizations.
    " Check authority-check objects
    AUTHORITY-CHECK OBJECT 'ZTRAVEL'
      ID 'ACTVT' FIELD requested_authorizations-%create.
    " ... set result accordingly
  ENDMETHOD.
ENDCLASS.

Best Practices

✅ DO

  • Use managed scenario for standard CRUD operations
  • Leverage determinations for calculated fields
  • Implement validations for data integrity
  • Use draft for complex forms
  • Apply side effects for UI reactivity
  • Keep behavior logic in behavior implementation class
  • Use projections to separate concerns
  • Annotate for Fiori Elements to auto-generate UI

❌ AVOID

  • Mixing business logic in projections
  • Direct database updates (bypass RAP framework)
  • Complex calculations in CDS views
  • Ignoring ETag for concurrency control
  • Not handling failed and reported structures
  • Exposing internal views directly

Debugging RAP Applications

Enable Debugging

" In behavior implementation class
BREAK-POINT.

" Or use managed debugging in ADT
// Right-click → Debug As → ABAP Application

Check Transaction Buffer

READ ENTITIES OF zi_travel IN LOCAL MODE
  ENTITY Travel
    ALL FIELDS WITH CORRESPONDING #( keys )
  RESULT DATA(travels).

" Inspect travels table in debugger

Conclusion

RAP transforms ABAP development from a code-heavy, manual approach to ametadata-driven, framework-guided paradigm.

Key takeaways:

  • RAP is SAP's strategic direction for S/4HANA and BTP
  • Dramatically reduces development time and complexity
  • Built-in support for OData, draft, validation, authorization
  • Seamless integration with Fiori Elements
  • Cloud-ready and future-proof

If you're building new applications on S/4HANA, RAP should be your default choice.

The future of ABAP development is declarative, metadata-driven, and RAP-powered.

About the Author: Yogesh Pandey is a passionate developer and consultant specializing in SAP technologies and full-stack development.