Prérequis techniques : environnement S/4 Hana On premise ou S/4 Hana – Private Cloud edition.
Dans ce blog, l’objectif est de créer une application Fiori à l’aide du framework RAP, sur la base d’une BAPI existante, tout en respectant les directives SAP pour atteindre l’objectif « Clean Core ».
Cas d’utilisation : Pour cette présentation, nous allons chercher à savoir si les livraisons ont des unités de manutention existantes. Si c’est le cas, nous devons pouvoir supprimer ces unités de manutention.
En ABAP Standard, nous pouvons utiliser la BAPI : BAPI_HU_DELETE_FROM_DEL.
Nous allons maintenant voir comment intégrer cela dans un objet RAP, en suivant les recommandations de SAP pour le Clean Core.
Je reviendrai également sur l’utilisation du cadre RAP avec l’utilisation d’ABAP Standard, sans objectif de clean core ou de cloud compliance, pour vous montrer quelques différences.
ABAP RAP, dans le contexte d’ABAP CLOUD
Jetons un bref coup d’œil au cadre ABAP RAP et à sa structure pour fournir les principales bases de ce blog, le tout dans le contexte du développement ABAP Cloud.
Je vous recommande toutefois de lire le livre SAPPRESS « ABAP RESTful Application Programming Model – A comprehensive guide » (1), qui explique parfaitement tous les détails de ce framework.
Avant de plonger dans la structure du framework, revenons sur la notion de LUW dans le cloud, car elle aura un impact majeur sur notre développement.
Comme évoqué en détail dans ce Blog SAP (2), s’il était auparavant recommandé d’utiliser IN UPDATE TASK lorsqu’on souhaitait écrire dans la base de données SAP en utilisant le standard ABAP, ce n’est plus le cas avec ABAP ( IN UPDATE TASK permettait entre autres d’éviter d’écrire dans les bases de données lorsqu’il y avait des commits implicites et d’augmenter les performances, mais je vous invite à lire l’article cité ci-dessus) . Ce n’est plus le cas avec ABAP cloud, puisque les performances de la base de données HANA permettent de s’en passer. Cependant, cela implique que les écritures dans la base de données ne se fassent qu’à la fin du SAP LUW (lors de la séquence de sauvegarde, voir plus loin dans l’explication du framework RAP), car en cas de commit implicite, des modifications de la base de données seraient effectuées et nous ne respecterions plus les règles ACID (encore une fois, voir l’article cité plus haut).
C’est là qu’intervient la structure du cadre RAP :
- Phase d’interaction
- Phase de sauvegarde
Pendant la phase d’interaction, les données sont placées dans une mémoire tampon, où elles peuvent être enrichies et vérifiées.
Lors de la séquence de sauvegarde, les données de la mémoire tampon ne peuvent plus être modifiées et sont sauvegardées dans la base de données (c’est la fin du SAP LUW).
Telles sont les règles suivies par le cadre RAP dans le cas du développement ABAP cloud :
Les violations suivantes entraîneront toujours une erreur d’exécution dans un contexte RAP (même si ABAP CLOUD est utilisé ou STANDARD ABAP) :
- Utilisation explicite des instructions COMMIT WORK ou ROLLBACK WORK, car la gestion des transactions et le commit de la base de données sont exclusivement gérés par RAP.
- Appel de la tâche de mise à jour en arrière-plan (bgPF) dans la phase d’interaction ou la phase de sauvegarde anticipée.
Certaines autres opérations n’entraînent des erreurs d’exécution que si la définition du comportement RAP est mise en œuvre dans ABAP for Cloud Development et que le mode strict est appliqué :
- L’invocation des contrôles d’autorisation dans la phase de sauvegarde anticipée ou tardive.
- Modification des opérations de base de données dans la phase d’interaction ou la phase de sauvegarde anticipée.
- Lancement d’événements commerciaux au cours de la phase d’interaction ou de la phase de sauvegarde anticipée.
- les validations (implicites ou explicites) de la base de données pour la connexion principale à la base de données dans la phase de sauvegarde tardive.
NOTE POUR L’UTILISATION DE ABAP STANDARD : En regardant ces règles, si vous n’êtes pas dans l’optique du clean core, vous pouvez développer en STANDARD ABAP, et effectuer un commit implicite pendant la phase tardive par exemple, ou utiliser l’instruction IN UPDATE TASK, mais dans mon cas je préfère suivre les règles plus strictes de ABAP RAP FRAMEWORK même si je développe en STANDARD ABAP.
Les 2 scénarios possibles en utilisant le framework RAP
Il existe deux types de scénarios pour la mise en œuvre du processus : managed ou unmanaged.
Dans le scénario managed, le système SAP génère automatiquement les méthodes et l’objet de gestion. Dans le scénario unmanaged, tout doit être développé à la main.
- Le cas d’utilisation du scénario managed est le suivant : Création d’une nouvelle application à partir de zéro (approche greenfield) – typiquement, les données seront stockées de manière persistante dans de nouvelles tables Z. Il faut ensuite améliorer la norme générée par le biais de validations, de déterminations et d’actions.
Ce n’est pas le cas ici, car nous voulons stocker/modifier nos données dans des tables standard à l’aide d’objets hérités (une BAPI).
- Le cas d’utilisation du scénario unmanaged est le suivant : Refonte d’une application existante (approche brownfield). L’application existante fournit une API qui peut et doit être utilisée pour l’implémentation de la phase d’interaction et de la séquence de sauvegarde en raison de ses caractéristiques (structure, découplage de la logique métier et des aspects techniques).
Dans ce cas, nous devrions tout développer, de la phase d’interaction à la séquence de sauvegarde. Nous ne sommes pas intéressés par cette implémentation, car nous ne disposons pas d’une API pour gérer la mise en mémoire tampon pour nos besoins, et nous aimerions utiliser le standard SAP pour la partie interaction.
- Dans ce cas, il existe un troisième scénario : Scénario managed with unmanaged save : Utilisation de parties d’une application existante (par exemple, une BAPI) dans la séquence de sauvegarde UNIQUEMENT.
NOTE : il existe également un scénario managed with additional save : l’ajout d’une logique de mise à jour supplémentaire à la séquence de sauvegarde (par exemple, dans le journal de l’application) qui n’affecte pas l’objet commercial actuel. Mais nous ne l’utiliserons pas.
Comment utiliser la BAPI dans le contexte du ABAP Cloud ?
SAP fournit les objets qu’il souhaite exposer pour le développement dans le cloud en les whitlistant. Si ces objets ne sont pas exposés, SAP peut également proposer leurs successeurs (par exemple, dans le cas des BAPI, si elles ne sont pas exposées, un comportement RAP peut être proposé à la place).
Ceci peut être vérifié soit directement dans l’ADT (3), soit sur GitHub (4).
Dans notre cas, la BAPI souhaitée n’est ni exposée ni remplacée.
Conformément aux directives SAP (5) (6), nous allons créer un wrapper autour de cette BAPI, et whitlister ce wrapper. Lorsque SAP proposera un successeur à cette BAPI, il sera possible de supprimer le wrapper et d’utiliser l’objet successeur à la place.
Pour créer un wrapper autour d’une BAPI, suivez ce tutoriel (7).
Ici, nous avons créé la classe ZCL_WRAP_DEL_HU_FAC (et ZCL_WRAP_DEL_HU en utilisant ZIF_WRAP_BAP_HU_DEL), qui sera utilisée pour supprimer les unités de manutention.
INTERFACE zif_wrap_bapi_hu_del
PUBLIC .
"Delivery Number
TYPES de_number TYPE BAPIDELICIOUSDELIVERY-DELIVERY_NUMBER.
"HU
TYPES de_hu TYPE BAPIHUKEY-HU_EXID.
"Return table
TYPES de_returns TYPE bapirettab.
METHODS delete
IMPORTING iv_number type de_number
iv_hu type de_hu
RETURNING VALUE(rt_results) TYPE de_returns.
ENDINTERFACE.
CLASS zcl_wrap_del_hu DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zif_wrap_bapi_hu_del.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_wrap_del_hu IMPLEMENTATION.
METHOD zif_wrap_bapi_hu_del~delete.
CALL FUNCTION 'BAPI_HU_DELETE_FROM_DEL'
EXPORTING
delivery = iv_number
hukey = iv_hu
TABLES
return = rt_results.
.
ENDMETHOD.
ENDCLASS.
CLASS zcl_wrap_del_hu_fac DEFINITION
PUBLIC
FINAL
CREATE PRIVATE .
PUBLIC SECTION.
CLASS-METHODS create_instance
RETURNING VALUE(result) TYPE REF TO zif_wrap_bapi_hu_del.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS constructor.
ENDCLASS.
CLASS zcl_wrap_del_hu_fac IMPLEMENTATION.
METHOD create_instance.
result = NEW zcl_wrap_del_hu( ).
ENDMETHOD.
METHOD constructor.
ENDMETHOD.
ENDCLASS.
Nous avons publié le wrapper et son interface en tant qu’API whitlisté.
Il peut donc être utilisé dès à présent dans ABAP pour le développement du cloud.
Définition et implémentation de l’objet RAP
Maintenant que nous avons whitlisté notre objet à utiliser dans notre objet rap, nous allons commencer à créer notre objet métier.
Nous avons créé un package qui utilise « ABAP for cloud development ».
1) Création des CDS
- Nous créons une vue racine CDS qui stocke le numéro de livraison et indique si au moins une unité de manutention est liée à cette livraison. (Nous avons créé la vue CDS privée ZP_HU pour trouver la première ligne de chaque unité de manutention pour une livraison, puis nous joignons cette vue à l’entité racine CDS pour vérifier si au moins une unité de manutention existe).
Notez ici que nous utilisons des CDS publiées par SAP, qui remplace les tables standards de type vekp).
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'CDS internal use HU'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}
define view entity ZP_HU
as select from I_DeliveryDocument
left outer join I_HandlingUnitHeader on I_HandlingUnitHeader.HandlingUnitPackingObjectKey = I_DeliveryDocument.DeliveryDocument
{
key I_DeliveryDocument.DeliveryDocument as deliveryDocument,
min(I_HandlingUnitHeader.HandlingUnitInternalID) as hu,
min(I_HandlingUnitHeader.HandlingUnitPackingObjectKey) as objectkey
}
group by
I_DeliveryDocument.DeliveryDocument
having
count(*) = 1
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Handling Units for Delivery'
define root view entity ZI_DELIV_HU as select from I_DeliveryDocument
left outer join ZP_HU on ZP_HU.deliveryDocument = I_DeliveryDocument.DeliveryDocument
{
key I_DeliveryDocument.DeliveryDocument as DeliveryDocument,
case
when $projection.DeliveryDocument = ZP_HU.objectkey
then cast ( 'X' as boole_d )
else cast ( ' ' as boole_d )
end as HUAssigned
}
- Nous créons la vue de projection associée, qui sera utilisée pour créer le service web. La vue de projection réduit l’objet métier à l’usage simple qui en sera fait dans l’application Fiori, alors que le cds ZI_DELIV_HU pourrait être réutilisé par d’autres applications.
(Ici, il n’y a pas de différences entre les 2 cds, mais il aurait pu y en avoir).
@EndUserText.label: 'Projection view ZI_DELIV_HU'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZC_DELIV_HU as projection on ZI_DELIV_HU
{
key DeliveryDocument,
HUAssigned
}
NOTE : nous aurions pu créer des cd pour une valeur ajoutée supplémentaire.
2) Nous créons la définition du comportement
- Nous spécifions d’abord le mode strict pour toutes les vérifications syntaxiques nécessaires, et la méthode d’autorisation utilisée (vérification de l’autorisation).
- Ensuite, nous spécifions les opérations qui peuvent être utilisées : dans ce cas, nous souhaitons seulement mettre à jour notre objet de gestion pour supprimer le drapeau d’affectation HU, donc nous spécifions « update ». (nous reviendrons sur l’interne).
- Ensuite, nous enrichissons la norme générée automatiquement en ajoutant une validation dans laquelle nous utilisons la BAPI en mode test pour vérifier que tout se passe bien.
- Nous enrichissons également la norme d’une action « unassign » qui supprimera le drapeau HUs attribué au CDS. Cette action utilisera l’opération UPDATE en interne pour modifier l’entité, et l’utilisateur ne devrait pas être en mesure de modifier lui-même l’indicateur : il doit utiliser le bouton « unassign » (l’explication de la raison pour laquelle interne est spécifiée dans la définition : UPDATE ne pourrait être utilisé qu’en interne, dans l’implémentation de l’objet d’entreprise RAP).
- enfin, nous spécifions Unmanaged save pour utiliser la BAPI dans la séquence de sauvegarde et sauvegarder les données dans la base de données.
managed with unmanaged save implementation in class zbp_i_deliv_hu unique;
strict ( 2 );
define behavior for ZI_DELIV_HU //alias <alias_name>
//persistent table <???>
lock master
authorization master ( instance )
//etag master <field_name>
{
// create;
internal update;
// delete;
action unassign;
validation validateDelivery on save { create; update; }
}
3) Créer l’implémentation
- Création de l’action
L’EML est utilisé pour mettre à jour l’objet en supprimant l’indicateur de validité. Si l’utilisateur tente de supprimer le témoin d’un objet déjà dépourvu de témoin (sans UM), un message d’erreur s’affiche.
- Création de la validation (au moment de la sauvegarde)
Exécutez l’interface BAPI en mode test pour vérifier que tout va bien. Si ce n’est pas le cas, un message d’erreur s’affiche.
- Création d’une séquence de sauvegarde :
La BAPI est appelée dans la dernière phase, à la fin du SAP LUW. Une fois la séquence de sauvegarde terminée, le cadre RAP se charge de la valider.
CLASS lhc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR zi_deliv_hu RESULT result.
METHODS unassign FOR MODIFY
IMPORTING keys FOR ACTION zi_deliv_hu~unassign.
METHODS validateDelivery FOR VALIDATE ON SAVE
IMPORTING keys FOR zi_deliv_hu~validateDelivery.
ENDCLASS.
CLASS lhc_ZI_DELIV_HU IMPLEMENTATION.
METHOD get_instance_authorizations.
* Here you check authorizations
ENDMETHOD.
METHOD unassign.
* We read the current value of the key instances
READ ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_sh_for_unassign).
* We loop at the finding values to check if instances have HUs qssigned or no, If yes we report an error
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign_err>) WHERE huassigned = ' '.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %tky = <ls_sh_for_unassign_err>-%tky
%msg = me->new_message( severity = if_abap_behv_message=>severity-error
id = 'ZHU_MSG'
number = '002'
v2 = |{ <ls_sh_for_unassign_err>-deliverydocument ALPHA = OUT }| ) ) ).
ENDLOOP.
DELETE lt_sh_for_unassign WHERE huassigned = ' '.
* We modify entities to delete the flag and un-assign the HUs linked to the delivery
LOOP AT lt_sh_for_unassign ASSIGNING FIELD-SYMBOL(<ls_sh_for_unassign>).
MODIFY ENTITIES OF zi_deliv_hu IN LOCAL MODE
ENTITY zi_deliv_hu
UPDATE FROM
VALUE #( FOR sh IN lt_sh_for_unassign (
%key = sh-%key
deliverydocument = sh-deliverydocument
huassigned = ' '
%control-huassigned = if_abap_behv=>mk-on ) ).
ENDLOOP.
ENDMETHOD.
METHOD validateDelivery.
* Validate here the data
* For example, if the BAPI have a test mode it is be possible to execute it in test mode and in case of error report errors
* However here it's not the case for BAPI_HU_DELETE_FROM_DEL
* It's still possible to add our own validation
ENDMETHOD.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS save_modified REDEFINITION.
METHODS cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_ZI_DELIV_HU IMPLEMENTATION.
METHOD save_modified.
* We loop at the updated instances with deleted flag
LOOP AT update-zi_deliv_hu INTO DATA(ls_update).
* In this loop we find all the HU linked to the delivery
SELECT HandlingUnitExternalID
FROM I_HandlingUnitHeader
WHERE HandlingUnitPackingObjectKey = @ls_update-DeliveryDocument
INTO TABLE @DATA(lt_hu_to_delete).
* Loop to all HUs assigned to delivery
LOOP AT lt_hu_to_delete INTO DATA(ls_hu_to_delete).
* Call the bapi to delete the HU using the wrapper for the cloud
DATA(lt_hu_delete) = zcl_wrap_del_hu_fac=>create_instance( )->delete(
EXPORTING
iv_number = ls_update-DeliveryDocument
iv_hu = ls_hu_to_delete-HandlingUnitExternalID ).
* At this stage we can't report errors, it should has been verify that everything is fine before the save sequence
* But in case of error we still give an information message
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
FOR r IN lt_hu_delete WHERE ( type = 'E' )
( deliverydocument = ls_update-deliverydocument
%msg = me->new_message(
id = r-id
number = r-number
severity = if_abap_behv_message=>severity-information
v1 = r-message_v1
v2 = r-message_v2
v3 = r-message_v3
v4 = r-message_v4 ) ) ).
IF reported IS INITIAL.
reported-zi_deliv_hu = VALUE #( BASE reported-zi_deliv_hu
( %key = ls_update-%key
%msg = me->new_message( severity = if_abap_behv_message=>severity-success
id = 'ZHU_MSG'
number = '002'
v1 = |{ ls_update-deliverydocument ALPHA = OUT }| ) ) ).
ENDIF.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
METHOD cleanup_finalize.
* Clean after save
ENDMETHOD.
ENDCLASS.
4) Nous créons une définition de consommation de comportement pour réduire les opérations CRUD à l’application si nécessaire (ici je crée la consommation mais elle est identique à ZI_DELIV_HU ).
projection;
strict ( 2 );
define behavior for ZC_DELIV_HU //alias <alias_name>
{
use action unassign;
}
5) Nous créons les metadata, en ajoutant le bouton supplémentaire pour les unités de manutention non attribuées.
@Metadata.layer: #CORE
annotate entity ZC_DELIV_HU
with
{
@UI: { lineItem: [{ position: 10, importance: #HIGH },
{ type: #FOR_ACTION, dataAction: 'unassign', label: 'Unassign' }],
selectionField: [{ position: 10 }] }
DeliveryDocument;
@UI.lineItem: [{ position: 20 },
{ label : 'HU(s) assigned ?' }]
@UI.selectionField: [{ position: 20 }]
@UI.identification:[ { label: 'HU(s) assigned ?' } ]
@EndUserText.label: 'HU(s) assigned ?'
HUAssigned;
}
6) Créer le service de définition
@EndUserText.label: 'Service def deliv hu'
define service ZUI_DELIV_HU {
expose ZC_DELIV_HU;
}
7) créer le service binding
8) Il ne reste plus qu’à créer l’application Fiori avec VS Code ou SAP BAS (8) en utilisant les éléments SAP Fiori.
Conclusion
Voici comment je crée des applications Fiori à l’aide du framework Rap, en tenant compte des directives SAP pour le cloud.
(1) Learn the ABAP RESTful Application Programming Model | – by SAP PRESS (sap-press.com)
(2) https://blogs.sap.com/2022/12/05/the-sap-luw-in-abap-cloud/
(4) https://raw.githubusercontent.com/SAP/abap-atc-cr-cv-s4hc/main/src/objectReleaseInfo_PCE2022.json
(5) https://www.sap.com/documents/2023/05/b0bd8ae6-747e-0010-bca6-c68f7e60039b.html
(7) https://developers.sap.com/tutorials/abap-s4hanacloud-purchasereq-create-wrapper.html
(8) https://blogs.sap.com/2022/10/31/fiori-development-with-vscode-and-nodejs/