Pourquoi nous avons choisi le multi-tenant natif
Une organisation, un scope, zéro fuite. Retour sur la décision la plus structurante de la plateforme.
Le multi-tenant partagé — toutes les organisations dans la même base, isolées par filtres applicatifs — est le compromis le plus dangereux qu'une plateforme SaaS puisse adopter. Une seule requête mal scope, une jointure oubliée, un voter manquant, et la fuite est silencieuse, durable, et indétectable sans audit complet.
C'est pourquoi Jul6art applique le multi-tenant à toutes les couches de l'application :
- Doctrine — chaque entité métier porte un FK
organization_id, indexé en composite avecdeleted_at. AucunfindAll()n'est jamais valide ; les repositories exposentqueryByOrganization(Organization)qui devient le point d'entrée canonique. - HTTP — l'en-tête
X-ORGANIZATIONest résolu en début de chaîne par un subscriber qui rejette toute incohérence avec le tenant de l'utilisateur authentifié. - API Platform — chaque
StateProviderapplique automatiquement le filtre tenant. Les processeurs vérifient le scope avant le persist sur les FK potentiellement cross-org (mass-assignment guard). - Voters —
denyAccessUnlessGranted(Voter::VIEW, $entity)sur chaqueshow,edit,delete, avec un re-check duorganization_idcôté entité.
La défense en profondeur évite qu'une seule erreur ne devienne une faille. Une régression sur un voter sera rattrapée par le filtre Doctrine ; une régression sur le filtre Doctrine sera rattrapée par le voter.