Que fait Multi-Tenancy ?
Règles
- Isolation au niveau ligne (par défaut) : ajoutez une colonne
tenantId à chaque table concernée par un locataire — appliquez via middleware ou RLS (Row-Level Security)
- Schéma par locataire : utilisez lorsque les locataires ont besoin de schémas personnalisés ou d'une isolation stricte des données — opérations plus complexes, meilleure isolation
- Routage par sous-domaine : extrayez le locataire de
{tenant}.app.com — résolvez dans le middleware, définissez tenantId sur le contexte de la requête
- Domaines personnalisés : CNAME vers votre application, recherchez le locataire par l'en-tête
Host — utilisez une table domains faisant correspondre les domaines personnalisés aux identifiants de locataire
- Configuration par locataire : stockez le thème (logo, couleurs), les feature flags et les limites dans une table
tenant_settings — mettez en cache en mémoire avec TTL
- Pattern RLS (Postgres) :
ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON posts USING (tenant_id = current_setting('app.tenant_id'))
- Middleware : extrayez le locataire du sous-domaine/en-tête, définissez-le sur le contexte, appliquez-le à toutes les requêtes de base de données — ne laissez jamais une requête s'exécuter sans portée locataire
- Partitionnement des données : pour les grandes tables, partitionnez par
tenantId — améliore les performances des requêtes et permet la sauvegarde/restauration par locataire
Pattern d'isolation au niveau ligne
function getTenantFromHost(host: string): string {
const subdomain = host.split(".")[0];
return subdomain;
}
prisma.$use(async (params, next) => {
if (TENANT_MODELS.includes(params.model)) {
params.args.where = { ...params.args.where, tenantId: context.tenantId };
}
return next(params);
});
Routage par sous-domaine (Middleware Next.js)
export function middleware(request: NextRequest) {
const host = request.headers.get("host") ?? "";
const tenant = host.split(".")[0];
const response = NextResponse.next();
response.headers.set("x-tenant-id", tenant);
return response;
}
À éviter
- Requêtes sans portée locataire — un seul
WHERE tenantId = ? manquant divulgue des données entre locataires
- Codage en dur du locataire côté client — résolvez à partir du sous-domaine/domaine côté serveur
- Absence d'index sur
tenantId — chaque table concernée par un locataire a besoin d'un index composite commençant par tenantId
- Mélange des données des locataires dans des caches partagés — namespacez les clés de cache avec
{tenantId}:{key}