============================== Backends d'Isolation Tenant ============================== Updo Backend supporte trois stratégies d'isolation des données tenant. Le choix du backend est configuré via ``MULTITENANT['ISOLATION_BACKEND']``. Module : ``src.share_kernel.infrastructure.isolation`` Factory ======= .. module:: src.share_kernel.infrastructure.isolation :synopsis: Sélection du backend d'isolation .. function:: get_isolation_backend() -> TenantIsolationBackend Retourne le singleton du backend d'isolation configuré. Lit ``MULTITENANT['ISOLATION_BACKEND']`` et instancie le backend approprié. :raises ConfigurationError: Si le type de backend est inconnu. .. function:: reset_backend() -> None Réinitialise le singleton. **Pour les tests uniquement.** Schema Backend (``schema``) ============================ .. module:: src.share_kernel.infrastructure.isolation.schema_backend :synopsis: Isolation par schéma PostgreSQL **Principe :** Chaque tenant obtient un schéma PostgreSQL dédié (``tenant_``). Le ``search_path`` est dynamiquement configuré pour chaque requête. **Recommandé pour :** Jusqu'à ~2 000 tenants. .. class:: SchemaIsolationBackend(TenantIsolationBackend) .. method:: activate_tenant(tenant, connection) -> None Configure ``search_path`` vers le schéma du tenant + public. Utilise ``SET LOCAL`` (transaction-scoped) pour la compatibilité PgBouncer. Utilise ``psycopg.sql.Identifier()`` pour prévenir l'injection SQL. Applique aussi ``statement_timeout`` pour la protection contre les « noisy neighbors ». .. method:: deactivate_tenant(connection) -> None Réinitialise ``search_path`` au schéma partagé uniquement. .. method:: create_tenant_storage(tenant) -> None Crée le schéma PostgreSQL et exécute les migrations. .. code-block:: sql CREATE SCHEMA IF NOT EXISTS tenant_acme_corp; .. method:: destroy_tenant_storage(tenant) -> None Supprime le schéma PostgreSQL. **DESTRUCTIF.** .. code-block:: sql DROP SCHEMA IF EXISTS tenant_acme_corp CASCADE; .. method:: migrate_tenant(tenant) -> None Applique les migrations Django dans le schéma du tenant. Configure le flag ``_migrating_tenant_schema`` pour que le ``DatabaseRouter`` n'autorise que les modèles tenant-specific. .. method:: schema_exists(schema_name: str) -> bool Vérifie si un schéma PostgreSQL existe. .. method:: list_schemas(prefix: str | None = None) -> list[str] Liste tous les schémas tenant. **Exemple :** .. code-block:: python # Configuration MULTITENANT = { 'ISOLATION_BACKEND': 'schema', 'SHARED_SCHEMA_NAME': 'public', 'TENANT_SCHEMA_PREFIX': 'tenant_', } # Le middleware active automatiquement le schéma # SQL généré : SET LOCAL search_path TO "tenant_acme_corp", "public" RLS Backend (``rls``) ====================== .. module:: src.share_kernel.infrastructure.isolation.rls_backend :synopsis: Isolation par Row-Level Security PostgreSQL **Principe :** Schéma unique avec des politiques RLS PostgreSQL. Le tenant courant est défini via ``SET app.current_tenant``. **Recommandé pour :** 2 000+ tenants ou PgBouncer en mode transaction pooling. **Prérequis :** Toutes les tables tenant-specific doivent avoir une colonne ``tenant_id``. .. class:: RLSIsolationBackend(TenantIsolationBackend) .. method:: activate_tenant(tenant, connection) -> None Exécute ``SET LOCAL app.current_tenant = ''``. ``SET LOCAL`` est transaction-scoped (compatible PgBouncer). .. method:: deactivate_tenant(connection) -> None Efface ``app.current_tenant`` avec ``SET LOCAL app.current_tenant = ''``. .. method:: create_tenant_storage(tenant) -> None Active RLS et crée les politiques d'isolation pour toutes les tables ayant une colonne ``tenant_id``. Politique créée : .. code-block:: sql ALTER TABLE ENABLE ROW LEVEL SECURITY; ALTER TABLE
FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_
ON
USING (tenant_id = current_setting('app.current_tenant', true)::uuid) WITH CHECK (tenant_id = current_setting('app.current_tenant', true)::uuid); .. method:: destroy_tenant_storage(tenant) -> None Supprime toutes les lignes du tenant (``DELETE WHERE tenant_id = ?``). Utilise ``SET CONSTRAINTS ALL DEFERRED`` pour éviter les problèmes d'ordre FK. .. method:: suspend_isolation(connection) -> None Désactive temporairement RLS pour les requêtes cross-tenant. Nécessite le rôle ``BYPASSRLS``. .. method:: resume_isolation(connection) -> None Réactive RLS après une opération cross-tenant. .. method:: migrate_tenant(tenant) -> None No-op : en mode RLS, les migrations sont partagées. Shared FK Backend (``shared_fk``) =================================== .. module:: src.share_kernel.infrastructure.isolation.shared_fk_backend :synopsis: Isolation par FK partagée (application-level) **Principe :** Schéma unique, filtrage au niveau applicatif via ``tenant_id`` FK. Aucune garantie au niveau base de données. **Recommandé pour :** Prototypage et petits déploiements. .. class:: SharedFKIsolationBackend(TenantIsolationBackend) .. method:: activate_tenant(tenant, connection) -> None No-op. Le filtrage est géré par les managers et querysets Django. .. method:: deactivate_tenant(connection) -> None No-op. .. method:: create_tenant_storage(tenant) -> None No-op. Pas de stockage séparé à créer. .. method:: destroy_tenant_storage(tenant) -> None Supprime toutes les lignes du tenant de toutes les tables tenant-specific. .. method:: migrate_tenant(tenant) -> None No-op. Les migrations sont partagées. Comparaison des backends ========================= .. list-table:: Comparaison des backends d'isolation :header-rows: 1 :widths: 20 30 30 30 * - Critère - Schema - RLS - Shared FK * - Isolation DB - Forte (schéma dédié) - Forte (politique PostgreSQL) - Faible (applicatif) * - Tenants max - ~2 000 - Illimité - Illimité * - PgBouncer - Oui (SET LOCAL) - Oui (SET LOCAL) - Oui * - Migrations - Par tenant - Partagées - Partagées * - Performance - Haute (index séparés) - Haute (RLS natif) - Moyenne * - Complexité - Élevée - Moyenne - Faible