Skip to main content

Après avoir travaillé sur plusieurs projets SAP, j’ai remarqué une tendance récurrente : une multitude de projets SEGW remplis de fonction imports qui sont généralement mal utilisés ou inutiles. Fort de mon expérience — et après avoir étudié la documentation SAP — j’ai décidé d’écrire ce blog afin de faire la lumière sur les fonction imports. J’expliquerai ce qu’ils sont, à quoi ils servent, quand les utiliser et quand il vaut mieux les éviter. Je le ferai non seulement pour les projets SAP SEGW, mais aussi pour leurs équivalents dans les frameworks plus récents que sont SAP RAP et CAP — car je m’investis activement dans ces approches de développement modernes et je travaille sur des projets métiers qui les exploitent.
Pour rendre cela plus concret, je présenterai un exemple pratique, que j’ai mis à disposition sur GitHub.

Éclaircissons cette confusion ensemble. Alors, attrapez votre café et plongeons dans le sujet !

Les nouveaux frameworks de SAP

SAP RAP (RESTful ABAP Programming Model) et SAP CAP (Cloud Application Programming Model) sont deux frameworks modernes de SAP conçus pour la création d’applications.

Les deux frameworks permettent d’étendre les opérations CRUD standard en permettant aux développeurs de définir des logiques personnalisées — communément appelées actions ou fonctions. Dans RAP, ces logiques sont introduites dans les définitions de comportements, tandis que dans CAP, elles sont modélisées dans la couche de service. Cette similarité fonctionnelle rend naturel de les aborder ensemble.

Une petite précision pour le contexte : ce blog ne rentrera pas dans les différences entre les implémentations statiques et basées sur des instances, ainsi que les actions et fonctions liées et non liées… car ces sujets dépassent le cadre de cet article — bien qu’ils puissent faire l’objet d’un futur blog.

Contexte SAP RAP

SAP RAP est le framework moderne de SAP pour développer des services OData et des applications métier en utilisant ABAP. Il sert le même objectif de base que SAP SEGW (Service Gateway), qui est d’exposer les données et les opérations des systèmes SAP via OData.

Contrairement à SEGW, qui repose fortement sur la programmation manuelle et des modèles séparés, RAP utilise une approche rationalisée et basée sur des modèles, avec une meilleure intégration dans l’environnement ABAP RESTful, en exploitant CDS (Core Data Services) pour définir et gérer les modèles de données de manière plus déclarative.

Contexte SAP CAP

SAP CAP est le framework moderne de SAP pour la création de services et d’applications, particulièrement optimisé pour la SAP Business Technology Platform (BTP). Il offre une expérience de développement simplifiée, de bout en bout, avec un support intégré pour définir des modèles de données, des API de services et de la logique personnalisée à l’aide de Core Data Services (CDS), Node.js ou Java.

Alors que SAP RAP est plus centré sur ABAP et adapté aux systèmes sur site ou S/4HANA, CAP est natif du cloud et idéal pour développer des applications dans l’environnement BTP.

Contrairement aux approches traditionnelles comme SEGW ou même ABAP RAP, qui sont profondément ancrées dans la pile ABAP, CAP est ouvert et orienté services par conception. Il favorise une forte intégration avec SAP HANA, SAP Fiori et des services externes via des standards ouverts tels que OData et REST.

Opérations standard vs non-standard dans RAP et CAP

Les deux frameworks SAP RAP et SAP CAP distinguent les opérations standard des opérations non-standard dans OData. Les opérations standard font référence aux fonctionnalités CRUD, qui sont gérées automatiquement par les frameworks. En plus des opérations CRUD, les deux frameworks gèrent également le comportement transactionnel, la gestion des brouillons, la validation et la persistance avec une configuration minimale.

Les opérations non-standard, quant à elles, sont utilisées pour implémenter des logiques métier personnalisées au-delà du CRUD, suivant le même modèle conceptuel que les Function Imports dans l’approche classique SEGW.

Actions et Fonctions dans RAP et CAP

Dans RAP et CAP, les Actions et Fonctions représentent des opérations non-standard qui intègrent de la logique personnalisée dans l’application.
Les Actions sont utilisées pour des logiques qui peuvent modifier les données ou entraîner des effets secondaires, tandis que les Fonctions sont des opérations en lecture seule qui retournent des données.

Dans RAP, ces éléments sont définis dans la définition de comportement et implémentés dans la classe de pool de comportement, assurant ainsi une gestion correcte des transactions et des autorisations.

Dans CAP, ils sont définis dans la définition de service CDS, et l’implémentation réside dans des handlers de services (Node.js ou Java).

Les deux frameworks assurent que ces opérations sont entièrement conformes à OData V4 et peuvent être consommées sans problème par Fiori Elements ou d’autres clients OData.

Différence fondamentale entre SEGW et SAP RAP/CAP

SAP SEGW (SAP Gateway Service Builder) repose sur une approche flexible pour la gestion des opérations CRUD. Lors de la création ou de la mise à jour d’un service, le framework génère des méthodes standard telles que CREATE_ENTITY, UPDATE_ENTITY, DELETE_ENTITY, etc., que les développeurs peuvent redéfinir pour incorporer une logique métier plus complexe répondant aux exigences fonctionnelles.

Lorsqu’un appel de batch est effectué depuis le front-end, par exemple via une opération SubmitChanges, les opérations groupées (création de l’entrée C, mise à jour de l’entrée A, suppression de l’entrée B, etc.) sont envoyées individuellement en back-end. En d’autres termes, chaque opération est traitée séparément par la méthode correspondante (CREATE_ENTITY, UPDATE_ENTITY, etc.).

Cependant, cette approche introduit certaines limitations, notamment en matière de gestion des erreurs dans le traitement par lot. Étant donné que chaque opération est traitée indépendamment, gérer des scénarios impliquant des rollback partiels ou une propagation cohérente des erreurs à travers le lot devient plus complexe.

SAP RAP et SAP CAP adoptent une approche structurée, basée sur des modèles, pour la gestion des données et de la logique métier. Les deux reposent sur OData v4 et Core Data Services (CDS) pour définir les entités, leurs relations et leurs comportements.

Les opérations CRUD comme CREATE et UPDATE sont définies au niveau de l’entité et sont conçues pour être traitées individuellement, ce qui signifie qu’elles sont exécutées sur une seule instance à la fois. Pour gérer des traitements plus complexes ou des scénarios impliquant plusieurs entités, RAP et CAP recommandent l’utilisation d’actions ou de fonctions, qui permettent d’encapsuler la logique métier sans l’intégrer directement dans les opérations standard.

Cependant, si aucune logique spécifique n’est requise, il est toujours possible de réaliser un traitement par lot (mise à jour en masse) en utilisant les opérations CRUD standard. Cela dit, cette approche n’est pas recommandée, car les opérations CRUD sont destinées au traitement des enregistrements individuels, avec des validations, des hooks de cycle de vie (comme avant, après, vérification, etc.), et un traitement des erreurs ligne par ligne. Utiliser le traitement par lot via CRUD peut entraîner un comportement incohérent, des rollbacks partiels difficiles à contrôler, et une perte de contrôle sur la logique métier et transactionnelle globale.

De plus, l’utilisation des actions présente de nombreux autres avantages, tels que :

  • Génération automatique d’interface : Les actions sont automatiquement exposées sous forme de boutons ou d’éléments de menu dans les interfaces Fiori Elements. Elles peuvent être déclenchées sur plusieurs sélections (multi-sélection) avec un comportement UX cohérent, et permettent de nommer explicitement des processus métier spécifiques (par exemple, « Valider tout », « Envoyer les factures »).
  • Contrôle précis sur la logique métier : Une action encapsule clairement la logique métier, séparée des opérations CRUD génériques. Vous pouvez facilement inclure des validations personnalisées, des règles complexes, des appels à d’autres services, et plus encore.
  • Gestion robuste des transactions et des erreurs : Les actions permettent une gestion centralisée des exceptions, le retour de messages utilisateurs structurés, et le support des rollbacks globaux lorsque nécessaire.

Un principe fondamental dans RAP et CAP est la séparation claire entre les opérations CRUD de base et la logique métier. Cette distinction améliore la cohérence des données, la lisibilité du code et, surtout, la maintenabilité à long terme des applications. Chaque action représente une intention métier claire, intégrée dans une structure de service bien définie.

En résumé, SAP SEGW offre une plus grande liberté d’implémentation, ce qui peut être adapté pour des cas d’utilisation simples avec une logique métier minimale. Cependant, il manque un cadre structuré pour gérer des scénarios complexes ou des traitements par lot. En revanche, SAP RAP et CAP imposent une architecture plus rigoureuse, assurant une gestion des applications plus fiable et évolutive — tant en back-end qu’en front-end.

Maintenant que nous avons couvert la théorie, explorons quelques exemples pratiques pour voir comment cette approche est implémentée dans ces frameworks modernes.

Exemple d’implémentation RAP

Voici un exemple d’implémentation montrant comment appeler une action et une fonction statique définies dans SAP RAP.

Dans le front-end (par exemple, Fiori Elements), l’action setStatusToCanceled est déclenchée via l’interface utilisateur à l’aide d’un appel OData POST vers /EntitySet(…)/setStatusToCanceled. Cela est traité dans le back-end en redéfinissant la méthode setStatusToCanceled dans la classe d’implémentation du comportement (lhc_ZFM_C_FLIGHTS), où la logique modifie le statut de l’entité.

Pour la fonction statique GetNumberOfDelayedFlights, elle est déclenchée par un appel GET comme /EntitySet/GetNumberOfDelayedFlights, et sa logique est implémentée dans la méthode getnumberofdelayedflights, retournant une structure de résultat avec les données calculées.

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.

Exemple d’implémentation CAP

Si tu es arrivé jusqu’ici, cela signifie que tu es vraiment intéressé par le sujet — super !

Pour illustrer les différences fondamentales entre SEGW et CAP/RAP, j’ai mis en place un projet CAP plus complet. Dans cette implémentation, l’utilisation d’une action plutôt que d’une mise à jour par lot prouve clairement sa valeur grâce à son intégration transparente avec Fiori Elements, ainsi que sa capacité à encapsuler la logique métier et à gérer efficacement les erreurs pendant les traitements en masse.

Pour rendre les choses un peu plus intéressantes, voici trois cas d’utilisation que j’ai implémentés dans l’exemple CAP suivant. Essayez de deviner pour chaque cas s’il devrait être géré avec une fonction ou une action. Les réponses détaillées sont fournies juste en dessous de l’implémentation — mais essayez d’abord !

  • Ajouter un retard à un vol – Cela met à jour le retard du vol et ajuste l’heure d’arrivée et/ou de départ en fonction du statut du vol.
  • Ajouter un retard à une liste de vols – Les mêmes règles s’appliquent, mais maintenant tu boucles à travers plusieurs vols, recalculant les heures, les mettant à jour, et affichant éventuellement des messages de succès ou d’avertissement.
  • Récupérer le nombre de vols retardés – Retourne le nombre de vols actuellement retardés.

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;
    });
};

Vous avez probablement déjà deviné les réponses en observant l’implémentation, mais permettez moi de vous expliquer la logique derrière chaque choix :

Pour le premier cas d’utilisation, “Ajouter un retard à un vol”, c’était un peu un piège — cela peut être facilement géré dans un hook before UPDATE, ce qui en fait un candidat naturel pour la logique CRUD standard.

En revanche, l’action “Ajouter un retard à une liste de vols” introduit plus de complexité. Puisqu’elle implique l’application de logique métier sur plusieurs enregistrements ainsi que l’intégration avec le front-end, il est préférable de l’implémenter en tant qu’action personnalisée. Cette approche permet également de générer un bouton directement dans l’en-tête du tableau, accompagné d’une fenêtre contextuelle pour la saisie, d’une gestion des erreurs améliorée, et à la fin du processus, d’une fenêtre contextuelle récapitulant les opérations qui ont été traitées dans l’interface utilisateur.

Il est possible d’activer la gestion standard de la suppression en masse dans Fiori Elements, gérée par le framework. Cela est possible car cela peut être envoyé en lot sans aucune logique métier, comme dans l’application que j’ai développée. Nous pouvons également constater la facilité d’intégration de l’action dans Fiori Elements via des annotations. Enfin, avec la complexité métier et la gestion des erreurs, nous recevons des retours, qu’il s’agisse de succès ou d’échec, ce qui aurait été compliqué avec un envoi par lot. Cet exemple illustre pourquoi ces nouveaux frameworks recommandent d’utiliser des actions pour de tels cas.

Enfin, le cas le plus simple, “Récupérer le nombre de vols retardés”, retourne simplement des données sans effets secondaires. Cela en fait un choix parfait pour une fonction.

Le projet complet est disponible sur mon dépôt GitHub => https://github.com/ValentinCadart/FlightManagement.

Conclusion

En résumé, les Function Imports, Actions, et Functions sont des éléments clés dans SAP SEGW, RAP, et CAP qui permettent d’implémenter des logiques métier personnalisées, allant au-delà des fonctionnalités CRUD de base. Bien que chaque plateforme offre ses caractéristiques uniques et ses méthodes pour définir et invoquer ces opérations, comprendre quand et comment les utiliser est essentiel pour créer des applications SAP efficaces et évolutives.

Alors… une erreur de plus dans l’utilisation des function imports, actions ou functions, et je vais appeler Basis pour révoquer vos privilèges de déploiement de service. Vous avez été averti !

Valentin Cadart

Author Valentin Cadart

More posts by Valentin Cadart