Singleton Pattern dans RAP : Gestion des paramètres applicatifs

Catégorie
RAP
Publié
Auteur
Johannes

Le Singleton Pattern dans RAP permet de créer des Business Objects qui n’ont qu’une seule instance. C’est idéal pour les paramètres applicatifs, configurations ou paramètres globaux qui doivent être gérés de manière centralisée.

Concept de base

Un Singleton dans le contexte RAP est un Business Object qui :

  • Ne peut avoir qu’une seule instance
  • Ne peut pas être supprimé (l’instance existe toujours)
  • Ne permet pas la création de nouvelles instances
  • N’autorise que les opérations UPDATE
BO normalBO Singleton
Nombre d’instances illimitéExactement une instance
CREATE, UPDATE, DELETEUniquement UPDATE
Clé propreClé fixe/virtuelle
Liste d’objetsUn objet global

Cas d’utilisation

Le Singleton Pattern convient pour :

  • Paramètres système : Paramètres de configuration globaux
  • Configuration applicative : Feature flags, seuils
  • Paramètres mandant : Valeurs par défaut spécifiques à l’entreprise
  • Compteurs et statistiques : Métriques applicatives
  • Dernier état : Objets “Current” (période de facturation actuelle)

Implémentation

1. Table de base de données

La table a généralement une valeur de clé fixe :

@EndUserText.label : 'Application Settings"
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #C
define table zappsettings {
key client : abap.clnt not null;
key singleton_key : abap.char(1) not null; " Toujours 'X"
max_items : abap.int4;
default_currency : abap.cuky;
enable_feature_a : abap_boolean;
enable_feature_b : abap_boolean;
last_changed_at : timestampl;
last_changed_by : abap.uname;
}

2. CDS View pour le Singleton

La différence clé est l’annotation @ObjectModel.singleton.element :

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Application Settings"
define root view entity ZI_AppSettings
as select from zappsettings
{
key singleton_key as SingletonKey,
max_items as MaxItems,
default_currency as DefaultCurrency,
enable_feature_a as EnableFeatureA,
enable_feature_b as EnableFeatureB,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy
}

3. Behavior Definition avec Singleton

La Behavior Definition déclare le Singleton avec le mot-clé singleton :

managed implementation in class zbp_i_appsettings unique;
strict ( 2 );
define behavior for ZI_AppSettings alias Settings
persistent table zappsettings
lock master
authorization master ( instance )
{
// Déclaration du Singleton
static singleton;
// Uniquement Update autorisé - pas de Create/Delete
update;
// Mapping des champs
field ( readonly ) SingletonKey;
field ( readonly ) LastChangedAt, LastChangedBy;
// Remplissage automatique des champs
mapping for zappsettings corresponding
{
SingletonKey = singleton_key;
MaxItems = max_items;
DefaultCurrency = default_currency;
EnableFeatureA = enable_feature_a;
EnableFeatureB = enable_feature_b;
LastChangedAt = last_changed_at;
LastChangedBy = last_changed_by;
}
}

4. Initialisation automatique du Singleton

Comme un Singleton doit toujours exister, il est créé automatiquement lors du premier accès. Cela se fait via une extension read :

managed implementation in class zbp_i_appsettings unique;
strict ( 2 );
define behavior for ZI_AppSettings alias Settings
persistent table zappsettings
lock master
authorization master ( instance )
{
static singleton;
update;
// Permet l'initialisation automatique
internal create;
field ( readonly ) SingletonKey;
}

5. Implémentation du Behavior

L’implémentation assure que le Singleton existe :

CLASS zbp_i_appsettings DEFINITION PUBLIC ABSTRACT FINAL
FOR BEHAVIOR OF zi_appsettings.
ENDCLASS.
CLASS zbp_i_appsettings IMPLEMENTATION.
ENDCLASS.
CLASS lhc_settings DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR Settings RESULT result.
METHODS read FOR READ
IMPORTING keys FOR READ Settings RESULT result.
ENDCLASS.
CLASS lhc_settings IMPLEMENTATION.
METHOD get_instance_authorizations.
" Tous peuvent lire et modifier (ajouter le contrôle d'autorisation ici)
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
APPEND VALUE #(
%tky = <key>-%tky
%update = if_abap_behv=>auth-allowed
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD read.
" Vérifier si le Singleton existe
SELECT SINGLE * FROM zappsettings
WHERE singleton_key = 'X"
INTO @DATA(ls_settings).
IF sy-subrc <> 0.
" Le Singleton n'existe pas - créer initialement
INSERT zappsettings FROM @( VALUE #(
singleton_key = 'X"
max_items = 100
default_currency = 'EUR"
) ).
" Relire
SELECT SINGLE * FROM zappsettings
WHERE singleton_key = 'X"
INTO @ls_settings.
ENDIF.
" Retourner le résultat
result = VALUE #( (
SingletonKey = ls_settings-singleton_key
MaxItems = ls_settings-max_items
DefaultCurrency = ls_settings-default_currency
EnableFeatureA = ls_settings-enable_feature_a
EnableFeatureB = ls_settings-enable_feature_b
LastChangedAt = ls_settings-last_changed_at
LastChangedBy = ls_settings-last_changed_by
) ).
ENDMETHOD.
ENDCLASS.

Singleton avec Draft

Pour des paramètres plus complexes, le Draft Handling peut être activé :

managed implementation in class zbp_i_appsettings unique;
strict ( 2 );
with draft;
define behavior for ZI_AppSettings alias Settings
persistent table zappsettings
draft table zappsettings_d
lock master
authorization master ( instance )
{
static singleton;
update;
internal create;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
field ( readonly ) SingletonKey;
}

La table Draft :

@EndUserText.label : 'Draft: Application Settings"
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
define table zappsettings_d {
key client : abap.clnt not null;
key singleton_key : abap.char(1) not null;
max_items : abap.int4;
default_currency : abap.cuky;
enable_feature_a : abap_boolean;
enable_feature_b : abap_boolean;
// Champs Admin Draft
"%admin" : include sych_bdl_draft_admin_inc;
}

Projection View

La Projection pour l’application Fiori :

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'App Settings"
@Metadata.allowExtensions: true
define root view entity ZC_AppSettings
provider contract transactional_query
as projection on ZI_AppSettings
{
key SingletonKey,
MaxItems,
DefaultCurrency,
EnableFeatureA,
EnableFeatureB,
LastChangedAt,
LastChangedBy
}

Projection Behavior

projection;
strict ( 2 );
use draft;
define behavior for ZC_AppSettings alias Settings
{
use update;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
}

UI-Annotation pour Object Page

Un Singleton est généralement affiché comme Object Page sans liste préalable :

@Metadata.layer: #CUSTOMER
annotate entity ZC_AppSettings with
{
@UI.facet: [
{
id: 'GeneralSettings',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: 'Paramètres généraux',
position: 10,
targetQualifier: 'General"
},
{
id: 'Features',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: 'Feature Flags',
position: 20,
targetQualifier: 'Features"
}
]
@UI.fieldGroup: [{ qualifier: 'General', position: 10 }]
MaxItems;
@UI.fieldGroup: [{ qualifier: 'General', position: 20 }]
DefaultCurrency;
@UI.fieldGroup: [{ qualifier: 'Features', position: 10 }]
EnableFeatureA;
@UI.fieldGroup: [{ qualifier: 'Features', position: 20 }]
EnableFeatureB;
}

Consommer le Singleton depuis le code

Accès EML

" Lire le Singleton
READ ENTITIES OF zi_appsettings
ENTITY Settings
ALL FIELDS WITH VALUE #( ( SingletonKey = 'X' ) )
RESULT DATA(lt_settings).
DATA(ls_settings) = lt_settings[ 1 ].
" Modifier le Singleton
MODIFY ENTITIES OF zi_appsettings
ENTITY Settings
UPDATE FIELDS ( MaxItems EnableFeatureA )
WITH VALUE #( (
SingletonKey = 'X"
MaxItems = 200
EnableFeatureA = abap_true
) )
FAILED DATA(failed)
REPORTED DATA(reported).
" Sauvegarder
COMMIT ENTITIES.

Classe Helper pour un accès simplifié

CLASS zcl_app_settings DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO zcl_app_settings,
get_max_items
RETURNING VALUE(rv_value) TYPE i,
get_default_currency
RETURNING VALUE(rv_value) TYPE waers,
is_feature_a_enabled
RETURNING VALUE(rv_enabled) TYPE abap_bool.
PRIVATE SECTION.
CLASS-DATA: go_instance TYPE REF TO zcl_app_settings.
DATA: ms_settings TYPE zi_appsettings.
METHODS constructor.
METHODS load_settings.
ENDCLASS.
CLASS zcl_app_settings IMPLEMENTATION.
METHOD get_instance.
IF go_instance IS NOT BOUND.
go_instance = NEW #( ).
ENDIF.
ro_instance = go_instance.
ENDMETHOD.
METHOD constructor.
load_settings( ).
ENDMETHOD.
METHOD load_settings.
READ ENTITIES OF zi_appsettings
ENTITY Settings
ALL FIELDS WITH VALUE #( ( SingletonKey = 'X' ) )
RESULT DATA(lt_settings).
IF lt_settings IS NOT INITIAL.
ms_settings = lt_settings[ 1 ].
ENDIF.
ENDMETHOD.
METHOD get_max_items.
rv_value = get_instance( )->ms_settings-MaxItems.
ENDMETHOD.
METHOD get_default_currency.
rv_value = get_instance( )->ms_settings-DefaultCurrency.
ENDMETHOD.
METHOD is_feature_a_enabled.
rv_enabled = get_instance( )->ms_settings-EnableFeatureA.
ENDMETHOD.
ENDCLASS.

Singleton avec entité parente

Un Singleton peut également exister en tant qu’enfant d’un autre BO :

define behavior for ZI_Project alias Project
{
create;
update;
delete;
// Chaque projet a exactement une instance Settings
association _ProjectSettings { create; }
}
define behavior for ZI_ProjectSettings alias ProjectSettings
lock dependent by _Project
authorization dependent by _Project
{
// Singleton par parent
singleton;
update;
internal create;
association _Project;
field ( readonly ) ProjectID;
}

Bonnes pratiques

RecommandationJustification
Utiliser une valeur de clé fixeAccès simple sans recherche
Utiliser internal createPermettre l’initialisation automatique
Fournir une classe HelperAccès simplifié depuis le code ABAP
Implémenter un cachePerformance lors d’accès fréquents
Vérifier les autorisationsTout le monde ne devrait pas pouvoir modifier les paramètres
Draft pour paramètres complexesMeilleure UX avec de nombreux paramètres

Résumé

Le Singleton Pattern dans RAP est la solution idéale pour :

  • Configurations globales gérées de manière centralisée
  • Feature Flags pour contrôler le comportement applicatif
  • Paramètres système qui ne doivent exister qu’une seule fois

Avec le mot-clé static singleton dans la Behavior Definition et internal create pour l’initialisation automatique, l’implémentation est simple et directe.

Sujets connexes