Mobilité
Plateforme de mobilité partagée (VTC/Covoiturage)
Un opérateur régional de VTC voulait reconstruire sa plateforme pour absorber dix fois plus de courses sans dix fois plus de douleur. Le monolithe Node.js historique tenait debout mais saturait chaque vendredi soir.
01 — Le défi
Le défi
Le monolithe de 200 000 lignes était déployé en SSH sur deux VMs ; chaque release demandait trois heures de coordination nocturne. La base PostgreSQL unique encaissait toutes les écritures et toutes les lectures temps réel, et les pannes étaient invariablement détectées par les chauffeurs avant les ingénieurs. La sécurité tenait sur un audit annuel — pas sur du contrôle continu. Il fallait sortir de cette zone à risque sans casser l'activité d'un partenaire de mobilité dont la réputation est faite ou défaite à chaque heure de pointe.
02 — L'approche
L'approche
Nous avons reconstruit la plateforme autour d'un monorepo Turborepo : deux apps React Native (chauffeur + passager), un back-office Next.js 14, et quatre services back-end isolés (rides, users, billing, geo) communicant exclusivement par événements Kafka. Le streaming temps réel passe par un cluster MQTT Emqx ; Redis sert de hot-state à 30s de TTL ; PostGIS porte la vérité historique. La pipeline GitHub Actions exécute Semgrep, Snyk, Gitleaks et Trivy à chaque commit, et le déploiement est blue/green sur ECS Fargate avec rollback automatique si la p95 dérive.
Cadrage & découpage
Audit du monolithe, identification des quatre domaines (rides, users, billing, geo), définition du modèle cible et des contrats Kafka.
Migration progressive
Découpage service par service, dual-write avec outbox idempotent, bascule contrôlée par feature flag sur chaque marché.
Industrialisation
Autoscaling Fargate, observabilité OTel, blue/green automatisé avec rollback SLI sous 90 secondes.
03 — Résultats
Résultats
En 18 mois de production : 840 000 courses traitées, 99,95% d'uptime, matching médian à 3,2 secondes et 11 déploiements par jour sans incident critique. Le coût d'infrastructure mensuel a baissé de 38% malgré 4× la charge, et zéro finding critique au pentest blanc trimestriel.
Détails techniques
L'opérateur de mobilité régional avec lequel nous avons travaillé pilotait son activité depuis un monolithe Node.js de 200 000 lignes, déployé en SSH sur deux VMs. Chaque mise en production demandait trois heures de coordination et une fenêtre nocturne. Les pics du vendredi soir saturaient régulièrement la base PostgreSQL unique, et les pannes — invariablement détectées par les chauffeurs avant les ingénieurs — érodaient la confiance des partenaires. L'objectif de la refonte était clair : absorber 10× la charge, déployer plusieurs fois par jour sans coordination humaine, et faire de la sécurité un sujet d'ingénierie plutôt qu'un sujet d'audit annuel. Nous avons reconstruit la plateforme autour d'un monorepo Turborepo regroupant trois applications frontend et quatre services back-end. Côté mobile, deux applications React Native (Expo bare workflow) : l'app chauffeur, qui maintient un tracking de position en background via expo-task-manager couplé à une intégration native iOS CLLocationManager pour survivre aux purges agressives du système ; et l'app passager, plus légère. La back-office Next.js 14 (App Router) sert à la fois aux opérateurs internes et aux dispatchers, avec Server Components par défaut et un dashboard temps réel branché sur les mêmes flux MQTT que les apps mobiles. Côté services, le monolithe a été éclaté en quatre domaines clairement isolés : `rides` (matching, état d'une course, événements), `users` (KYC chauffeur, profils, documents), `billing` (Stripe Connect, frais de plateforme, paiements), et `geo` (calculs d'itinéraires via OSRM self-hosted, distance-time matrix, géofencing). Chaque service possède son propre schéma PostgreSQL ; le partage se fait via événements Kafka — jamais par requêtes cross-service synchrones. PostGIS est activé sur le service `geo` pour les queries ST_DWithin sur les positions chauffeurs en temps réel, indexées GIST sur la colonne position. Le streaming de positions est l'axe le plus délicat. Chaque chauffeur publie sa position toutes les 5 secondes sur un topic MQTT (Emqx, cluster 3 nodes), retenu côté broker pour 60 secondes. Les apps passager se subscribent par course active uniquement, ce qui borne le fan-out. Côté serveur, un consumer Go subscribe au wildcard et écrit dans Redis (TTL 30s) tout en déclenchant les events de matching. Cette séparation — broker pour le fan-out temps réel, Redis pour le hot-state, PostgreSQL pour la vérité historique — nous a permis de tenir 3,2 secondes en p50 sur le matching, contre 11 secondes sur l'ancienne architecture. La pipeline GitHub Actions exécute à chaque commit : Semgrep avec règles custom (interdiction d'eval, détection de secrets endurcie), Snyk pour les dépendances npm et Python, Gitleaks pour l'historique git, Trivy sur les images Docker construites en multi-stage. Les artefacts sont signés cosign et stockés dans ECR. Le déploiement utilise blue/green via AWS CodeDeploy sur ECS Fargate : 5% du trafic pendant 10 minutes, puis 25%, puis 100%, avec rollback automatique si la latence p95 dépasse le baseline +20% ou si l'error rate franchit 0,5%. Le défi opérationnel le plus sous-estimé a été la cohérence éventuelle entre billing et rides. Une course annulée pendant la phase de paiement pouvait laisser un état orphelin si le webhook Stripe arrivait après le timeout côté ride. Nous avons résolu ça avec un outbox pattern, des compensations idempotentes, et un job de réconciliation horaire qui croise les états Stripe et nos transactions internes — historiquement il rattrape 2 à 5 incohérences par jour, contre des centaines au début de la migration. En 18 mois de production, la plateforme a traité 840 000 courses, maintenu 99,95% d'uptime (≈ 4h30 d'indisponibilité cumulée), tenu un temps de matching médian de 3,2 secondes et permis 11 déploiements par jour en moyenne sans incident critique. Un pentest blanc trimestriel par un cabinet externe complète notre coverage automatisée : aucun finding critique sur les 4 derniers exercices. Le coût d'infrastructure mensuel a baissé de 38% malgré 4× la charge, principalement grâce à l'autoscaling Fargate et au cache Redis qui absorbe 92% du trafic de lecture du service geo.