Ce que vous saurez faire
Au terme de ce tutoriel, vous serez capable de découper une application monolithique PHP/Node.js en microservices indépendants pour une PME sénégalaise. Vous saurez identifier les frontières fonctionnelles (Domain Driven Design simplifié), choisir entre REST et messaging asynchrone via RabbitMQ, conteneuriser chaque service avec Docker, orchestrer le tout avec Docker Compose puis Kubernetes (k3s) sur un VPS à 15 000 FCFA/mois. Vous mettrez en place un API Gateway (Kong ou Traefik), une base de données par service (PostgreSQL et MongoDB), de l'observabilité (Prometheus + Grafana) et un pipeline CI/CD avec GitHub Actions. À la fin, votre architecture supportera 500 commandes/jour avec un coût mensuel inférieur à 35 000 FCFA et une montée en charge horizontale automatique selon la fréquentation (typique pour un e-commerce dakarois après les paiements de salaire).
Étape 1 : Cartographier le monolithe existant
Avant tout découpage, listez les fonctionnalités métier de votre application actuelle. Pour une boutique en ligne de Médina, on identifie typiquement : Catalogue produits, Panier, Commandes, Paiement (Wave, Orange Money), Livraison, Notifications SMS, Comptes clients. Créez un fichier contexte.md :
## Bounded Contexts identifies
1. catalog-service -> produits, categories, stock
2. cart-service -> panier session
3. order-service -> commandes, statuts
4. payment-service -> Wave, OM, carte
5. shipping-service -> livreurs, zones Dakar
6. notification-service -> SMS, email
7. account-service -> users, auth JWT
Règle d'or : chaque contexte possède SES données et n'y accède jamais directement à celles d'un autre.
Étape 2 : Préparer l'environnement de développement
Installez Docker Desktop (Windows/Mac) ou Docker Engine sur Ubuntu :
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
docker --version
docker compose version
Créez la structure du projet :
mkdir boutique-dakar && cd boutique-dakar
mkdir -p services/{catalog,cart,order,payment,shipping,notification,account}
mkdir -p gateway infra/postgres infra/rabbitmq
Étape 3 : Créer le premier microservice (catalog) en Node.js
Dans services/catalog, initialisez le projet :
cd services/catalog
npm init -y
npm install express pg dotenv helmet cors
Fichier index.js :
const express = require('express');
const { Pool } = require('pg');
const app = express();
app.use(express.json());
const pool = new Pool({ connectionString: process.env.DB_URL });
app.get('/products', async (req, res) => {
const r = await pool.query('SELECT * FROM products WHERE stock > 0');
res.json(r.rows);
});
app.listen(3001, () => console.log('catalog up on 3001'));
Étape 4 : Conteneuriser avec un Dockerfile multi-stage
Créez services/catalog/Dockerfile :
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3001
USER node
CMD ["node", "index.js"]
L'image finale fait moins de 150 Mo, idéale pour les VPS limités en bande passante.
Étape 5 : Une base de données PAR service
Dans docker-compose.yml, donnez à chaque service sa propre base PostgreSQL ou MongoDB :
services:
catalog-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: catalog
POSTGRES_PASSWORD: catalogpass
volumes:
- catalog_data:/var/lib/postgresql/data
order-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: orders
POSTGRES_PASSWORD: orderpass
volumes:
- order_data:/var/lib/postgresql/data
volumes:
catalog_data:
order_data:
Cela évite le couplage : si la base catalogue tombe, les commandes restent fonctionnelles.
Étape 6 : Communication asynchrone avec RabbitMQ
Quand une commande est validée, on envoie un événement plutôt que d'appeler directement le service paiement :
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: dakar2026
Code émetteur dans order-service :
const amqp = require('amqplib');
const conn = await amqp.connect('amqp://admin:dakar2026@rabbitmq');
const ch = await conn.createChannel();
await ch.assertQueue('order.created');
ch.sendToQueue('order.created', Buffer.from(JSON.stringify(order)));
Étape 7 : Mettre en place l'API Gateway avec Traefik
Traefik route automatiquement vers les bons services et gère les certificats Let's Encrypt :
traefik:
image: traefik:v3.0
command:
- --api.insecure=true
- --providers.docker=true
- --entrypoints.web.address=:80
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Sur chaque service, ajoutez les labels :
labels:
- traefik.http.routers.catalog.rule=PathPrefix(`/api/catalog`)
- traefik.http.services.catalog.loadbalancer.server.port=3001
Étape 8 : Authentification centralisée avec JWT
Le service account émet un JWT que tous les autres vérifient :
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'no token' });
try {
req.user = jwt.verify(token, SECRET);
next();
} catch (e) {
res.status(403).json({ error: 'invalid' });
}
}
Stockez le secret dans un fichier .env jamais commité.
Étape 9 : Observabilité avec Prometheus et Grafana
Ajoutez dans chaque service Node.js le module prom-client :
const promClient = require('prom-client');
promClient.collectDefaultMetrics();
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
Configurez Prometheus pour scraper toutes les 15 secondes et créez des dashboards Grafana montrant le temps de réponse par service, le taux d'erreur 5xx et l'utilisation CPU.
Étape 10 : Pipeline CI/CD avec GitHub Actions
Fichier .github/workflows/deploy.yml :
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build catalog
run: docker build -t registry.example.sn/catalog:${{ github.sha }} ./services/catalog
- name: Push
run: docker push registry.example.sn/catalog:${{ github.sha }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: cd /opt/boutique && docker compose pull && docker compose up -d
Étape 11 : Passage à Kubernetes léger (k3s)
Quand le trafic dépasse 1000 req/min, migrez vers k3s sur un VPS Contabo à 8 000 FCFA/mois :
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
Créez un deployment catalog.yaml avec autoscaling :
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog
spec:
replicas: 2
selector:
matchLabels: { app: catalog }
template:
metadata:
labels: { app: catalog }
spec:
containers:
- name: catalog
image: registry.example.sn/catalog:latest
resources:
limits: { cpu: 200m, memory: 256Mi }
Étape 12 : Tests d'intégration entre services
Utilisez testcontainers pour démarrer une vraie base PostgreSQL pendant les tests :
const { PostgreSqlContainer } = require('@testcontainers/postgresql');
test('catalog returns products', async () => {
const pg = await new PostgreSqlContainer().start();
process.env.DB_URL = pg.getConnectionUri();
// ... lance le service et appelle GET /products
});
Étape 13 : Stratégie de déploiement progressive
Ne migrez jamais tout d'un coup. Utilisez le pattern Strangler Fig : extraire UN service à la fois (commencez par notification car c'est le moins critique), faites coexister monolithe et microservices derrière le Gateway pendant 4 à 6 semaines, puis basculez le trafic à 10 %, 50 %, 100 %.
Étape 14 : Documentation et monitoring opérationnel
Créez un RUNBOOK.md par service listant : commande de redémarrage, logs à consulter (docker logs catalog --tail 100 -f), seuils d'alerte, contacts d'astreinte. Configurez des alertes Grafana vers un canal Slack ou un numéro WhatsApp via Twilio. En production sénégalaise, prévoyez aussi un fallback en cas de coupure Sonatel : files RabbitMQ persistantes et retry automatique sur 24h.
Erreurs
Erreur 1 : Microservices trop fins. Découper en 30 services pour 5 développeurs est suicidaire. Démarrez avec 4 à 6 services maximum.
Erreur 2 : Base de données partagée. Si deux services lisent la même table PostgreSQL, vous avez un monolithe distribué, pire qu'un monolithe classique.
Erreur 3 : Communication 100 % synchrone. Chaîner 5 appels REST = latence cumulée > 2 secondes. Utilisez RabbitMQ ou Kafka pour les opérations non bloquantes.
Erreur 4 : Pas de versioning d'API. Préfixez toujours par /v1/. Sinon une mise à jour casse tous les clients mobiles.
Erreur 5 : Logs dispersés. Sans Loki ou ELK centralisé, déboguer un problème en production prend 3 heures au lieu de 10 minutes.
Erreur 6 : Oubli des transactions distribuées. Implémentez le pattern Saga (compensation) pour les commandes impliquant paiement + stock + livraison.
Erreur 7 : Sécurité réseau négligée. Les services internes ne doivent JAMAIS être exposés sur Internet. Utilisez un network Docker privé.
Checklist
- Bounded contexts identifiés et documentés
- Une base de données par service
- Dockerfile multi-stage < 200 Mo
- API Gateway (Traefik ou Kong) en place
- JWT centralisé pour l'authentification
- RabbitMQ pour la communication asynchrone
- Endpoints /metrics exposés sur tous les services
- Dashboards Grafana actifs
- Pipeline CI/CD GitHub Actions opérationnel
- Tests d'intégration avec testcontainers
- Stratégie Strangler Fig appliquée
- RUNBOOK.md par service rédigé
- Backup quotidien des volumes Docker (cron + rsync vers S3 compatible)
- Coût mensuel total < 35 000 FCFA validé