Cas d'étude — SaaS e-commerce multi-tenant¶
Appliquer la validation et la gouvernance a un système réel — un SaaS e-commerce multi-tenant de bout en bout.
Contexte¶
Ce cas d'étude traverse l'ensemble du parcours d'architecture (chapitres 00 a 08) et illustre comment chaque concept s'applique a un système concret. On retrouve ici le même système que dans les chapitres précédents, avec un focus sur la validation et la gouvernance.
Système de e-commerce SaaS multi-tenant : 50 tenants, 10 000 commandes par jour, conformité RGPD. Les tenants partagent l'infrastructure mais leurs données sont strictement isolées. Le système doit rester opérationnel pendant les pics de trafic (promotions, soldes) et garantir la traçabilité des données personnelles.
Cadrage — exigences non-fonctionnelles¶
Le cadrage (chapitre 01) définit les attributs qualité qui pilotent toutes les décisions architecturales.
| Attribut | Cible | Mesure |
|---|---|---|
| Latence p99 | < 300 ms | Prometheus histogram |
| Disponibilité | 99.95% (~4h/an) | Uptime check externe |
| RPO | < 5 minutes | Ecart entre dernier backup et incident |
| Throughput | 50 commandes/seconde en pic | k6 load test |
Ces exigences deviennent les seuils des fitness functions en CI. Sans mesure, ce ne sont que des vœux.
ADR structurant¶
Le processus de décision (chapitre 03) s'applique des le premier choix structurant.
ADR-001 : Monolithe modulaire avec extraction progressive¶
Contexte : l'équipe est de taille moyenne (8 développeurs), le domaine est bien compris, le budget infra est contraint. Une architecture microservices complète introduirait une complexité opérationnelle disproportionnee.
Décision : monolithe modulaire avec des modules a frontieres strictes. Le module Payment sera extrait en premier en service indépendant (contrainte PCI-DSS). Les autres modules peuvent être extraits progressivement si le besoin s'en fait sentir.
Conséquences : simplicité opérationnelle initiale, risque de couplage involontaire a surveiller via fitness functions, chemin d'extraction documente.
Gouvernance : cet ADR a été valide par le comite d'architecture. Le critère de révision est défini : si le throughput dépassé 200 commandes/seconde ou si l'équipe dépassé 20 développeurs, on reevalue l'architecture.
ADR-002 : PostgreSQL RLS pour l'isolation multi-tenant¶
Contexte : l'isolation des données entre tenants est une exigence reglementaire (RGPD). Trois options ont été evaluees : schéma par tenant, database par tenant, row-level security.
Décision : row-level security avec partitioning par tenant_id. Le filtrage est automatique via des policies RLS, l'application positionne le tenant en début de session.
Conséquences : simplicité opérationnelle (un seul schéma a maintenir), performance grâce au partitioning, risque de fuite si la policy RLS est mal configurée — couvert par une fitness function dédiée.
Gouvernance : revue sécurité obligatoire pour toute modification des policies RLS. Fitness function en CI qui valide l'isolation.
Topologie — vue C4¶
La modélisation (chapitre 04) produit les diagrammes de référence qui vivent dans l'architecture repository.
C4Container
title SaaS e-commerce multi-tenant — Container diagram
Person(user, "Acheteur", "Navigue, commande, paye")
Person(admin, "Administrateur tenant", "Gere le catalogue et les commandes")
System_Boundary(platform, "Plateforme e-commerce") {
Container(gateway, "API Gateway", "nginx / Kong", "Routage, auth JWT, rate limiting par tenant")
Container(catalog, "Catalog Module", "Python/FastAPI", "Produits, categories, stock")
Container(order, "Order Module", "Python/FastAPI", "Panier, commandes, workflow")
Container(notification, "Notification Module", "Python/FastAPI", "Email, SMS, webhooks")
ContainerDb(postgres, "PostgreSQL", "PostgreSQL 16", "Row-level security, partitioning par tenant_id")
ContainerDb(redis, "Redis", "Redis 7", "Cache par tenant, sessions")
Container(mq, "RabbitMQ", "RabbitMQ 3.13", "Events inter-modules")
}
System_Ext(payment, "Payment Provider", "Stripe / Adyen")
System_Ext(smtp, "SMTP Provider", "Mailgun")
Rel(user, gateway, "HTTPS")
Rel(admin, gateway, "HTTPS")
Rel(gateway, catalog, "REST")
Rel(gateway, order, "REST")
Rel(order, payment, "REST (mTLS)")
Rel(order, mq, "Publish: order.placed")
Rel(notification, mq, "Subscribe: order.placed")
Rel(notification, smtp, "SMTP")
Rel(catalog, postgres, "SQL")
Rel(order, postgres, "SQL")
Rel(catalog, redis, "Cache")
Rel(order, redis, "Sessions") Ce diagramme est versionne dans l'architecture repository (chapitre 04 de cette section). Toute modification de la topologie passe par une PR avec un ADR si le changement est structurant.
Communication inter-modules¶
Les patterns de communication (chapitre 06) sont documentes et gouvernes.
- Synchrone (REST) : opérations utilisateur directes — consultation catalogue, création commande, interrogation statut. Justification : cohérence forte requise, UX immédiate.
- Asynchrone (RabbitMQ) : notifications post-commande, mise à jour analytics, génération de factures. Justification : découplage temporel, résilience aux pics, pas de contrainte de cohérence forte.
Le choix REST vs messaging est documente dans ADR-003. Le critère de révision : si le volume d'events dépassé 10 000/seconde ou si le besoin de replay emerge, on reevalue le broker (Kafka en assessment sur le radar).
Guardrail : une fitness function vérifié qu'aucun appel synchrone n'est fait depuis un handler d'événement asynchrone — ce serait un anti-pattern qui reintroduirait du couplage temporel.
Données et isolation multi-tenant¶
La stratégie de données (chapitre 06) est un point critique pour la sécurité et la conformité.
PostgreSQL avec deux mécanismes d'isolation :
- Row-level security : chaque requête filtre automatiquement par
tenant_idvia une policy RLS. L'application positionneSET app.current_tenant = :tenant_iden début de session. - Partitioning : les tables
ordersetorder_itemssont partitionnees partenant_idpour les performances de requête et la facilite de purge RGPD (suppression de partition entière).
Cache Redis structure par tenant : clef tenant:{tenant_id}:catalog:{product_id} — evite les collisions et permet l'invalidation selective par tenant.
Fitness function : un test vérifié qu'aucune requête SQL dans le codebase n'accede aux tables partitionnees sans clause WHERE tenant_id = .... Les requêtes sans filtre tenant sont une fuite de données potentielle.
Résilience¶
Les patterns de résilience (chapitre 07) sont instrumentes et valides en continu.
- Circuit breaker sur le payment provider : seuil de 50% d'erreurs sur 10 secondes ouvre le circuit. En état ouvert, les tentatives de paiement retournent une erreur explicite plutôt que de bloquer.
- Retry avec backoff exponentiel sur les notifications : 3 tentatives, backoff 1s / 5s / 30s. Les échecs definitifs vont en dead letter queue pour analyse.
- Health checks readiness : incluent la connectivité PostgreSQL et RabbitMQ — un pod sans accès DB ne reçoit pas de trafic.
Gouvernance : les seuils de circuit breaker et les politiques de retry sont documentes dans l'architecture repository. Toute modification passe par un ADR léger.
Sécurité¶
La stratégie de sécurité (chapitre 08) est validee et gouvernee en continu.
- Zero trust inter-modules : chaque requête inter-module porte un JWT interne signe par le service appelant, vérifié par le service cible. Pas de confiance implicite sur le réseau interne.
- TLS everywhere : TLS entre gateway et modules, mTLS entre le monolithe et le payment provider (exigence PCI-DSS).
- STRIDE appliqué : modèle de menace formalise sur l'API gateway (spoofing de tenant_id, elevation de privilege) et sur le payment flow (tampering des montants, information disclosure des données carte).
Guardrails sécurité :
- Scan CVE automatique en CI (Trivy) — zero vulnérabilité critique autorisee
- Détection de secrets dans le code (gitleaks) — bloque le merge
- Vérification des headers de sécurité HTTP (OWASP ZAP baseline) — alerte si manquant
Validation — SLO et fitness functions¶
SLO définis et instrumentes¶
- p99 latence API < 300 ms — mesure via Prometheus histogram, alerte si dépassement sur 5 min
- Error rate < 0.1% sur les endpoints commande — mesure via compteurs Prometheus
- Disponibilité 99.95% — mesure via uptime check externe
Fitness functions CI¶
# Verifie que Order Module n'importe pas depuis Catalog Module
def test_order_does_not_import_catalog():
...
# Verifie qu'aucune image Docker ne depasse 500 MB
def test_docker_image_size():
result = subprocess.run(
["docker", "image", "inspect", "ecommerce-api:latest",
"--format", "{{.Size}}"],
capture_output=True, text=True
)
size_bytes = int(result.stdout.strip())
assert size_bytes < 500 * 1024 * 1024, (
f"Image trop lourde: {size_bytes / 1e6:.0f} MB"
)
# Verifie que toutes les requetes SQL filtrent par tenant_id
def test_all_queries_filter_by_tenant():
...
# Verifie que les circuit breakers sont configures
def test_circuit_breaker_config():
...
Gouvernance en action¶
Le comite d'architecture revoit trimestriellement :
- Les métriques SLO : est-ce que les cibles sont atteintes ?
- Les violations de fitness functions : combien, quelles tendances ?
- Le backlog de dette technique : est-ce que le score total diminue ?
- Le Technology Radar : est-ce que les choix technologiques restent pertinents ?
Les résultats sont documentes dans un compte-rendu accessible à toutes les équipes. Les actions sont trackees dans le backlog commun.
Dette technique identifiée¶
Le système e-commerce porte de la dette technique délibérée et prudente, documentee dans le backlog.
| Item de dette | Sévérité | Effort | Stratégie | ADR |
|---|---|---|---|---|
| Cache Redis sans invalidation event-driven | Moyenne | Moyen | Strangler fig vers pub/sub invalidation | ADR-018 |
| Monolithe sans feature flags | Faible | Faible | Boy scout rule, ajouter progressivement | ADR-022 |
| Tests d'intégration lents (8 min) | Moyenne | Élevé | Parallelisation CI + refactoring fixtures | Planifie |
| Pas de distributed tracing | Élevée | Moyen | Ajouter OpenTelemetry au prochain sprint | ADR-025 |
Le score total de dette est suivi dans le tableau de bord et révisé à chaque rétrospective architecture.
Technology Radar du projet¶
Le comite maintient un radar spécifique au projet, aligne sur le radar de l'organisation.
| Quadrant | Technologie | Anneau | Justification |
|---|---|---|---|
| Backend | FastAPI | Adopt | Standard confirme, performance et DX |
| Backend | Celery | Adopt | Workers asynchrones, stable |
| Datastore | PostgreSQL 16 | Adopt | RLS, partitioning, maturité |
| Datastore | Redis 7 | Adopt | Cache et sessions, performant |
| Messaging | RabbitMQ | Adopt | Simple, suffisant pour le volume actuel |
| Messaging | Kafka | Assess | A évaluer si besoin de replay ou volume > 10k evt/s |
| Observabilité | Prometheus | Adopt | Standard métriques |
| Observabilité | OpenTelemetry | Trial | Distributed tracing, en cours d'évaluation |
Leçons apprises¶
Après 18 mois d'exploitation, les enseignements clés :
- Le RLS PostgreSQL fonctionne mais nécessité une vigilance constante. Chaque nouvelle requête doit être vérifiée par la fitness function tenant isolation. Deux incidents de fuite de données ont été evites grâce à ce guardrail.
- Le monolithe modulaire a tenu pour 8 développeurs et 50 tenants. Le critère de révision (200 cmd/s ou 20 devs) n'a pas été atteint. La décision de ne pas partir en microservices etait la bonne pour ce contexte.
- Les fitness functions sont le meilleur investissement du projet. Elles ont détecté 12 violations de couplage en 6 mois — toutes corrigees avant la production. Le coût de les écrire est negligeable compare au coût des bugs qu'elles ont evites.
- La gouvernance légère suffit pour une équipe de cette taille. Un comite mensuel de 2 heures, des ADR en PR, et des fitness functions en CI couvrent 95% des besoins.
Tip
Un cas d'étude n'est pas un template a copier. C'est un exemple de raisonnement — chaque système a ses propres contraintes, ses propres trade-offs, et ses propres priorités. L'important est la démarche, pas la solution spécifique.
Chapitre suivant : Cas d'étude — Plateforme d'automatisation — validation et gouvernance appliquees a une plateforme d'automatisation infrastructure.