RAP Managed vs Unmanaged : Quel scénario choisir ?

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Managed vs. Unmanaged est la décision architecturale centrale dans RAP (RESTful ABAP Programming). Elle détermine qui gère le contrôle transactionnel et les opérations CRUD : le framework RAP (Managed) ou vous-même (Unmanaged).

La question fondamentale

" Qui exécute CREATE, UPDATE, DELETE ?
" Managed : "SAP, fais-le !"
managed implementation in class zbp_i_travel unique;
" Unmanaged : "Je le fais moi-même !"
unmanaged implementation in class zbp_i_travel unique;

Scénario Managed : Le framework fait le travail

Quand utiliser Managed ?

Parfait pour :

  • Nouvelles applications (développement Greenfield)
  • Processus métier standards sans logique legacy complexe
  • Applications Fiori transactionnelles avec opérations CRUD
  • Quand vous avez besoin rapidement d’un BO fonctionnel
  • Fonctionnalité Draft (sauvegarde intermédiaire)

Non adapté pour :

  • Intégration avec du code legacy (modules fonction, BAPIs)
  • Logique transactionnelle complexe en dehors de RAP
  • Quand vous avez besoin d’un contrôle total sur les accès DB
  • Migration de Dynpro/Web Dynpro avec comportement existant

Managed : Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with draft; " Draft uniquement disponible en Managed !
define behavior for ZI_Travel alias Travel
persistent table ztravel " Le framework écrit ici automatiquement
draft table zdraft_travel " Pour les données Draft
lock master " Le framework gère les verrous
total etag LastChangedAt " Optimistic Locking via ETag
authorization master ( instance )
{
// CRUD : Seulement DÉCLARER, pas d'implémentation nécessaire !
create;
update;
delete;
// Champs : Le framework s'occupe du mapping
field ( readonly ) TravelId;
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
field ( numbering : managed ) TravelId; " Attribution automatique de numéro !
// Logique métier : C'est ici que vous implémentez
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Draft Actions : Le framework les fournit automatiquement
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
// Associations
association _Bookings { create; with draft; }
}
define behavior for ZI_Booking alias Booking
persistent table zbooking
draft table zdraft_booking
lock dependent by _Travel " Verrou du Parent
authorization dependent by _Travel
{
update;
delete;
field ( readonly ) TravelId, BookingId;
field ( numbering : managed ) BookingId;
association _Travel { with draft; }
}

Managed : Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" ✅ Implémentez UNIQUEMENT la logique métier !
" Validation : Exécutée lors de la sauvegarde
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
" Determination : Définir des valeurs automatiques
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
" Action : Opération métier
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
" ❌ NE PAS implémenter : get_global_authorizations, read, create, update, delete
" → Le framework fait ça automatiquement !
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates.
" Le framework a DÉJÀ lu les données → valider seulement
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel).
" Vérifier la règle métier
IF ls_travel-EndDate < ls_travel-BeginDate.
" Remplir la structure framework pour l'erreur
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'La date de fin doit être après la date de début"
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setStatusNew.
" Le framework a créé l'entité → définir les valeurs par défaut
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" Mettre uniquement les nouveaux (Status initial) à 'O' (Open)
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL )
( %tky = travel-%tky
Status = 'O' ) )
REPORTED DATA(reported_modify).
ENDMETHOD.
METHOD acceptTravel.
" Action = Opération métier (pas seulement un Update)
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status LastChangedAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'A"
LastChangedAt = cl_abap_context_info=>get_system_date( ) ) )
FAILED failed
REPORTED reported.
" Retourner le résultat (result [1] $self)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
ENDCLASS.

Ce que vous n’avez PAS à écrire :

  • SELECT * FROM ztravel – Le framework le fait
  • INSERT ztravel FROM ... – Le framework le fait
  • UPDATE ztravel SET ... – Le framework le fait
  • DELETE FROM ztravel – Le framework le fait
  • ❌ Gestion des verrous – Le framework le fait
  • ❌ Attribution de numéros – Le framework le fait (avec numbering : managed)

Scénario Unmanaged : Vous avez le contrôle total

Quand utiliser Unmanaged ?

Parfait pour :

  • Intégration Legacy (BAPIs, modules fonction à intégrer)
  • Logique transactionnelle complexe (commits multi-étapes)
  • Migration d’applications Dynpro/Web-Dynpro existantes
  • Quand vous avez besoin d’opérations DB spéciales (par ex. Native SQL)
  • Mécanismes de verrouillage personnalisés

Non adapté pour :

  • Prototypage rapide
  • CRUD standard sans particularités
  • Fonctionnalité Draft (non disponible en Unmanaged !)

Unmanaged : Behavior Definition

unmanaged implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
lock master
authorization master ( instance )
etag master LastChangedAt
{
// CRUD : Tout doit être implémenté !
create;
update;
delete;
// Read DOIT aussi être implémenté (contrairement à Managed !)
// Logique métier comme en Managed
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Gestion des verrous VOUS devez l'implémenter
lock ( lock_key );
}

Unmanaged : Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" ✅ Tout implémenter SOI-MÊME :
" Create : Créer de nouvelles entités
create FOR MODIFY
IMPORTING entities FOR CREATE Travel,
" Update : Modifier des entités existantes
update FOR MODIFY
IMPORTING entities FOR UPDATE Travel,
" Delete : Supprimer des entités
delete FOR MODIFY
IMPORTING keys FOR DELETE Travel,
" Read : Lire des entités
read FOR READ
IMPORTING keys FOR READ Travel RESULT result,
" Lock : Gérer les verrous
lock FOR LOCK
IMPORTING keys FOR LOCK Travel,
" Feature Control
get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR Travel RESULT result,
" Logique métier (comme en Managed)
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD create.
" Extraire les données du paramètre entities
DATA lt_travel TYPE TABLE FOR CREATE zi_travel.
lt_travel = entities.
" Attribution de numéro (MANUEL, car unmanaged !)
LOOP AT lt_travel ASSIGNING FIELD-SYMBOL(<fs_travel>).
" Obtenir le numéro de la série de numéros
TRY.
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number(
nr_range_nr = '01"
object = 'ZTRAVEL"
).
CATCH cx_number_ranges INTO DATA(lx_nr).
" Gérer l'erreur
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
CONTINUE.
ENDTRY.
" Définir les valeurs par défaut
<fs_travel>-CreatedBy = sy-uname.
<fs_travel>-CreatedAt = cl_abap_context_info=>get_system_date( ).
<fs_travel>-Status = 'O'.
" Écrire dans DB (MANUEL !)
INSERT ztravel FROM @( CORRESPONDING #( <fs_travel> ) ).
IF sy-subrc = 0.
" Retourner le mapping réussi
APPEND VALUE #(
%cid = <fs_travel>-%cid
TravelId = <fs_travel>-TravelId
) TO mapped-travel.
ELSE.
" Erreur
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
" Extraire les champs à mettre à jour
LOOP AT entities INTO DATA(ls_entity).
" %control vérifie quels champs doivent être modifiés
IF ls_entity-%control-Status = if_abap_behv=>mk-on.
" Status a été modifié → DB-Update
UPDATE ztravel
SET status = @ls_entity-Status,
last_changed_by = @sy-uname,
last_changed_at = @cl_abap_context_info=>get_system_date( )
WHERE travel_id = @ls_entity-TravelId.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_entity-%tky
%fail-cause = if_abap_behv=>cause-not_found
) TO failed-travel.
ENDIF.
ENDIF.
" Autres champs de manière analogue...
IF ls_entity-%control-Description = if_abap_behv=>mk-on.
UPDATE ztravel SET description = @ls_entity-Description
WHERE travel_id = @ls_entity-TravelId.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD delete.
" Supprimer de la DB
DELETE FROM ztravel
WHERE travel_id IN ( SELECT TravelId FROM @keys AS k ).
IF sy-dbcnt < lines( keys ).
" Pas tous supprimés → Erreur
LOOP AT keys INTO DATA(ls_key).
SELECT SINGLE @abap_true FROM ztravel
WHERE travel_id = @ls_key-TravelId
INTO @DATA(lv_exists).
IF lv_exists = abap_true.
" Existe encore → n'a pas pu être supprimé
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD read.
" Lire les données de la DB
SELECT * FROM ztravel
FOR ALL ENTRIES IN @keys
WHERE travel_id = @keys-TravelId
INTO TABLE @DATA(lt_db_travel).
" Convertir en structure RAP
result = CORRESPONDING #( lt_db_travel MAPPING TO ENTITY ).
ENDMETHOD.
METHOD lock.
" Verrouiller l'objet de verrouillage (via ENQUEUE)
LOOP AT keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZTRAVEL"
EXPORTING
mode_ztravel = 'E"
mandt = sy-mandt
travel_id = ls_key-TravelId
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Verrouillage échoué
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD get_instance_features.
" Feature Control (comme en Managed)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
result = VALUE #( FOR travel IN lt_travel
( %tky = travel-%tky
%features-%action-acceptTravel = COND #(
WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled
)
)
).
ENDMETHOD.
METHOD validateDates.
" Identique à Managed
" ... (voir exemple Managed ci-dessus)
ENDMETHOD.
METHOD setStatusNew.
" Identique à Managed
" ... (voir exemple Managed ci-dessus)
ENDMETHOD.
METHOD acceptTravel.
" Identique à Managed
" ... (voir exemple Managed ci-dessus)
ENDMETHOD.
ENDCLASS.

Ce que vous devez écrire VOUS-MÊME :

  • SELECT * FROM ztravel – Vous devez lire
  • INSERT ztravel FROM ... – Vous devez écrire
  • UPDATE ztravel SET ... – Vous devez mettre à jour
  • DELETE FROM ztravel – Vous devez supprimer
  • ✅ Gestion des verrous – Vous devez verrouiller/déverrouiller
  • ✅ Attribution de numéros – Vous devez appeler les séries de numéros

Comparaison : Managed vs. Unmanaged

AspectManagedUnmanaged
Implémentation CRUDAutomatique par le frameworkImplémentation manuelle
Accès DBFrameworkVous-même (SELECT, INSERT, etc.)
Attribution de numérosfield ( numbering : managed )Manuel via cl_numberrange_runtime
Gestion des verrousAutomatiqueENQUEUE/DEQUEUE manuel
Support Draft✅ Oui (out-of-the-box)❌ Non
Effort de développement🟢 Faible (logique métier uniquement)🔴 Élevé (tout soi-même)
Flexibilité🔴 Limitée (règles du framework)🟢 Maximum (contrôle total)
Intégration Legacy🔴 Difficile🟢 Facile (BAPIs etc.)
Optimisation des performances🔴 Limitée🟢 Contrôle total
Idéal pourNouvelles apps CloudMigration Legacy
Courbe d’apprentissage🟢 Douce (moins de code)🔴 Abrupte (beaucoup de code)

Scénario hybride : Managed save + Unmanaged side effects

Problème : Vous voulez utiliser Managed, mais avez besoin d’une logique personnalisée après la sauvegarde (par ex. appeler une API externe).

Solution : Hook save_modified dans le scénario Managed :

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with additional save; " ← Activer le hook
define behavior for ZI_Travel alias Travel
persistent table ztravel
{
create;
update;
delete;
// ...
}

Implémentation :

CLASS lsc_travel DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS:
" save_modified : APRÈS le save du framework, AVANT le commit final
save_modified REDEFINITION,
" cleanup_finalize : APRÈS le COMMIT (ou ROLLBACK)
cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_travel IMPLEMENTATION.
METHOD save_modified.
" Le framework a DÉJÀ écrit dans la DB (mais pas encore commité)
" Maintenant vous pouvez implémenter des side-effects :
" Exemple : Pour chaque nouveau Travel envoyer un email
IF create-travel IS NOT INITIAL.
LOOP AT create-travel INTO DATA(ls_created).
" Appeler l'API Email (voir /email-sending/)
TRY.
cl_email_sender=>send_notification(
recipient = '[email protected]"
subject = |Nouveau voyage { ls_created-TravelId } créé|
body = |Voyage du { ls_created-BeginDate } au { ls_created-EndDate }|
).
CATCH cx_send_req_bcs INTO DATA(lx_email).
" Journaliser l'erreur, mais NE PAS interrompre la transaction
cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_email ) )->save( ).
ENDTRY.
ENDLOOP.
ENDIF.
" Exemple : Appeler une API externe pour les mises à jour
IF update-travel IS NOT INITIAL.
LOOP AT update-travel INTO DATA(ls_updated).
" Appel HTTP vers un système externe (voir /http-client/)
DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( ... ).
" ... Envoyer la requête HTTP
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD cleanup_finalize.
" APRÈS COMMIT ou ROLLBACK
" Ici vous pouvez implémenter la logique de nettoyage
" (par ex. supprimer des fichiers temporaires, fermer des connexions)
ENDMETHOD.
ENDCLASS.

Quand utiliser ?

  • Managed pour les opérations CRUD standard + DB
  • save_modified pour les side-effects (Email, APIs externes, Logging)
  • Toutes les fonctionnalités du framework (Draft, Numbering, etc.) restent disponibles

Arbre de décision

┌─────────────────────────────────────────────────┐
│ Nouvelle application (Greenfield) ? │
└──┬────────────────────────────────────────┬─────┘
│ Oui │ Non
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ CRUD standard suffit ? │ │ Intégrer système legacy ? │
└──┬──────────────────────┬───┘ └──┬──────────────────────┬───┘
│ Oui │ Non │ Oui │ Non
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ MANAGED │ │ Opérations │ │UNMANAGED │ │ Transaction │
│ │ │ DB complexes?│ │ │ │ spéciale ? │
│ ✅ │ └──┬───────┬───┘ │ ✅ │ └──┬───────┬───┘
└──────────┘ │ Oui │ Non └──────────┘ │ Oui │ Non
▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│UNMANAGED │ │ MANAGED + │ │UNMANAGED │ │ MANAGED │
│ │ │save_modified│ │ │ │ │
│ ✅ │ │ ✅ │ │ ✅ │ │ ✅ │
└──────────┘ └────────────┘ └──────────┘ └──────────┘

Migration : De Unmanaged vers Managed

Scénario : Vous avez un BO Unmanaged et voulez migrer vers Managed.

Étapes :

  1. Adapter la Behavior Definition :
" Avant :
unmanaged implementation in class zbp_i_travel unique;
" Après :
managed implementation in class zbp_i_travel unique;
" En plus :
define behavior for ZI_Travel alias Travel
persistent table ztravel " ← Ajouter
// draft table zdraft_travel ← Optionnel : Activer Draft
lock master
{
create;
update;
delete;
field ( numbering : managed ) TravelId; " ← Au lieu de l'attribution manuelle
// ...
}
  1. Nettoyer la Behavior Implementation :
" Avant (Unmanaged) : Toutes les méthodes
CLASS lhc_travel DEFINITION ...
METHODS:
create FOR MODIFY ...,
update FOR MODIFY ...,
delete FOR MODIFY ...,
read FOR READ ...,
lock FOR LOCK ...,
validateDates FOR VALIDATE ...,
// etc.
" Après (Managed) : Garder uniquement la logique métier
CLASS lhc_travel DEFINITION ...
METHODS:
" ❌ create, update, delete, read, lock → SUPPRIMER !
" ✅ Seulement la logique métier :
validateDates FOR VALIDATE ...,
setStatusNew FOR DETERMINE ...,
acceptTravel FOR MODIFY ...
  1. Supprimer les accès DB :
" ❌ Avant (Unmanaged) :
METHOD create.
INSERT ztravel FROM @( CORRESPONDING #( entities ) ).
" ...
ENDMETHOD.
" ✅ Après (Managed) : Supprimer complètement la méthode !
" Le framework fait l'INSERT automatiquement
  1. Attribution de numéros :
" ❌ Avant (Unmanaged) :
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number( ... ).
" ✅ Après (Managed) :
" Dans BDEF : field ( numbering : managed ) TravelId;
" → Le framework attribue automatiquement depuis la séquence de clés `persistent table ztravel`

Remarques importantes / Bonnes pratiques

  • Par défaut = Managed : Utilisez Managed, sauf si vous avez une bonne raison pour Unmanaged
  • Draft nécessite Managed : La fonctionnalité Draft est UNIQUEMENT disponible en Managed
  • Unmanaged pour Legacy : Si vous devez intégrer des BAPIs/FuBas → Unmanaged
  • Hybride possible : managed + with additional save pour les side-effects
  • Performance : Managed n’est PAS plus lent – le framework est optimisé
  • Testing : Managed est plus facile à tester (moins de code = moins d’erreurs)
  • Objets de verrouillage : En Unmanaged, vous devez créer vous-même les objets de verrouillage (SE11 : ENQUEUE_*)
  • Séries de numéros : En Unmanaged, vous devez gérer vous-même les séries de numéros (SNRO)
  • Transactions : Unmanaged donne un contrôle total sur COMMIT WORK et ROLLBACK
  • Migration : De Unmanaged → Managed est plus laborieux que l’inverse
  • Documentation : Justifiez la décision Unmanaged (pour les futurs développeurs)
  • IN LOCAL MODE : À utiliser dans les DEUX scénarios pour le code d’implémentation Behavior
  • Utiliser EML : Même en Unmanaged, vous devriez utiliser EML pour les accès BO (voir EML Guide)

Ressources supplémentaires