
After working on several SAP projects, I have noticed a common trend consisting in a plethora of SEGW projects full of function imports that are generally misused or unnecessary. With my experience—and after delving into the SAP documentation—I have decided to write this blog to shed some light on function imports. I will explain what they are, what they are used for, when to use them and when to avoid using them, and will do so not only for SAP SEGW projects, but also for their equivalents within the more recent SAP RAP and CAP frameworks—since I am actively diving into these modern development approaches and working on business projects that leverage them. To make this more concrete, I’ll walk through a practical example, which I’ve made available on GitHub.
Let's clear up the confusion together. So, grab your coffee, and let’s dive in !
SAP RAP (RESTful ABAP Programming Model) and SAP CAP (Cloud Application Programming Model) are the two modern frameworks from SAP designed for building applications.
Both frameworks support extending standard CRUD operations by allowing developers to define custom logic—commonly known as actions or functions. In RAP, these are introduced in behavior definitions, while in CAP, they're modeled in the service layer. This functional similarity makes it natural to discuss them together.
As a quick note for context: this blog won’t delve into the differences between static and instance-based implementations, bound and unbound actions and functions.. as those topics are beyond the scope of this post — although they might be the subject of a future blog post.
SAP RAP is SAP’s modern framework for developing OData services and business applications using ABAP. It serves the same core purpose as SAP SEGW (Service Gateway), which is to expose data and operations from SAP systems via OData.
Unlike SEGW, which relies heavily on manual coding and separate models, RAP uses a streamlined, model-driven approach with better integration into the ABAP RESTful environment, leveraging CDS (Core Data Services) to define and manage the data models in a more declarative way.
SAP CAP is SAP’s modern development framework for building services and applications, particularly optimized for the SAP Business Technology Platform (BTP). It provides a streamlined, full-stack development experience with built-in support for defining data models, service APIs, and custom logic using a combination of Core Data Services (CDS), Node.js, or Java.
While SAP RAP is more ABAP-centric and suited for on-premise or S/4HANA systems, CAP is cloud-native and ideal for developing applications in the BTP environment.
Unlike traditional approaches like SEGW or even ABAP RAP, which are deeply rooted in the ABAP stack, CAP is open and service-oriented by design. It promotes strong integration with SAP HANA, SAP Fiori, and external services through open standards like OData and REST.
Both SAP RAP and SAP CAP distinguish between standard and non-standard OData operations. Standard operations refer to the CRUD functionalities, which are automatically handled by the frameworks. In addition to CRUD, both frameworks also manage transactional behavior, draft handling, validation, and persistence with minimal configuration.
Non-standard operations, on the other hand, are used to implement custom business logic beyond CRUD, following the same conceptual model as Function Imports in the classical SEGW approach.
In both RAP and CAP, Actions and Functions represent non-standard operations that integrate custom logic into the application. Actions are used for logic that may change data or cause side effects while Functions are read-only operations that return data.
In RAP, these are defined in the behavior definition and implemented in the behavior pool class, ensuring proper transactional and authorization handling.
In CAP, they are defined in the CDS service definition, and the implementation resides in service handlers (Node.js or Java).
Both frameworks ensure these operations are fully OData V4-compliant and seamlessly consumable by Fiori Elements or other OData clients.
SAP SEGW (SAP Gateway Service Builder) is based on a flexible approach for managing CRUD operations. When creating or updating a service, the framework generates standard methods such as CREATE_ENTITY, UPDATE_ENTITY, DELETE_ENTITY, etc., which developers can redefine to incorporate more complex business logic tailored to functional requirements.
When a batch call is made from the front end, for example via a SubmitChanges operation, the grouped operations (creating entry C, updating entry A, deleting entry B, etc.) are dispatched individually in the backend. In other words, each operation is handled separately by the corresponding method (CREATE_ENTITY, UPDATE_ENTITY, etc.).
However, this approach introduces certain limitations, particularly in error handling within batch processing. Since each operation is processed independently, managing scenarios involving partial rollbacks or consistent error propagation across the batch becomes more complex.
SAP RAP and SAP CAP adopt a structured, model-based approach for managing data and business logic. Both rely on OData v4 and Core Data Services (CDS) to define entities, their relationships, and their behaviors.
CRUD operations such as CREATE and UPDATE are defined at the entity level and are designed to be handled individually, meaning they are executed on a single instance at a time. To manage more complex processing or scenarios involving multiple entities, RAP and CAP recommend the use of actions or functions, which allow business logic to be encapsulated without being directly embedded in the standard operations.
However, if no specific logic is required, it is still possible to perform batch processing (bulk update) using standard CRUD operations. That said, this approach is not recommended, as CRUD operations are intended for single-record processing, with validations, lifecycle hooks (such as before, after, check, etc.), and line-by-line error handling. Using batch processing via CRUD can lead to inconsistent behavior, difficult-to-control partial rollbacks, and a loss of control over overall business and transactional logic.
In addition, using actions offers many other advantages, such as:
A core principle in RAP and CAP is the clear separation between basic CRUD operations and business logic. This distinction enhances data consistency, code readability, and, most importantly, long-term maintainability of applications. Each action represents a clear business intent, integrated within a well-defined service structure.
In summary, SAP SEGW offers greater implementation freedom, which can be suitable for simple use cases with minimal business logic. However, it lacks a structured framework for handling complex scenarios or bulk processing. In contrast, SAP RAP and CAP enforce a more rigorous architecture, ensuring more reliable and scalable application management—both on the backend and frontend.
Now that we've covered the theory, let's explore some practical examples to see how this approach is implemented in these modern frameworks.
Below is an implementation example of how to call an action and a static function defined in SAP RAP.
In the front-end (e.g., Fiori Elements), the action setStatusToCanceled is triggered via the UI using the OData POST call to /EntitySet(...)/setStatusToCanceled. This is handled in the back-end by redefining the setStatusToCanceled method in the behavior implementation class (lhc_ZFM_C_FLIGHTS), where the logic modifies the entity’s status.
For the static function GetNumberOfDelayedFlights, it is triggered by a GET call like /EntitySet/GetNumberOfDelayedFlights, and its logic is implemented in the getnumberofdelayedflights method, returning a result structure with the computed data.
Flight Root Entity CDS View
define root view entity ZFM_C_FLIGHTS as select from ZFM_I_FLIGHTS
{
key Flight,
Origin,
Destination,
Departure,
Arrival,
Delay,
.lineItem: [{ position: 70 }, { type: #FOR_ACTION, dataAction: 'setStatusToCanceled' , label: 'Cancel flight'}]
Status
}
Behavior Definition
managed implementation in class zbp_fm_c_flights unique;
strict ( 2 );
define behavior for ZFM_C_FLIGHTS
persistent table zfm_flights
lock master
authorization master ( instance )
{
create;
update;
delete;
field ( numbering : managed, readonly ) Flight;
action setStatusToCanceled result [1] $self;
static function GetNumberOfDelayedFlights result [1] ZD_GETDELAYEDFLIGHTS_RESULT;
mapping for zfm_flights {
Flight = flight_id;
Origin = origin_code;
Destination = destination_code;
Departure = departure;
Arrival = arrival;
Delay = delay;
Status = status_code;
}
}
Behavior Projection
projection;
strict ( 2 );
define behavior for ZFM_P_FLIGHTS
{
use create;
use update;
use delete;
use action setStatusToCanceled;
use function GetNumberOfDelayedFlights;
}
Behavior Implementation Class (ABAP)
CLASS lhc_ZFM_C_FLIGHTS DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR zfm_c_flights RESULT result.
METHODS setstatustocanceled FOR MODIFY
IMPORTING keys FOR ACTION zfm_c_flights~setstatustocanceled RESULT result.
METHODS getnumberofdelayedflights FOR READ
IMPORTING keys FOR FUNCTION zfm_c_flights~getnumberofdelayedflights RESULT result.
ENDCLASS.
CLASS lhc_ZFM_C_FLIGHTS IMPLEMENTATION.
METHOD get_instance_authorizations.
ENDMETHOD.
METHOD setStatusToCanceled.
MODIFY ENTITIES OF zfm_c_flights IN LOCAL MODE
ENTITY zfm_c_flights
UPDATE FROM VALUE #( FOR key IN keys
( Flight = key-Flight
Status = 'CAN' " Cancelled
%control-Status = if_abap_behv=>mk-on ) )
FAILED failed
REPORTED reported.
"Read changed data for action result
READ ENTITIES OF zfm_c_flights IN LOCAL MODE
ENTITY zfm_c_flights
ALL FIELDS WITH
CORRESPONDING #( keys )
RESULT DATA(flights).
result = VALUE #( FOR flight IN flights
( %tky = flight-%tky
%param = flight ) ).
ENDMETHOD.
METHOD getNumberOfDelayedFlights.
DATA: ls_delayedflights TYPE zd_getdelayedflights_result.
" Read all flights where status_code = 'CAN'
SELECT COUNT( * ) FROM zfm_flights
WHERE delay > 0
INTO _delayedflights .
" Count how many were found
APPEND VALUE #( %param = CORRESPONDING #( ls_delayedflights ) ) TO result.
ENDMETHOD.
ENDCLASS.
If you’ve made it this far, it means you’re really interested in the topic — great!
To illustrate the fundamental differences between SEGW and CAP/RAP, I have implemented a more comprehensive CAP project. In this implementation, the use of an action instead of a batch update clearly proves its value through its seamless integration with Fiori Elements, as well as its ability to encapsulate business logic and handle errors effectively during mass processing.
To make things a bit more fun, here are three example use cases that I implemented in the following CAP example. Try to guess for each one whether it should be handled with a function or an action. The detailed answers are provided just below the implementation — but give it a try first!
CDS Service Definition (srv/service.cds)
using { flightManagement as fm } from '../db/schema';
service FlightsService @(path: '/flights') {
.draft.enabled
entity Flights as projection on fm.Flights actions {
action addDelay(
delay: Integer
@label: '{i18n>addDelayAction_delayParameter}' ) returns Flights; };
function getNbOfDelayedFlights() returns Integer;
}
Service Implementation (srv/service.js)
const cds = require('@sap/cds');
const TextBundle = require('@sap/textbundle').TextBundle;
module.exports = cds.service.impl(srv => {
const { Flights } = srv.entities;
// Validate and adjust flight data before update
srv.before('UPDATE', 'Flights.drafts', async (req) => {
// Recalculate departure/arrival if delay is updated
if ('delay' in req.data) {
try {
let { departure, arrival } = await this._recalculate_departure_arrival(req.data.ID, req.data.delay);
if (departure === undefined && arrival === undefined) {
req.reject(400, 'ERR_UPDATING_CAN_OR_LAN', 'delay');
}
req.data.departure = departure;
req.data.arrival = arrival;
} catch (error) {
req.reject(400, 'ERR_UPDATING_DELAY', 'delay');
}
}
});
srv.on('addDelay', async (req) => {
const locale = req.locale;
const bundle = new TextBundle('./i18n/i18n', locale);
// Loop through all flights
for (let flight of req.params) {
let { departure, arrival } = await this._recalculate_departure_arrival(flight.ID, req.data.delay);
if (departure === undefined && arrival === undefined) {
// If calculation failed, send a failure notification for this flight
req.warn({
message: bundle.getText('WARNING_UPDATING_DELAY_CAN_OR_LAN', [flight.ID])
})
continue; // Skip updating this flight
}
// Update the flight with the new delay, departure, and arrival times
const result = await UPDATE(Flights) .where({ ID: flight.ID }) .set({ delay: req.data.delay, departure, arrival });
if (result === 1) {
// Success: 1 row was affected
req.notify({
code: 1,
message: bundle.getText('SUCCESS_UPDATING_DELAY', [flight.ID, req.data.delay, departure, arrival])
});
} else {
// Failure: no rows were updated
req.error({
message: bundle.getText('FAILURE_UPDATING_DELAY', [flight.ID])
});
}
}
});
// Function: Return the number of delayed flights
srv.on('getNbOfDelayedFlights', async () => {
const result = await SELECT `count(*) as count` .from(Flights) .where `delay > 0`;
return (Array.isArray(result) && result.length > 0) ? result[0].count : 0;
});
};
You’ve probably already figured out the answers by looking at the implementation, but let me walk you through the reasoning behind each choice:
For the first use case, “Add delay to a flight”, this one was a bit of a trick — it can be neatly handled within a before UPDATE hook, making it a natural candidate for standard CRUD logic.
In contrast, the action “Add delay to a list of flights” introduces more complexity. Since it involves applying business logic across multiple records as well as front-end integration, it is best implemented as a custom action. This approach also enables the generation of a button directly in the table header, along with an input popup, improved error handling, and, at the end of the process, a summary popup listing the operations that were processed in the user interface.
It is possible to enable standard bulk deletion management in Fiori Elements, handled by the framework. This is because it can be sent in batch without any business logic, as in the application I developed. We can also see the ease of integrating the action into a Fiori Elements through annotations. Finally, with the business complexity and error handling, we get feedback regardless of whether it's a success or failure, something that would have been complicated with a batch send. This example illustrates why these new frameworks recommend actions for such cases.
Lastly, the most straightforward case, “Retrieve the number of delayed flights”, simply returns data with no side effects. This makes it a perfect fit for a function.
The complete project is available on my GitHub repository => https://github.com/ValentinCadart/FlightManagement.
In a nutshell, Function Imports, Actions, and Functions are key components across SAP SEGW, RAP, and CAP that enable you to implement custom business logic, transcending basic CRUD functionality. While each platform offers its unique features and methods of defining and invoking these operations, understanding when and how to use them is essential for building efficient, scalable SAP applications.
Sooo.. one more misuse of function imports, actions or functions and I’m calling Basis to revoke your service deployment privileges. You’ve been warned !
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
6 | |
6 | |
5 | |
4 | |
4 | |
3 | |
3 | |
3 | |
3 | |
3 |