Documentation Technique : Architecture B2B SaaS Multi-Tenant
Cette documentation détaille la transition du système d'un produit "Single-Tenant" vers une plateforme B2B SaaS Multi-Tenant capable d'accueillir plusieurs organisations (écoles, entreprises) avec une isolation stricte et une configuration personnalisée.
🏛️ 1. Architecture des Données & Isolation
Modèle de Données (Prisma)
Toutes les entités critiques (Track, Lesson, User, Response, Enrollment) sont désormais liées à une entité Organization.
- Champ
organizationId: Présent et indexé sur chaque table pour garantir des performances de filtrage optimales. - Isolation Automatique : Utilisation d'une extension Prisma (
packages/database/src/extension.ts) qui injecte automatiquement la clausewhere: { organizationId }dans toutes les requêtes.
Contexte Tenant
Le contexte est propagé via AsyncLocalStorage (packages/database/src/context.ts) :
- API : Le
tenantMiddlewareextrait l'ID depuis les headers (x-organization-id) ou le token auth. - Worker : Le contexte est extrait des données du job BullMQ et injecté avant l'exécution du handler.
⚙️ 2. Worker WhatsApp (Engine)
Le worker a été totalement modularisé pour éviter le monolithisme.
Dispatcher Central (index.ts)
Le worker agit comme un dispatcher léger qui délègue les tâches à des JobHandlers spécialisés.
Handlers Spécialisés (src/handlers/)
- InboundHandler : Point d'entrée unique pour tous les messages texte/audio.
- AdminHandler : Commandes réservées à l'administration.
- NudgeHandler : Relances automatiques.
- EnrollHandler : Gestion des inscriptions aux parcours.
- MediaHandler : Traitement des images et vidéos.
🧠 3. Moteur d'IA & Pattern Strategy
Le service d'intelligence artificielle (apps/api/src/services/ai/) a été conçu pour la résilience.
Provider Registry
L'IA n'est plus liée à un seul fournisseur. Le ProviderRegistry permet d'enregistrer plusieurs moteurs (Gemini, OpenAI, Mistral) avec leurs capacités :
- Priority Failover : Si le provider primaire (ex: Gemini) échoue, le système bascule automatiquement sur le secondaire (ex: OpenAI).
- Capability Routing : Les requêtes sont routées vers le meilleur modèle selon le besoin (Vision pour les images, Whisper pour l'audio, GPT-4o pour le texte complexe).
Personnalisation Dynamique
Chaque organisation peut configurer son propre "Personality Studio". Les prompts sont compilés dynamiquement en fusionnant :
- Le template de base du système.
- La configuration spécifique de l'organisation (nom du bot, mission, ton).
👁️ 4. Observabilité & Debugging
Logging Multi-Tenant
Le logger (logger.ts) est synchronisé avec le contexte AsyncLocalStorage.
- Auto-Injection : Chaque ligne de log (
info,error) inclut automatiquement l'organizationIds'il est présent dans le contexte d'exécution. - Bénéfice : Possibilité de filtrer les logs en temps réel par client dans les outils de monitoring (Cloudwatch, Datadog).
📡 5. Flux de Message (Pipeline)
Le flux de traitement est désormais unifié et asynchrone :
- Webhook API : Reçoit le message, identifie l'organisation via le
phone_number_id, et ajoute un jobhandle-inbounddans Redis (BullMQ). - BullMQ : Gère la file d'attente et la persistance des tâches.
- Worker : Consomme le job, initialise le contexte tenant, normalise le texte (Wolof/FR), et appelle le handler approprié.
- WhatsApp Cloud API : Envoi de la réponse finale via les services utilitaires (
whatsapp-cloud.ts).
🧪 6. Stratégie de Test
Le projet utilise Vitest pour garantir la stabilité :
- Tests Unitaires : Validation de la normalisation du Wolof (
normalizeWolof.test.ts). - Tests d'Intégration : Simulation des flux métier via des mocks Prisma et BullMQ (
OnboardingHandler.test.ts).
🛠️ Maintenance & Évolutions
- Ajouter un Handler : Créer une classe implémentant
JobHandlerdanssrc/handlers/et l'enregistrer dansindex.ts. - Ajouter un Provider IA : Créer une classe implémentant
LLMProvideret l'ajouter auProviderRegistry. - Migration DB : Toujours utiliser
npx prisma migrate devpour maintenir l'intégrité du schéma multi-tenant.