API REST développée avec Node.js permettant de gérer des produits avec MySQL et Redis.
Le projet met en œuvre une architecture en couches ainsi qu'un système de cache Redis basé sur le pattern Cache-Aside, avec TTL, invalidation du cache et protection contre le cache stampede.
- Création de produits
- Récupération d'un produit par son ID
- Modification d'un produit
- Suppression d'un produit
- Cache Redis
- TTL configurable
- Activation/désactivation du cache
- Invalidation du cache après modification ou suppression
- Statistiques HIT / MISS
- Protection contre le cache stampede avec un lock Redis
- Benchmark des performances
- Exécution avec Docker Compose
- Node.js
- Express
- MySQL
- Redis
- Docker
- Docker Compose
src/
├── benchmark/
│ └── cache.js
├── cache/
│ ├── redis.js
│ └── lock.js
├── controllers/
│ ├── product.js
│ └── cache.js
├── repositories/
│ ├── mysql.js
│ └── product.js
├── routes/
│ ├── product.js
│ └── cache.js
├── services/
│ ├── product.js
│ └── cache.js
└── index.js
Client
│
▼
Routes
│
▼
Controllers
│
▼
Services
│
├──────────────► Redis
│
▼
Repositories
│
▼
MySQL
Routes
- Définissent les endpoints HTTP.
- Délèguent les requêtes aux controllers.
Controllers
- Gèrent les paramètres HTTP.
- Appellent les services.
- Construisent les réponses HTTP.
Services
- Contiennent la logique métier.
- Gèrent le cache et son invalidation.
- Coordonnent Redis et les repositories.
Repositories
- Gèrent l'accès à MySQL.
Cache
- Centralise la connexion Redis.
- Gère les locks Redis.
Le projet utilise le pattern Cache-Aside.
Lors d'une requête GET /products/:id :
GET /products/:id
│
▼
Redis GET
│
┌─────┴─────┐
│ │
HIT MISS
│ │
▼ ▼
Retour Lock Redis
│
▼
MySQL
│
▼
Redis SET
│
▼
Retour
Lorsque le produit existe dans Redis, il est retourné directement sans interroger MySQL.
Lorsque le produit n'existe pas dans Redis :
- Un lock Redis est tenté.
- La requête qui obtient le lock interroge MySQL.
- Le résultat est placé dans Redis avec un TTL.
- Le lock est libéré.
- Les autres requêtes récupèrent le produit depuis Redis.
Sans protection, l'expiration d'une entrée peut provoquer plusieurs requêtes simultanées vers MySQL :
100 requêtes
│
├──► MySQL
├──► MySQL
├──► MySQL
├──► MySQL
└──► ...
Le projet utilise un lock Redis avec :
SET key value NX EX
NX: le lock est créé uniquement s'il n'existe pas.EX: le lock possède une durée d'expiration.- Une valeur unique est associée au lock.
Les autres requêtes attendent ensuite que le produit soit disponible dans Redis.
Lorsqu'un produit est modifié :
UPDATE MySQL
│
▼
DEL product:id
Lorsqu'un produit est supprimé :
DELETE MySQL
│
▼
DEL product:id
Cela évite de conserver une ancienne version du produit dans Redis.
Endpoint :
GET /cache/statsExemple :
{
"hits": 41,
"misses": 1,
"total": 42,
"hitRate": 97.61904761904762
}Les statistiques permettent de mesurer l'efficacité du cache.
POST /products
Content-Type: application/json{
"name": "MacBook Pro",
"description": "Ordinateur portable Apple",
"price": 1999.99
}GET /products/:idExemple :
GET /products/1PUT /products/:id
Content-Type: application/json{
"name": "MacBook Pro M5",
"description": "Nouveau modèle",
"price": 2199.99
}DELETE /products/:idGET /cache/statsCréer un fichier .env :
PORT=3000
MYSQL_HOST=mysql
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_DATABASE=products
REDIS_HOST=redis
REDIS_PORT=6379
CACHE_ENABLED=true
CACHE_TTL=60Active ou désactive le cache Redis.
CACHE_ENABLED=truePour désactiver le cache :
CACHE_ENABLED=falseDurée de vie d'une entrée Redis en secondes.
CACHE_TTL=60Démarrer les services :
docker compose up -dReconstruire les images :
docker compose up -d --buildVoir les services :
docker compose psVoir les logs de l'API :
docker logs rc_apiAccéder au CLI Redis :
docker compose exec redis redis-cliVider la base Redis :
docker compose exec redis redis-cli FLUSHDBAfficher les clés :
docker compose exec redis redis-cli KEYS "*"Le projet contient un benchmark pour mesurer les performances du cache :
node src/benchmark/cache.jsExemple :
Cache benchmark
First request: 60.44 ms
Concurrent requests: 100
Average: 36.50 ms
Min: 32.45 ms
Max: 42.66 ms
P50: 36.40 ms
P95: 42.02 ms
P99: 42.38 ms
Le benchmark permet notamment de comparer les performances avec Redis activé et désactivé.
La protection contre le cache stampede peut également être vérifiée avec les logs MySQL. Après un cache miss suivi de nombreuses requêtes concurrentes, une seule requête doit normalement effectuer la lecture initiale depuis MySQL.
Exemple de table products :
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
price DECIMAL(10, 2) NOT NULL
);Ce projet permet de mettre en pratique :
- Architecture en couches
- Pattern Repository
- Pattern Service
- Pattern Controller
- Cache-Aside Pattern
- Redis
- TTL
- Invalidation de cache
- Distributed Lock
- Prévention du cache stampede
- Docker et Docker Compose
- Benchmark et analyse des performances
- Tests unitaires
- Tests d'intégration
- Middleware global de gestion des erreurs
- Validation avancée des données
- Rate limiting
- Monitoring avec Prometheus
- Visualisation avec Grafana
- Tests de charge
- Documentation OpenAPI / Swagger
- CI/CD