Le problème du multi-tenant naïf
La première approche qui vient à l'esprit : ajouter un champ tenant_id sur chaque entité et filtrer
les requêtes manuellement. Ça fonctionne en développement. En production, c'est une bombe à retardement.
Un développeur qui oublie le filtre WHERE tenant_id = ? dans une seule requête expose les données
d'un client à un autre. Le compilateur et les test unitaires n'avertissent pas.
Résolution de tenant via le domaine
Comment identifier le tenant d'une requête entrante ? La méthode la plus propre en SaaS :
le sous-domaine. Chaque client a son propre sous-domaine (acme.myapp.io),
que l'on résout vers un Tenant en base.
final class TenantResolver
{
public function __construct(
private readonly TenantRepository $tenants,
private readonly CacheInterface $cache,
) {}
public function resolveFromRequest(Request $request): TenantId
{
$host = $request->getHost(); // ex: "acme.myapp.io"
$slug = explode('.', $host)[0]; // "acme"
return $this->cache->get(
"tenant_id.$slug",
fn() => $this->tenants->findBySlugOrFail($slug)->getId(),
);
}
} On cache le résultat dans Redis avec une TTL courte. La résolution de tenant est sur le chemin critique de chaque requête.
Async isolé : Symfony Messenger et le contexte tenant
Le piège classique : un worker Messenger traite un message sans savoir à quel tenant il appartient. La solution est de sérialiser le tenant dans le message via un Stamp dédié.
final readonly class TenantStamp implements StampInterface
{
public function __construct(
public readonly TenantId $tenantId,
) {}
}
// Middleware d'injection (côté dispatch)
final class TenantStampMiddleware implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
if ($envelope->last(TenantStamp::class) === null) {
$envelope = $envelope->with(new TenantStamp($this->context->getTenantId()));
}
return $stack->next()->handle($envelope, $stack);
}
} Conclusion
L'isolation multi-tenant n'est pas une feature — c'est une propriété de sécurité fondamentale. Le résultat : une architecture où une fuite inter-tenant est structurellement impossible, pas juste improbable.