RAP Actions and Functions: Implementing Business Logic

Category
RAP
Published
Author
Johannes

Actions and Functions are the means in RAP to implement custom business logic beyond the standard CRUD operations. This article explains the differences and shows when to use which concept.

Action vs. Function: The Difference

AspectActionFunction
PurposeModifies data (side effects)Reads/calculates data (no side effects)
HTTP MethodPOSTGET
TransactionalYes, part of the transactionNo, read-only
IdempotentNoYes
ExamplesCancel, Release, CopyCalculate price, Check availability

When to Use Action vs. Function?

  • Action: When the operation changes the state of the entity or system
  • Function: When only a value is calculated or information is retrieved without changing data

Bound vs. Unbound (Static)

AspectBound (Instance)Unbound (Static)
ContextBound to an instanceWithout instance context
InvocationOn selected rowVia button in toolbar
KeysInstance keys availableNo automatic keys
Use Case”Cancel this travel""Release all open travels”

Instance-bound Action

An instance-bound action is executed on a specific instance:

Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
{
// Instance-bound Action without parameters
action cancel result [1] $self;
// Instance-bound Action with result
action accept result [1] $self;
// Instance-bound Action returning multiple instances
action copyTravel result [0..*] $self;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS cancel FOR MODIFY
IMPORTING keys FOR ACTION Travel~cancel RESULT result.
METHODS accept FOR MODIFY
IMPORTING keys FOR ACTION Travel~accept RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD cancel.
" Read current data
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelID Status )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
" Only open travels can be cancelled
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>)
WHERE Status = 'O'.
" Set status to Cancelled
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = <travel>-%tky
Status = 'X' " Cancelled
) ).
ENDLOOP.
" Return result
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels_result).
result = VALUE #( FOR travel IN travels_result (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
METHOD accept.
" Set status to Accepted
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
Status = 'A' " Accepted
) ).
" Return updated data
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.
ENDCLASS.

Result Cardinality

CardinalityMeaning
result [1] $selfExactly one instance returned (the same entity)
result [0..1] $selfOptionally one instance
result [0..*] $selfAny number of instances
result [1] EntityNameOne instance of a different entity
(no result)No return value

Static (Unbound) Action

Static actions are not bound to an instance and are typically used for mass operations:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Static Action without parameters
static action releaseAllOpen;
// Static Action with result
static action createFromTemplate result [1] $self;
// Static Action with Global Feature Control
static action ( features: global ) massUpdate;
// For Global Feature Control
static features;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS releaseAllOpen FOR MODIFY
IMPORTING keys FOR ACTION Travel~releaseAllOpen.
METHODS createFromTemplate FOR MODIFY
IMPORTING keys FOR ACTION Travel~createFromTemplate RESULT result.
METHODS get_global_features FOR GLOBAL FEATURES
IMPORTING REQUEST requested_features FOR Travel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD releaseAllOpen.
" Find all open travels
SELECT * FROM ztravel
WHERE status = 'O'
INTO TABLE @DATA(open_travels).
" Set status to Released
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN open_travels (
TravelUUID = travel-travel_uuid
Status = 'R' " Released
) ).
ENDMETHOD.
METHOD createFromTemplate.
" Create new travel from template
DATA(template_id) = 'DEFAULT'.
" Load template (simplified example)
SELECT SINGLE * FROM ztravel_template
WHERE template_id = @template_id
INTO @DATA(template).
" Create new travel
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description )
WITH VALUE #( (
%cid = 'NEW_TRAVEL'
CustomerID = template-customer_id
BeginDate = cl_abap_context_info=>get_system_date( )
EndDate = cl_abap_context_info=>get_system_date( ) + 7
Description = 'Created from template'
) )
MAPPED DATA(mapped).
" Return created entity
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH VALUE #( ( %tky = mapped-travel[ 1 ]-%tky ) )
RESULT DATA(created_travel).
result = VALUE #( (
%cid_ref = 'NEW_TRAVEL'
%param = created_travel[ 1 ]
) ).
ENDMETHOD.
METHOD get_global_features.
" Check if user has authorization for mass update
DATA(has_auth) = check_mass_update_auth( ).
result = VALUE #(
%action-massUpdate = COND #(
WHEN has_auth = abap_true
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
).
ENDMETHOD.
ENDCLASS.

Function: Calculations Without Side Effects

Functions are ideal for calculations and queries that do not change data:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Instance-bound Function
function calculatePrice result [1] ZA_Price;
// Function with multiple return values
function getAvailability result [1] ZA_Availability;
// Static Function
static function getExchangeRates result [0..*] ZA_ExchangeRate;
// Function with parameters
function simulateDiscount
parameter ZA_DiscountInput
result [1] ZA_DiscountResult;
}

Abstract Entity for Return Values

Functions often require abstract entities for structured return values:

define abstract entity ZA_Price
{
TravelUUID : sysuuid_x16;
NetPrice : abap.dec( 15, 2 );
TaxAmount : abap.dec( 15, 2 );
TotalPrice : abap.dec( 15, 2 );
Currency : abap.cuky;
}
define abstract entity ZA_Availability
{
TravelUUID : sysuuid_x16;
IsAvailable : abap_boolean;
SeatsLeft : abap.int4;
NextAvailable: abap.dats;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS calculatePrice FOR READ
IMPORTING keys FOR FUNCTION Travel~calculatePrice RESULT result.
METHODS getAvailability FOR READ
IMPORTING keys FOR FUNCTION Travel~getAvailability RESULT result.
METHODS getExchangeRates FOR READ
IMPORTING keys FOR FUNCTION Travel~getExchangeRates RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD calculatePrice.
" Read travel data
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TotalPrice Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Calculate price (simplified)
DATA(net_price) = <travel>-TotalPrice * '0.84'.
DATA(tax_amount) = <travel>-TotalPrice * '0.16'.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_price(
TravelUUID = <travel>-TravelUUID
NetPrice = net_price
TaxAmount = tax_amount
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getAvailability.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate MaxSeats BookedSeats )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
DATA(seats_left) = <travel>-MaxSeats - <travel>-BookedSeats.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_availability(
TravelUUID = <travel>-TravelUUID
IsAvailable = COND #( WHEN seats_left > 0 THEN abap_true ELSE abap_false )
SeatsLeft = seats_left
NextAvailable = COND #( WHEN seats_left = 0
THEN <travel>-BeginDate + 30
ELSE <travel>-BeginDate )
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getExchangeRates.
" Static Function - no keys available
" Read exchange rates from system
SELECT * FROM tcurr
WHERE kurst = 'M'
AND gdatu >= @( cl_abap_context_info=>get_system_date( ) )
INTO TABLE @DATA(rates)
UP TO 100 ROWS.
result = VALUE #( FOR rate IN rates (
%param = VALUE za_exchangerate(
SourceCurrency = rate-fcurr
TargetCurrency = rate-tcurr
ExchangeRate = rate-ukurs
ValidFrom = rate-gdatu
)
) ).
ENDMETHOD.
ENDCLASS.

Action with Input Parameters

Actions can accept structured input parameters:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Action with abstract parameter
action rejectWithReason
parameter ZA_RejectionReason
result [1] $self;
// Action with multiple parameters
action reschedule
parameter ZA_RescheduleInput
result [1] $self;
// Action with Deep Structure
action addParticipants
parameter ZA_ParticipantList
result [1] $self;
}

Abstract Entity for Input Parameters

define abstract entity ZA_RejectionReason
{
ReasonCode : abap.char( 2 );
ReasonText : abap.string( 255 );
NotifyCustomer : abap_boolean;
}
define abstract entity ZA_RescheduleInput
{
NewBeginDate : abap.dats;
NewEndDate : abap.dats;
UpdatePrice : abap_boolean;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS rejectWithReason FOR MODIFY
IMPORTING keys FOR ACTION Travel~rejectWithReason RESULT result.
METHODS reschedule FOR MODIFY
IMPORTING keys FOR ACTION Travel~reschedule RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD rejectWithReason.
" Read parameters from keys
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Rejection reason from parameter
DATA(reason_code) = <key>-%param-ReasonCode.
DATA(reason_text) = <key>-%param-ReasonText.
DATA(notify) = <key>-%param-NotifyCustomer.
" Save status and reason
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status RejectionReason )
WITH VALUE #( (
%tky = <key>-%tky
Status = 'X' " Rejected
RejectionReason = reason_text
) ).
" Optional: Notify customer
IF notify = abap_true.
" Send email...
send_rejection_notification(
travel_key = <key>-%tky
reason = reason_text
).
ENDIF.
ENDLOOP.
" Return updated data
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 reschedule.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" New dates from parameter
DATA(new_begin) = <key>-%param-NewBeginDate.
DATA(new_end) = <key>-%param-NewEndDate.
DATA(update_price) = <key>-%param-UpdatePrice.
" Update dates
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( BeginDate EndDate )
WITH VALUE #( (
%tky = <key>-%tky
BeginDate = new_begin
EndDate = new_end
) ).
" Optional: Recalculate price
IF update_price = abap_true.
recalculate_price( <key>-%tky ).
ENDIF.
ENDLOOP.
" Return result
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.
ENDCLASS.

Actions in the Projection

Actions must be exposed in the projection:

projection implementation in class zbp_c_travel unique;
strict ( 2 );
define behavior for ZC_Travel alias Travel
{
use action cancel;
use action accept;
use action rejectWithReason;
use action reschedule;
use function calculatePrice;
use function getAvailability;
}

UI Integration with Fiori Elements

Button Placement

define root view entity ZC_Travel
provider contract transactional_query
as projection on ZI_Travel
{
@UI.lineItem: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'cancel', label: 'Cancel' },
{ type: #FOR_ACTION, dataAction: 'accept', label: 'Release' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rejectWithReason', label: 'Reject' }
]
key TravelUUID,
// Header Actions
@UI.fieldGroup: [{ qualifier: 'Actions',
type: #FOR_ACTION, dataAction: 'reschedule', label: 'Reschedule' }]
Status
}

Action with Dialog

For actions with input parameters, a dialog is automatically generated:

define abstract entity ZA_RejectionReason
{
@UI.defaultValue: 'OT'
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonCode', element: 'Code' }}]
ReasonCode : abap.char( 2 );
@UI.multiLineText: true
ReasonText : abap.string( 255 );
@UI.defaultValue: 'true'
NotifyCustomer : abap_boolean;
}

Factory Actions

Factory actions create new instances based on existing ones:

define behavior for ZI_Travel alias Travel
{
// Factory Action creates new instance
factory action copyTravel [1];
// Factory Action with parameter
factory action createFromBooking parameter ZA_BookingRef [1];
}

Implementation

METHOD copyTravel.
" Read original data
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Create copy with new %cid
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description TotalPrice Currency )
WITH VALUE #( (
%cid = |COPY_{ sy-index }|
CustomerID = <travel>-CustomerID
BeginDate = <travel>-BeginDate
EndDate = <travel>-EndDate
Description = |Copy of { <travel>-TravelID }|
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
) )
MAPPED DATA(mapped).
" mapped contains %cid and %tky of the new instance
APPEND VALUE #(
%cid_ref = keys[ sy-index ]-%cid_ref
%key = mapped-travel[ 1 ]-%key
) TO mapped-travel.
ENDLOOP.
ENDMETHOD.

Best Practices

  1. Choose Action vs. Function correctly: Actions for modifications, Functions for calculations.

  2. Define Result Type: Always specify result [n] $self so the UI gets updated.

  3. Use IN LOCAL MODE: Bypass authorization checks for internal calls.

  4. Error Handling: Return errors via FAILED and REPORTED:

METHOD cancel.
" Validation
IF travel-Status <> 'O'.
APPEND VALUE #(
%tky = travel-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Only open travels can be cancelled'
)
) TO reported-travel.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
RETURN.
ENDIF.
ENDMETHOD.
  1. Combine with Feature Control: Disable actions when they are not applicable.

  2. Transactional Consistency: All changes are committed only at SAVE.

  • RAP Basics - Fundamentals of the RESTful ABAP Programming Model
  • Feature Control & Side Effects - Dynamic UI control
  • Draft Handling in RAP - Intermediate storage with Drafts