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):
- Right-click on Service Definition → New Service Binding
- Name:
ZUI_TRAVEL_O4 - Binding Type: OData V4 - UI
- Service Definition:
ZUI_TRAVEL_SRV - 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
| Aspect | Traditional (SEGW) | RAP |
|---|---|---|
| Development Time | 2-4 weeks | 2-5 days |
| Lines of Code | 2000-5000 | 500-1000 |
| Files Created | 15-20 | 5-8 |
| Learning Curve | Moderate | Steep initially |
| Maintainability | Medium | High |
| SAP Support | Legacy | Active |
| Cloud Ready | No | Yes |
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 debuggerConclusion
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.
