Aller au contenu

Base de données multi‑tenant (dynors-db)

Objectif : expliquer comment un projet client utilise dynors-db pour isoler les données des clients (tenants) sans réinventer la roue.


1. Rappels : tenant, tiers, stratégie

Dans dynors-db :

  • Un tenant représente un client logique (SeckShop, MIAMCAMPUS Sénégal, JOKKO Interne, …).
  • Un tier détermine le niveau de service (FREE, TIER_3, TIER_2, TIER_1, SOVEREIGN).
  • Une stratégie d’isolation détermine comment les données sont séparées :
  • SHARED : une base, un schéma, une colonne tenant_id.
  • SCHEMA_DEDICATED : un schéma par tenant.
  • DATABASE_DEDICATED : une base par tenant.

Pour les projets clients ESN (CLIENT_PROJECT) : - Règle actuelle : SCHEMA_DEDICATED obligatoire. - TAKKU choisit le tier et crée le schéma (via TenantInitializationService).

👉 L’app client ne gère pas la création des tenants ni des schémas : elle consomme ce que TAKKU a préparé.


2. Dépendances & configuration

Dans build.gradle.kts :

dependencies {
    implementation(platform("com.dynors:dynors-core-bom:1.0.0"))
    implementation("com.dynors:dynors-db")
}

Dans application.yml :

dynors:
  db:
    multi-tenant: true
    default-strategy: schema   # SCHEMA_DEDICATED pour les projets clients
    default-tier: TIER_2

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/dynors_db
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate

3. Entités & schémas côté projet client

En stratégie SCHEMA_DEDICATED :

  • Les tables métier (ex : customers, orders, …) sont créées dans le schéma du tenant (ex : tenant_seckshop).
  • L’application ne rajoute pas de colonne tenant_id dans chaque table :
  • l’isolation est assurée par le schéma,
  • le code reste "classique" côté JPA.

Exemple :

@Entity
@Table(name = "customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(nullable = false)
    private String firstName;

    @Column(nullable = false)
    private String lastName;

    @Column(nullable = false, unique = true)
    private String email;
}

Les scripts Flyway créent simplement customers dans le schéma courant, sans gérer le schéma lui‑même.


4. Repositories tenant‑aware

Pour bénéficier de l’isolation automatique, les repositories métier doivent étendre TenantAwareRepository :

@Repository
public interface CustomerRepository extends TenantAwareRepository<Customer, UUID> {
    Optional<Customer> findByEmail(String email);
}

TenantAwareRepository se charge de : - appliquer le bon schéma / le bon filtre en fonction du tenant courant, - éviter les fuites de données entre tenants.


5. TenantContext & services dynors-db

dynors-db fournit plusieurs services utiles dans un projet client :

  • TenantContextService :
  • getCurrentTenant() → code du tenant courant.
  • utilisé pour enrichir les logs, appliquer certaines règles métier, etc.
  • QuotaService :
  • contrôle des quotas (stockage, nombre d’utilisateurs, appels API…).

Exemple :

@Autowired
private TenantContextService tenantContextService;

@Autowired
private QuotaService quotaService;

public void createCustomer(Customer customer) {
    String tenant = tenantContextService.getCurrentTenant();

    // Vérifier les quotas avant l’opération
    quotaService.checkUsersQuota(tenant);

    // ... créer le client ...
}

👉 C’est TenantContextFilter (fourni par dynors-db) qui positionne automatiquement le tenant courant pour chaque requête HTTP.


6. Migrations Flyway

  • Les migrations sont placées dans src/main/resources/db/migration.
  • Le schéma tenant est géré par dynors-db / TAKKU, pas par les scripts.
  • Chaque script crée/modifie uniquement les tables métier.

Exemple V1__create_customers_table.sql :

CREATE TABLE IF NOT EXISTS customers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

7. Points d’attention

  • Ne pas contourner TenantContext par des requêtes SQL brutes sans filtre.
  • Ne pas créer les schémas/database à la main dans les migrations.
  • Toujours vérifier que les tests d’intégration tournent avec un tenant défini (sinon les repositories tenant‑aware peuvent échouer).

Pour les détails avancés (tiers, stratégies, quotas complets), se référer à : - core/packages/core/db/README.md - core/packages/core/db/ARCHITECTURE_FINALE.md - core/CODES_ET_IDENTIFIANTS_PROJETS.md.