Le Multitenancy permet d’exploiter une application ABAP Cloud pour plusieurs clients (tenants) sans deployer le code plusieurs fois. Pour les fournisseurs SaaS, c’est la base de solutions evolutives et rentables.
Qu’est-ce que le Multitenancy ?
Dans une architecture multi-locataires, plusieurs clients partagent la meme instance d’application, tandis que leurs donnees restent strictement separees :
| Terme | Description |
|---|---|
| Tenant | Un client ou locataire avec ses propres donnees |
| Provider Account | Compte SAP BTP du fournisseur SaaS |
| Consumer Subaccount | Subaccount BTP d’un client SaaS |
| Tenant ID | Identifiant unique pour chaque locataire |
| Tenant-aware | Code qui prend en compte le locataire actuel |
Modeles de Multitenancy sur SAP BTP
| Modele | Description | Cas d’utilisation |
|---|---|---|
| Shared Schema | Tous les tenants dans une instance de base de donnees | Standard pour ABAP Cloud |
| Separate Schema | Schema DB propre par tenant | Isolation stricte |
| Separate Instance | Instance ABAP propre par tenant | Isolation maximale |
ABAP Cloud utilise principalement le modele Shared Schema avec des tables dependantes du mandant. La separation se fait via le champ CLIENT (mandant).
Architecture d’une application SaaS multi-locataires
+-----------------------------------------------------------------------------+| SAP BTP Global Account || || +------------------------------------------------------------------------+ || | Provider Subaccount | || | +--------------------+ +-------------------------------------+ | || | | Systeme ABAP Cloud | | SaaS Provisioning Service | | || | | (Multitenant) | | - Tenant Onboarding | | || | | | | - Tenant Offboarding | | || | | - Code partage | | - Lifecycle Callbacks | | || | | - Tables partagees | +-------------------------------------+ | || | | - Mandant par | | || | | Tenant | +-------------------------------------+ | || | +--------------------+ | Service XSUAA | | || | | - Tokens JWT specifiques au tenant | | || +----------------------------+-------------------------------------+-----+ || || +------------------+ +------------------+ +------------------+ || | Consumer | | Consumer | | Consumer | || | Subaccount A | | Subaccount B | | Subaccount C | || | (Client Alpha) | | (Client Beta) | | (Client Gamma) | || | | | | | | || | Mandant : 100 | | Mandant : 200 | | Mandant : 300 | || +------------------+ +------------------+ +------------------+ |+-----------------------------------------------------------------------------+Composants principaux
| Composant | Fonction |
|---|---|
| Systeme ABAP | ABAP Environment compatible multi-locataires |
| SaaS Provisioning Service | Gere le cycle de vie des tenants |
| XSUAA | Authentification et autorisation par tenant |
| Destination Service | Configurations de connexion specifiques au tenant |
Contexte du Tenant dans ABAP Cloud
Le contexte du tenant actuel est disponible via differentes APIs :
Lire le Tenant-ID
CLASS zcl_tenant_utils DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. CLASS-METHODS: get_current_tenant_id RETURNING VALUE(rv_tenant_id) TYPE string,
get_current_tenant_zone_id RETURNING VALUE(rv_zone_id) TYPE string,
is_provider_tenant RETURNING VALUE(rv_is_provider) TYPE abap_bool.
ENDCLASS.
CLASS zcl_tenant_utils IMPLEMENTATION.
METHOD get_current_tenant_id. " Tenant-ID depuis le Security Context DATA(lo_context) = cl_abap_context_info=>get_system_context( ). rv_tenant_id = lo_context->get_attribute( 'zoneId' ). ENDMETHOD.
METHOD get_current_tenant_zone_id. " Zone-ID correspond au GUID du subaccount TRY. DATA(lo_context) = cl_abap_context_info=>get_system_context( ). rv_zone_id = lo_context->get_attribute( 'zoneId' ). CATCH cx_abap_context_info_error. " Fallback : string vide rv_zone_id = ''. ENDTRY. ENDMETHOD.
METHOD is_provider_tenant. " Verifier si le tenant actuel est le provider DATA(lv_zone_id) = get_current_tenant_zone_id( ). DATA(lv_provider_zone) = cl_abap_context_info=>get_system_context( )->get_attribute( 'providerZoneId' ).
rv_is_provider = xsdbool( lv_zone_id = lv_provider_zone ). ENDMETHOD.
ENDCLASS.Donnees dependantes du mandant
Dans ABAP Cloud, les donnees des tenants sont separees via le mandant (CLIENT). Chaque tenant recoit son propre mandant :
CLASS zcl_tenant_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: get_tenant_specific_config RETURNING VALUE(rs_config) TYPE zconfig_s.
ENDCLASS.
CLASS zcl_tenant_data IMPLEMENTATION.
METHOD get_tenant_specific_config. " Le SELECT standard lit automatiquement dependant du mandant " Le mandant est defini par le systeme en fonction du token JWT SELECT SINGLE * FROM ztenant_config INTO rs_config.
" Pas de clause WHERE pour CLIENT necessaire - c'est automatique ENDMETHOD.
ENDCLASS.CDS Views Tenant-aware
Les CDS Views sont automatiquement dependants du mandant quand la table de base de donnees sous-jacente est dependante du mandant :
Comportement standard du mandant
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Produits specifiques au tenant"define view entity ZI_Product as select from zproduct{ key product_id as ProductId, product_name as ProductName, product_price as ProductPrice, created_by as CreatedBy, created_at as CreatedAt}La clause WHERE WHERE mandt = sy-mandt est automatiquement ajoutee par le systeme. Chaque tenant ne voit que ses propres donnees.
Acces Cross-Client pour le Provider
Dans certains cas, le provider doit acceder aux donnees de tous les tenants (par ex. pour le reporting ou l’administration) :
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Tous les produits (Cross-Client)"@ClientHandling.type: #CLIENT_INDEPENDENTdefine view entity ZI_ProductCrossClient as select from zproduct{ key mandt as Client, key product_id as ProductId, product_name as ProductName, tenant_id as TenantId}Important : Les vues Cross-Client necessitent des controles d’autorisation speciaux pour eviter les fuites de donnees.
Gestion du cycle de vie
La gestion du cycle de vie des tenants comprend l’onboarding (accueillir de nouveaux clients) et l’offboarding (retirer des clients).
SaaS Provisioning Service
Le SaaS Provisioning Service appelle des callbacks lors d’evenements tenant :
+----------------------------------------------------------------+| Cycle de vie du Tenant || || +----------+ +-------------+ +-------------------------+ || | Subscribe|-->| SaaS |-->| ABAP Cloud | || | (Client) | | Provisioning| | Callback Onboarding | || +----------+ +-------------+ | - Creer le mandant | || | - Charger donnees init | || | - Setup autorisations | || +-------------------------+ || || +------------+ +-------------+ +-------------------------+ || | Unsubscribe|->| SaaS |-->| ABAP Cloud | || | (Client) | | Provisioning| | Callback Offboarding | || +------------+ +-------------+ | - Archiver les donnees | || | - Desactiver mandant | || +-------------------------+ |+----------------------------------------------------------------+Implementer le Callback Onboarding
CLASS zcl_tenant_provisioning DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_saas_provisioning_callback.
ENDCLASS.
CLASS zcl_tenant_provisioning IMPLEMENTATION.
METHOD if_saas_provisioning_callback~on_subscription. " Appele quand un nouveau tenant s'abonne DATA(lv_tenant_id) = iv_tenant_id. DATA(lv_subdomain) = iv_subdomain.
TRY. " 1. Creer la configuration initiale pour le tenant create_tenant_config( iv_tenant_id = lv_tenant_id iv_subdomain = lv_subdomain ).
" 2. Charger les donnees de base standard load_initial_master_data( lv_tenant_id ).
" 3. Configurer l'utilisateur admin setup_tenant_admin( iv_tenant_id = lv_tenant_id iv_admin_email = is_subscription_params-admin_email ).
" Signaler le succes ev_status = if_saas_provisioning_callback=>cs_status-success.
CATCH cx_root INTO DATA(lx_error). " Erreur lors de l'onboarding ev_status = if_saas_provisioning_callback=>cs_status-failed. ev_message = lx_error->get_text( ). ENDTRY. ENDMETHOD.
METHOD if_saas_provisioning_callback~on_unsubscription. " Appele quand un tenant resilie DATA(lv_tenant_id) = iv_tenant_id.
TRY. " 1. Archiver les donnees pour conservation legale archive_tenant_data( lv_tenant_id ).
" 2. Terminer les sessions actives terminate_tenant_sessions( lv_tenant_id ).
" 3. Desactiver la configuration du tenant deactivate_tenant( lv_tenant_id ).
ev_status = if_saas_provisioning_callback=>cs_status-success.
CATCH cx_root INTO DATA(lx_error). ev_status = if_saas_provisioning_callback=>cs_status-failed. ev_message = lx_error->get_text( ). ENDTRY. ENDMETHOD.
METHOD if_saas_provisioning_callback~get_dependencies. " Declarer les dependances vers d'autres services SaaS " par ex. si ce service necessite un autre service SaaS rt_dependencies = VALUE #( ). ENDMETHOD.
ENDCLASS.Donnees initiales specifiques au tenant
CLASS zcl_tenant_initializer DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION. CLASS-METHODS: create IMPORTING iv_tenant_id TYPE string RETURNING VALUE(ro_instance) TYPE REF TO zcl_tenant_initializer.
METHODS: initialize_master_data, initialize_customizing, initialize_number_ranges.
PRIVATE SECTION. DATA mv_tenant_id TYPE string.
ENDCLASS.
CLASS zcl_tenant_initializer IMPLEMENTATION.
METHOD create. ro_instance = NEW #( ). ro_instance->mv_tenant_id = iv_tenant_id. ENDMETHOD.
METHOD initialize_master_data. " Donnees de base standard pour chaque nouveau tenant DATA lt_default_categories TYPE STANDARD TABLE OF zcategory.
lt_default_categories = VALUE #( ( category_id = 'CAT01' name = 'Standard' description = 'Categorie standard' ) ( category_id = 'CAT02' name = 'Premium' description = 'Categorie premium' ) ).
INSERT zcategory FROM TABLE lt_default_categories. ENDMETHOD.
METHOD initialize_customizing. " Customizing specifique au tenant DATA(ls_config) = VALUE zconfig_s( tenant_id = mv_tenant_id currency = 'EUR" language = 'FR" date_format = 'DD/MM/YYYY" time_zone = 'CET" ).
INSERT ztenant_config FROM ls_config. ENDMETHOD.
METHOD initialize_number_ranges. " Initialiser les tranches de numeros par tenant " Dans ABAP Cloud via objets de tranches de numeros DATA(lo_nr_runtime) = cl_numberrange_runtime=>create( iv_object = 'ZORDER" ).
" Creer l'intervalle pour le tenant lo_nr_runtime->create_interval( is_interval = VALUE #( nrrangenr = '01" fromnumber = '0000000001" tonumber = '9999999999" procind = 'I' " Tranche de numeros interne ) ). ENDMETHOD.
ENDCLASS.Autorisations specifiques au tenant
Chaque tenant a ses propres roles d’autorisation :
Concept IAM pour Multitenancy
+-------------------------------------------------------------------+| Architecture des autorisations || || Provider : || +---------------------------------------------------------+ || | IAM Business Catalog (Provider) | || | - Z_PROVIDER_ADMIN : Gerer tous les tenants | || | - Z_PROVIDER_SUPPORT : Support tenant | || | - Z_PROVIDER_BILLING : Acces facturation | || +---------------------------------------------------------+ || || Consumer (par tenant) : || +---------------------------------------------------------+ || | IAM Business Catalog (Tenant) | || | - Z_TENANT_ADMIN : Gerer son propre tenant | || | - Z_TENANT_USER : Utilisateur standard | || | - Z_TENANT_VIEWER : Acces lecture seule | || +---------------------------------------------------------+ |+-------------------------------------------------------------------+Isolation du tenant dans RAP
CLASS lhc_product DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS: authorize_create FOR VALIDATE ON SAVE IMPORTING keys FOR Product~authorizeCreate,
check_tenant_access FOR VALIDATE ON SAVE IMPORTING keys FOR Product~checkTenantAccess.
ENDCLASS.
CLASS lhc_product IMPLEMENTATION.
METHOD authorize_create. " Controle d'autorisation standard READ ENTITIES OF ZI_Product IN LOCAL MODE ENTITY Product ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_products).
LOOP AT lt_products INTO DATA(ls_product). " Verifier l'objet d'autorisation pour Create AUTHORITY-CHECK OBJECT 'Z_PRODUCT" ID 'ACTVT' FIELD '01'.
IF sy-subrc <> 0. APPEND VALUE #( %tky = ls_product-%tky %msg = NEW zcx_product_auth( severity = if_abap_behv_message=>severity-error ) ) TO failed-product. ENDIF. ENDLOOP. ENDMETHOD.
METHOD check_tenant_access. " Verification tenant supplementaire pour les scenarios cross-tenant " Pertinent seulement quand le provider accede aux donnees tenant
DATA(lv_current_tenant) = zcl_tenant_utils=>get_current_tenant_id( ). DATA(lv_is_provider) = zcl_tenant_utils=>is_provider_tenant( ).
READ ENTITIES OF ZI_Product IN LOCAL MODE ENTITY Product ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_products).
LOOP AT lt_products INTO DATA(ls_product). " Le provider peut voir toutes les donnees IF lv_is_provider = abap_true. CONTINUE. ENDIF.
" Le consumer ne peut modifier que les donnees de son propre tenant IF ls_product-TenantId <> lv_current_tenant. APPEND VALUE #( %tky = ls_product-%tky %msg = NEW zcx_tenant_violation( ) ) TO failed-product. ENDIF. ENDLOOP. ENDMETHOD.
ENDCLASS.Configuration specifique au tenant
Customizing par tenant
CLASS zcl_tenant_customizing DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: get_config RETURNING VALUE(rs_config) TYPE zconfig_s,
get_feature_flags RETURNING VALUE(rt_flags) TYPE zfeature_flags_t,
is_feature_enabled IMPORTING iv_feature TYPE string RETURNING VALUE(rv_enabled) TYPE abap_bool.
PRIVATE SECTION. DATA ms_config TYPE zconfig_s. DATA mt_feature_flags TYPE zfeature_flags_t. DATA mv_loaded TYPE abap_bool.
METHODS load_config.
ENDCLASS.
CLASS zcl_tenant_customizing IMPLEMENTATION.
METHOD get_config. IF mv_loaded = abap_false. load_config( ). ENDIF. rs_config = ms_config. ENDMETHOD.
METHOD load_config. " Charger la configuration dependante du mandant SELECT SINGLE * FROM ztenant_config INTO @ms_config.
" Charger les Feature Flags SELECT * FROM zfeature_flags INTO TABLE @mt_feature_flags.
mv_loaded = abap_true. ENDMETHOD.
METHOD get_feature_flags. IF mv_loaded = abap_false. load_config( ). ENDIF. rt_flags = mt_feature_flags. ENDMETHOD.
METHOD is_feature_enabled. DATA(lt_flags) = get_feature_flags( ).
READ TABLE lt_flags INTO DATA(ls_flag) WITH KEY feature = iv_feature.
rv_enabled = xsdbool( sy-subrc = 0 AND ls_flag-enabled = abap_true ). ENDMETHOD.
ENDCLASS.Tests dans les scenarios Multitenancy
Tests unitaires avec Tenant-Mocking
CLASS ltcl_tenant_aware DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA: environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS: class_setup, class_teardown.
METHODS: test_tenant_isolation FOR TESTING, test_cross_tenant_access FOR TESTING.
ENDCLASS.
CLASS ltcl_tenant_aware IMPLEMENTATION.
METHOD class_setup. " Creer l'environnement de test pour les CDS Views environment = cl_cds_test_environment=>create( i_for_entity = 'ZI_PRODUCT" ). ENDMETHOD.
METHOD class_teardown. environment->destroy( ). ENDMETHOD.
METHOD test_tenant_isolation. " Donnees de test pour differents mandants DATA lt_test_data TYPE STANDARD TABLE OF zproduct.
lt_test_data = VALUE #( ( mandt = '100' product_id = 'P001' product_name = 'Produit Tenant A' ) ( mandt = '200' product_id = 'P002' product_name = 'Produit Tenant B' ) ).
environment->insert_test_data( lt_test_data ).
" Test : Tenant A ne voit que ses produits " Le mandant courant est defini sur 100 par le framework de test SELECT COUNT(*) FROM zi_product INTO @DATA(lv_count).
cl_abap_unit_assert=>assert_equals( act = lv_count exp = 1 msg = 'Le tenant ne devrait voir que ses propres produits" ). ENDMETHOD.
METHOD test_cross_tenant_access. " Test pour l'acces cross-tenant du provider " Necessite l'autorisation speciale Z_PROVIDER " ... ENDMETHOD.
ENDCLASS.Bonnes pratiques
A faire
| Recommandation | Justification |
|---|---|
| Utiliser des tables dependantes du mandant | Isolation automatique par le systeme |
| Inclure le Tenant-ID dans les logs | Pour le debugging et la piste d’audit |
| Feature Flags par tenant | Deployments de fonctionnalites individuels |
| Tranches de numeros separees | Pas de collisions entre tenants |
| Destinations specifiques au tenant | Chaque client a ses propres backends |
A eviter
| A eviter | Risque |
|---|---|
| Mandants codes en dur | Ne fonctionne pas en multitenancy |
| Cross-Client sans autorisation | Fuite de donnees entre tenants |
| Variables globales pour donnees tenant | Conditions de course |
| Parser le Tenant-ID depuis l’URL | Faille de securite |
| Operations tenant synchrones | Problemes de performance |
Comparaison : Single-Tenant vs. Multi-Tenant
| Aspect | Single-Tenant | Multi-Tenant |
|---|---|---|
| Isolation | Physiquement separee | Logiquement separee |
| Couts | Plus eleves (par instance) | Plus bas (partages) |
| Scalabilite | Par tenant | Sur tous les tenants |
| Customizing | Illimite | Dans le cadre de la plateforme |
| Mises a jour | Individuelles | Pour tous simultanement |
| Conformite | Plus simple | Plus complexe |
Sujets complementaires
- ABAP On-Premise vs. BTP - Bases de l’architecture BTP
- Authorization Checks - Controles d’autorisation en detail
- RAP Authorization - Autorisations dans RAP
- IAM Business Catalogs - Creer des catalogues d’autorisation
Resume
Le Multitenancy dans ABAP Cloud permet des applications SaaS evolutives :
- Shared Schema : Un systeme ABAP pour tous les tenants, separation via mandants
- Isolation automatique : Les CDS Views et acces base de donnees sont par defaut dependants du mandant
- Gestion du cycle de vie : SaaS Provisioning Service pour l’onboarding et l’offboarding
- Contexte Tenant :
cl_abap_context_infopour acceder aux informations du tenant - Autorisations : IAM Business Catalogs pour les roles provider et consumer
La combinaison des services SAP BTP (XSUAA, Destination Service, SaaS Provisioning) et ABAP Cloud fournit une base solide pour les applications metier multi-locataires.