Pourquoi nous avons migré (et pourquoi nous ne le referions pas pareil)
Mars 2023 : une startup fintech suisse me contacte pour moderniser leur monolithe Rails. 85,000 lignes de code, 4 ans d’existence, scaling difficile.
Décision : migration vers une architecture microservices.
Estimation : 4 mois
Réalité : 10 mois
Surcoût : ~340,000 CHF
Voici les 7 erreurs majeures qui ont fait exploser le planning, et comment les éviter.
Erreur #1 : Trop de services, trop vite
Ce qu’on a fait (mal)
Architecture cible initiale : 23 microservices
auth-service
user-service
profile-service ← Auraient dû rester ensemble
notification-service
email-service ← Idem
sms-service ← Idem
payment-service
invoice-service
transaction-service
...
Résultat
- 23 repos Git à maintenir
- 23 pipelines CI/CD
- Communication inter-services cauchemardesque
- Latence moyenne : +300ms vs monolithe
Ce qu’on aurait dû faire
Règle des bounded contexts : 1 microservice = 1 domaine métier clairement isolé
Architecture corrigée : 6 microservices
auth-service # Authentification/autorisation
user-service # Users + profiles (même contexte)
communication-service # Email + SMS + notifications
payment-service # Paiements + invoices + transactions
analytics-service # Tracking + reporting
admin-service # Backoffice
Indicateur : si 2 services communiquent de manière synchrone > 10x/seconde, ils devraient être fusionnés.
Erreur #2 : Base de données partagée
Ce qu’on a fait (mal)
┌──────────────┐ ┌──────────────┐
│ User Service │────▶│ │
└──────────────┘ │ Postgres │◀────┌────────────────┐
┌──────────────┐ │ (shared) │ │ Payment Service│
│ Auth Service │────▶│ │ └────────────────┘
└──────────────┘ └──────────────┘
Problèmes rencontrés
- Impossible de déployer indépendamment (migrations DB couplées)
- Lock contention sur table
users(500+ req/s) - Impossible de scaler auth et user séparément
Ce qu’on aurait dû faire
Database per service
┌──────────────┐ ┌─────────────┐
│ User Service │────▶│ Postgres │
└──────────────┘ │ (users DB) │
└─────────────┘
┌──────────────┐ ┌─────────────┐
│ Auth Service │────▶│ Redis │
└──────────────┘ │ (sessions) │
└─────────────┘
Pattern pour dupliquer les données : Event sourcing + CQRS
// User service publie un event
await eventBus.publish('user.created', {"{"}
userId: user.id,
email: user.email,
name: user.name
{"}"});
// Auth service écoute et stocke sa copie
eventBus.subscribe('user.created', async (data) => {"{"}
await authDB.users.create({"{"}
id: data.userId,
email: data.email,
// Uniquement les champs nécessaires à l'auth
{"}"});
{"}"});
Erreur #3 : Communication synchrone partout
Ce qu’on a fait (mal)
Scénario : création d’un paiement
// Payment service
async function createPayment(data) {"{"}
// 1. Vérifier l'user (HTTP call)
const user = await fetch('http://user-service/users/' + data.userId);
// 2. Vérifier KYC (HTTP call)
const kyc = await fetch('http://kyc-service/verify/' + data.userId);
// 3. Créer la transaction (HTTP call)
const transaction = await fetch('http://transaction-service/create', {"{"}
method: 'POST',
body: JSON.stringify(data)
{"}"});
// 4. Envoyer notification (HTTP call)
await fetch('http://notification-service/send', {"{"}
method: 'POST',
body: JSON.stringify({"{"}
userId: data.userId,
type: 'payment_created'
{"}"})
{"}"});
return transaction;
{"}"}
// Latence totale : 450ms + risque de cascade failure
Problème : si notification-service est down → paiement échoue
Ce qu’on aurait dû faire
Pattern : synchrone uniquement pour les données critiques, asynchrone pour le reste
async function createPayment(data) {"{"}
// 1. Vérifier user (sync - critique)
const user = await userService.getUser(data.userId);
if (!user) throw new Error('User not found');
// 2. Vérifier KYC (sync - critique)
const isVerified = await kycService.verify(data.userId);
if (!isVerified) throw new Error('KYC not verified');
// 3. Créer le paiement en DB locale
const payment = await db.payments.create(data);
// 4. Publier event (async - non bloquant)
await eventBus.publish('payment.created', payment);
return payment;
{"}"}
// Latence : 120ms
// Notification-service écoute l'event et envoie en background
Erreur #4 : Pas de circuit breaker
Le problème
Scénario réel (août 2023)
- KYC service crashe à cause d’un bug
- Payment service continue à l’appeler (timeout 30s)
- 200 requests/s × 30s = 6000 threads bloqués
- Payment service crashe aussi
- Cascade failure → toute la plateforme down
La solution
Circuit breaker pattern
import CircuitBreaker from 'opossum';
const kycBreaker = new CircuitBreaker(kycService.verify, {"{"}
timeout: 3000, // Timeout après 3s
errorThresholdPercentage: 50, // Ouvre si > 50% d'erreurs
resetTimeout: 30000 // Réessaye après 30s
{"}"});
kycBreaker.fallback(() => {"{"}
// Fallback si circuit ouvert
return {"{"}
verified: false,
reason: 'KYC service unavailable'
{"}"};
{"}"});
// Utilisation
const result = await kycBreaker.fire(userId);
Avec Istio (service mesh)
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: kyc-service
spec:
host: kyc-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
Erreur #5 : Logs dispersés, debugging impossible
Le problème
Bug en production : un paiement échoue
Où chercher les logs ?
payment-service logs → "Error calling transaction-service"
transaction-service logs → "Invalid user_id"
user-service logs → "User deleted by admin"
admin-service logs → ...
45 minutes pour reconstituer le flow.
La solution
Distributed tracing + correlation ID
// API Gateway génère un correlation ID
app.use((req, res, next) => {"{"}
req.correlationId = req.headers['x-correlation-id'] || uuidv4();
res.setHeader('x-correlation-id', req.correlationId);
next();
{"}"});
// Chaque service propage le correlation ID
async function callService(url, data, context) {"{"}
return fetch(url, {"{"}
headers: {"{"}
'x-correlation-id': context.correlationId
{"}"},
body: JSON.stringify(data)
{"}"});
{"}"}
// Logs structurés
logger.info('Payment created', {"{"}
correlationId: req.correlationId,
userId: payment.userId,
amount: payment.amount,
service: 'payment-service'
{"}"});
Stack complète
- Jaeger ou Zipkin : distributed tracing
- ELK (Elasticsearch + Logstash + Kibana) : logs centralisés
- Grafana : métriques
Requête Elasticsearch
{"{"}
"query": {"{"}
"match": {"{"}
"correlationId": "abc-123-def-456"
{"}"}
{"}"}
{"}"}
// Tous les logs de tous les services pour cette requête
Erreur #6 : Pas de stratégie de rollback
Le problème
Déploiement payment-service v2.0
- Breaking change dans l’API
- User-service (v1) continue d’appeler l’ancienne API
- Production down
La solution
1. API versioning
// v1 (deprecated mais toujours disponible)
app.post('/v1/payments', oldPaymentHandler);
// v2 (nouvelle version)
app.post('/v2/payments', newPaymentHandler);
// Header-based (alternative)
app.post('/payments', (req, res) => {"{"}
const version = req.headers['api-version'] || 'v1';
if (version === 'v2') return newPaymentHandler(req, res);
return oldPaymentHandler(req, res);
{"}"});
2. Blue-Green deployment
# Kubernetes
apiVersion: v1
kind: Service
metadata:
name: payment-service
spec:
selector:
app: payment-service
version: v2 # Switch instantané v1 ↔ v2
3. Feature flags
if (featureFlags.isEnabled('new-payment-flow', userId)) {"{"}
return newPaymentFlow(data);
{"}"}
return oldPaymentFlow(data);
Erreur #7 : Sous-estimer la complexité DevOps
Monolithe (1 app)
- 1 Dockerfile
- 1 pipeline CI/CD
- 1 déploiement
Microservices (6 services)
- 6 Dockerfiles
- 6 pipelines CI/CD
- 6 déploiements
- Service mesh (Istio)
- API Gateway
- Message broker (RabbitMQ/Kafka)
- Distributed tracing (Jaeger)
- Logs centralisés (ELK)
- Monitoring (Prometheus + Grafana)
Budget DevOps : multiplié par 4.
Quand migrer vers les microservices ?
✅ Vous devriez envisager les microservices si :
- Équipe > 15 développeurs
- Domaines métier clairement séparés
- Besoin de scaler différemment selon les modules
- Releases indépendantes nécessaires
❌ Restez monolithe si :
- Équipe < 10 développeurs
- Domaine métier simple
- Startup en phase de validation
Alternative : monolithe modulaire (modules découplés dans 1 app).
Checklist avant de migrer
Business
[ ] ROI justifié (pas juste "c'est moderne")
[ ] Équipe prête (DevOps solide)
Architecture
[ ] Bounded contexts identifiés
[ ] Stratégie de communication définie
[ ] Database strategy claire
Infrastructure
[ ] Orchestration (Kubernetes)
[ ] Service mesh envisagé
[ ] Observability (logs, traces, metrics)
Équipe
[ ] Compétences microservices
[ ] Culture DevOps mature
[ ] Processes de déploiement rodés
Vous envisagez une migration vers les microservices ? Contactez-moi pour un audit d’architecture.