Concept #028
ArchitectureStateful vs Stateless
- architecture
- scalabilité
- web
Le principe en une phrase
Un service stateful conserve des informations entre les requêtes (il "se souvient" du client). Un service stateless ne conserve rien : chaque requête est autonome et contient toutes les informations nécessaires à son traitement.
C'est une distinction fondamentale en architecture logicielle qui impacte directement la scalabilité, la résilience et la complexité de votre système.
L'analogie de l'atelier
Stateful, c'est le confort de l'atelier. Vous arrivez les mains vides, vos outils sont sur l'établi, votre projet en cours est posé là où vous l'aviez laissé. C'est rapide et confortable. Mais vous êtes piégé : vous ne pouvez pas bosser ailleurs sans retourner chercher vos affaires. Si l'atelier brûle, tout est perdu.
Stateless, c'est le travailleur nomade. Il arrive avec sa mallette contenant tout le nécessaire. Il peut travailler dans n'importe quel bureau, n'importe quel co-working. Si un bureau ferme, il va dans un autre sans rien perdre. Le prix : il trimballe tout à chaque déplacement.
Stateful en pratique
Un serveur stateful garde des données en mémoire entre les requêtes d'un même client. L'exemple classique : les sessions serveur.
import express from "express";
import session from "express-session";
const app = express();
app.use(session({
secret: "my-secret",
resave: false,
saveUninitialized: false,
}));
// Le serveur "se souvient" du panier entre les requêtes
app.post("/api/cart/add", (req, res) => {
if (!req.session.cart) {
req.session.cart = [];
}
req.session.cart.push(req.body.item);
res.json({ cart: req.session.cart });
});
app.get("/api/cart", (req, res) => {
res.json({ cart: req.session.cart ?? [] });
});Le panier est stocké dans la session, en mémoire du serveur. Le client envoie juste un cookie de session ; le serveur retrouve les données associées.
Le problème de la scalabilité
Imaginons que le trafic augmente. Vous ajoutez un deuxième serveur derrière un load balancer :
Client → Load Balancer → Serveur A (contient la session)
→ Serveur B (ne connaît pas la session)
Si la première requête atterrit sur le serveur A et la suivante sur le serveur B, le panier est introuvable. Le serveur B ne "connaît" pas ce client.
Solutions courantes (toutes avec des compromis) :
- Sticky sessions : le load balancer envoie toujours le même client au même serveur. Mais si ce serveur tombe, la session est perdue.
- Réplication de sessions : les serveurs synchronisent leurs sessions entre eux. Complexe et coûteux en réseau.
- Session store externe (Redis, Memcached) : les sessions sont stockées dans un service partagé. Ajoute une dépendance externe mais résout le problème.
Stateless en pratique
Un serveur stateless ne conserve rien entre les requêtes. Chaque requête contient toutes les informations nécessaires. L'exemple le plus courant : les JWT (JSON Web Tokens).
import jwt from "jsonwebtoken";
const SECRET = process.env.JWT_SECRET!;
// Login : le serveur génère un token contenant les infos nécessaires
app.post("/api/login", async (req, res) => {
const user = await authenticateUser(req.body.email, req.body.password);
if (!user) return res.status(401).json({ error: "Invalid credentials" });
// Toutes les infos sont DANS le token
const token = jwt.sign(
{ userId: user.id, role: user.role },
SECRET,
{ expiresIn: "1h" },
);
res.json({ token });
});
// Chaque requête est autonome : le token contient tout
app.get("/api/profile", (req, res) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "No token" });
const payload = jwt.verify(token, SECRET);
// Le serveur n'a rien en mémoire — il décode le token à chaque requête
const user = await userRepository.findById(payload.userId);
res.json(user);
});Le serveur ne stocke aucune session. Le token JWT contient les informations d'identité et circule avec chaque requête. N'importe quel serveur peut le vérifier.
Scalabilité horizontale
Avec un service stateless, ajouter des serveurs est trivial :
Client (avec JWT) → Load Balancer → Serveur A ← peut traiter
→ Serveur B ← peut traiter
→ Serveur C ← peut traiter
Tous les serveurs sont interchangeables. Si le serveur A tombe, la requête suivante va sur B ou C sans aucune perte. Pas de sticky sessions, pas de réplication, pas de session store.
Comparaison détaillée
| Stateful | Stateless | |
|---|---|---|
| Etat | Stocké sur le serveur | Porté par le client (token, headers) |
| Scalabilité horizontale | Difficile (sticky sessions, réplication) | Triviale (serveurs interchangeables) |
| Résilience | Perte d'état si le serveur tombe | Aucun impact, un autre serveur prend le relais |
| Performance | Rapide (données en mémoire locale) | Légèrement plus lent (décodage du token, requête BDD) |
| Complexité réseau | Session store, réplication | Token plus gros, re-validation à chaque requête |
| Révocation | Facile (supprimer la session) | Difficile (le token est valide jusqu'à expiration) |
Quand choisir l'un ou l'autre ?
Préférer le Stateless quand :
- Vous devez scaler horizontalement : microservices, API REST, applications cloud. C'est le modèle par défaut des architectures modernes.
- La résilience est critique : chaque serveur peut tomber sans impacter les utilisateurs.
- Vous utilisez des conteneurs ou du serverless : les instances sont éphémères par nature. Stocker de l'état en mémoire n'a pas de sens quand l'instance peut disparaître à tout moment.
Préférer le Stateful quand :
- Les interactions sont longues et continues : WebSockets, jeux en ligne, éditeurs collaboratifs en temps réel. Maintenir une connexion avec état est plus naturel et performant.
- La latence est critique : accéder à des données en mémoire locale est plus rapide qu'un aller-retour vers Redis ou une base de données.
- Le protocole l'exige : certains protocoles (FTP, certaines connexions de base de données) sont intrinsèquement stateful.
Le monde réel : un mélange des deux
En pratique, les architectures modernes combinent les deux approches. Les serveurs web sont stateless (API REST avec JWT), mais l'état est externalisé dans des services spécialisés :
[Serveurs API stateless] → [Redis] (cache, sessions partagées)
→ [PostgreSQL] (état persistant)
→ [Kafka] (état des événements)
Les serveurs eux-mêmes sont stateless et interchangeables. L'état vit dans des systèmes dédiés, conçus pour le gérer de manière fiable et répliquée.
C'est la meilleure des deux mondes : la scalabilité du stateless avec la persistance d'un état centralisé et résilient.
HTTP : un protocole stateless par design
Le protocole HTTP est fondamentalement stateless. Chaque requête est indépendante ; le serveur ne "se souvient" de rien entre deux requêtes. Les cookies ont été inventés précisément pour contourner cette limitation et permettre aux serveurs d'identifier les clients d'une requête à l'autre.
C'est pour cela que le web a naturellement convergé vers des architectures stateless : le protocole sous-jacent l'encourage.
Résumé
Stateful et Stateless ne sont pas des approches bonnes ou mauvaises en soi. Elles répondent à des contraintes différentes. Le Stateful offre le confort et la performance d'un état local. Le Stateless offre la scalabilité et la résilience d'une architecture sans attache. Les architectures modernes externalisent l'état dans des services spécialisés, permettant aux serveurs applicatifs de rester stateless tout en bénéficiant d'un état partagé et persistant.