11 patterns de System Design à connaître en 2026
Load balancer, CDN, files de messages, CQRS : quand utiliser ces onze patterns d’architecture, et quelles contraintes prévoir avant de les ajouter ?
Partir du symptôme de l’application
Une API ralentit pendant une campagne commerciale. Faut-il ajouter un serveur, mettre les réponses en cache ou déplacer un traitement dans une file ? Ces choix répondent à des contraintes différentes. Je propose de parcourir onze patterns de System Design en reliant chacun à une situation concrète et au coût qu’il ajoute.
La sélection couvre le réseau, mais aussi le stockage et l’organisation des traitements. CQRS et le sharding dépassent ainsi le seul routage des requêtes. Les exemples décrivent des situations fictives.
1. Load balancer : répartir les requêtes
Un répartiteur de charge distribue le trafic entre plusieurs instances d’un service. Il peut alterner les requêtes entre les serveurs, ou tenir compte des connexions en cours. Pour une application web dont le serveur sature, il permet d’utiliser plusieurs instances, à condition que celles-ci puissent traiter les mêmes demandes.
La haute disponibilité demande aussi de détecter les instances défaillantes et de prévoir la panne du répartiteur lui-même. Les sessions stockées uniquement dans la mémoire d’une instance compliquent le basculement. Ajouter des serveurs applicatifs ne corrige pas non plus une base déjà saturée. Je commencerais donc par identifier où les requêtes attendent réellement.
2. CDN : rapprocher le contenu des utilisateurs
Un Content Delivery Network sert des copies de contenu depuis des points de présence répartis géographiquement. Une image ou un fichier JavaScript déjà en cache peut être livré sans solliciter le serveur d’origine. C’est utile pour un catalogue consulté depuis plusieurs régions ou pour distribuer des médias. Le point choisi dépend du routage et de la disponibilité, pas seulement de la distance sur une carte.
Il faut définir ce qui peut être partagé et pendant combien de temps. Des fichiers statiques dont le nom contient une empreinte peuvent rester longtemps en cache. Une réponse personnalisée exige une politique adaptée : no-store interdit son stockage, tandis que private interdit son stockage dans un cache partagé. Une réponse absente du cache doit encore être récupérée à l’origine.
3. Message queue : différer le travail
Une file de messages sépare la réception d’une demande de son traitement. Dans un export volumineux, l’API peut enregistrer un travail à effectuer, puis un consommateur produit le fichier. La file absorbe un pic temporaire si les consommateurs peuvent ensuite rattraper le retard. Une arrivée durablement supérieure à leur capacité fait croître l’attente.
La fiabilité dépend de la configuration de durabilité et des confirmations de publication, puis des acquittements après traitement. Une livraison peut se répéter : le consommateur doit éviter de créer deux fois la même facture, par exemple en reconnaissant un identifiant déjà traité. Il faut aussi prévoir le sort des messages qui échouent durablement.
RabbitMQ propose notamment des files de travail. Kafka repose sur des journaux d’événements partitionnés et conservés selon une politique de rétention ; les consommateurs suivent leur position de lecture. Il peut répartir du travail au sein d’un groupe, mais sa relecture et ses groupes indépendants lui donnent des usages différents d’une file consommée puis acquittée.
4. Rate limiter : fixer un budget de requêtes
Un limiteur contrôle le débit autorisé pour un utilisateur, une clé d’API ou une autre identité. On peut, par exemple, fixer un quota par minute sur une route coûteuse, avec une tolérance pour les petites rafales. Cette règle aide à partager les ressources et à contenir certains abus. Une réponse HTTP 429 indique un dépassement ; Retry-After peut préciser le délai avant une nouvelle tentative.
Le choix de la clé compte : une limite par adresse IP peut pénaliser plusieurs personnes derrière le même accès réseau. Avec plusieurs instances, un compteur local par serveur multiplie potentiellement le quota effectif. Il faut décider comment coordonner les limites et quel comportement adopter si le stockage des compteurs devient indisponible.
5. Circuit breaker : interrompre les appels voués à échouer
Lorsqu’une dépendance accumule les erreurs, un disjoncteur ouvre le circuit : les appels suivants échouent rapidement pendant un délai défini. Une phase de test laisse ensuite passer quelques requêtes pour évaluer la reprise. Ce mécanisme limite les ressources immobilisées par une API externe défaillante et aide à contenir les pannes en cascade.
Le service appelant doit prévoir ce que voit l’utilisateur pendant l’interruption. Une estimation de livraison peut devenir temporairement indisponible ; un paiement incertain doit conserver un état explicite. Les délais d’attente restent nécessaires. Les nouvelles tentatives doivent être bornées et tenir compte du circuit ouvert pour éviter d’aggraver la panne.
6. Database sharding : répartir les données
Le sharding distribue des sous-ensembles de données entre plusieurs partitions hébergées sur des serveurs distincts. Une application multiclient pourrait utiliser l’identifiant d’organisation pour choisir le fragment. Cette approche devient pertinente lorsque les volumes ou le débit dépassent les capacités raisonnables d’un seul serveur, après examen des requêtes et des index.
La clé choisie détermine la répartition de la charge. Si une organisation concentre l’activité, son fragment peut saturer pendant que les autres restent peu utilisés. Les jointures et transactions entre fragments se compliquent, tout comme le rééquilibrage. La réplication conserve des copies ; le sharding répartit les données. On peut combiner les deux pour des objectifs différents.
7. Caching layer : éviter les lectures répétées
Un cache applicatif, par exemple avec Redis ou Memcached, conserve des données fréquemment consultées. Avec le modèle cache-aside, l’application cherche d’abord dans le cache. En cas d’absence, elle lit la base, remplit le cache et renvoie la réponse. Un catalogue souvent lu et peu modifié peut bénéficier de cette approche.
Le point délicat est la fraîcheur. Une durée de vie limite l’ancienneté d’une entrée, sans garantir une lecture à jour après chaque écriture. Il faut organiser l’invalidation et prévoir la panne du cache : un retour massif vers la base peut la saturer. Je définirais d’abord le retard acceptable pour la donnée concernée, puis la stratégie d’expiration.
8. API gateway : organiser l’entrée des API
Une passerelle expose une adresse d’entrée stable devant plusieurs services. Elle route, par exemple, /catalogue vers un service et /commandes vers un autre. Selon sa configuration, elle peut vérifier des jetons d’authentification ou appliquer des quotas. Elle devient utile quand plusieurs API doivent présenter des règles communes aux clients.
Les services doivent conserver les autorisations métier liées aux données : pouvoir appeler une route ne suffit pas à autoriser la lecture de n’importe quelle commande. La passerelle ajoute aussi un élément critique sur le trajet. Sa disponibilité et ses changements de configuration demandent autant d’attention que ceux des services qu’elle expose.
9. Reverse proxy : relayer les appels côté serveur
Un reverse proxy reçoit les requêtes pour le compte des serveurs d’origine. Il peut terminer la connexion TLS, relayer les appels vers l’application et, selon sa configuration, compresser ou mettre en cache les réponses. C’est utile pour exposer une application tout en centralisant certains réglages HTTP.
Les rôles se recouvrent : un même NGINX peut agir comme reverse proxy et répartiteur de charge. Une API gateway peut s’appuyer sur ce fonctionnement en y ajoutant des politiques propres aux API. Ces termes ne désignent donc pas nécessairement des machines séparées. Il faut aussi contrôler les en-têtes transmis et empêcher l’accès direct à l’origine si la protection repose sur le proxy.
10. Event-driven architecture : réagir à un fait métier
Dans une architecture événementielle, un service publie un fait, tel que CommandeConfirmée. D’autres traitements peuvent y réagir pour envoyer une notification ou alimenter des statistiques. Le producteur n’a pas besoin de connaître tous les consommateurs. Une commande demande une action ; un événement décrit quelque chose qui s’est produit.
Une file est un mécanisme de transport possible ; l’architecture événementielle organise les interactions autour de ces faits. Il faut décider comment faire évoluer leur format, gérer les doublons et interpréter leur ordre. Le temps réel dépend du délai de livraison et de traitement. Un consommateur en retard ne donne pas une vue immédiatement à jour.
Une autre difficulté apparaît entre l’écriture en base et la publication : un incident peut laisser une commande enregistrée sans événement. Le pattern transactional outbox consiste à enregistrer le fait à publier dans la même transaction que la modification métier, puis à le transmettre séparément. Les consommateurs doivent toujours supporter les livraisons répétées.
11. CQRS : distinguer les modèles de lecture et d’écriture
CQRS (Command Query Responsibility Segregation) sépare les commandes qui modifient l’état des requêtes qui le consultent, avec des modèles adaptés à chaque usage. Dans une réservation, l’écriture vérifie les règles de disponibilité. La lecture peut proposer une représentation préparée pour afficher le planning. Cette séparation devient intéressante lorsque les règles métier et les besoins de consultation divergent fortement.
Les deux modèles peuvent partager la même base. CQRS n’impose ni Kafka, ni l’event sourcing, qui conserve les changements d’état sous forme d’événements. Si une projection de lecture est alimentée de façon asynchrone, elle peut afficher un état en retard. Il faut accepter ce délai et gérer la reconstruction des projections. Pour un CRUD simple, le coût de cette séparation peut dépasser son intérêt.
Ce que j’ajouterais à la liste
Pour concevoir une application, je compléterais cette sélection par les délais d’attente et les nouvelles tentatives avec attente progressive et part aléatoire, afin d’éviter que tous les clients recommencent ensemble. L’idempotence, qui permet de répéter une opération sans répéter son effet métier, devient essentielle dès qu’un résultat réseau reste incertain.
Avant d’ajouter un composant, prenez un parcours précis, par exemple la validation d’une commande. Notez chaque appel distant, le délai maximal acceptable et ce que l’utilisateur verra en cas d’échec. Mesurez ensuite les attentes et les erreurs sur ce parcours. Cela permet de choisir un premier pattern sur un problème observable, puis de vérifier son effet.
Sources et liens utiles
- NGINX : répartition de charge HTTP (pattern 1)
- MDN : rôle d’un CDN (pattern 2)
- MDN : Cache-Control et caches partagés (pattern 2)
- RabbitMQ : acquittements et confirmations de publication (pattern 3)
- Apache Kafka : événements, rétention et consommateurs (pattern 3)
- MDN : HTTP 429 et Retry-After (pattern 4)
- NGINX : limites par clé et gestion des rafales (pattern 4)
- Microsoft : circuit breaker (pattern 5)
- Microsoft : sharding et choix des partitions (pattern 6)
- Microsoft : cache-aside et cohérence (pattern 7)
- Microsoft : routage par une passerelle (pattern 8)
- Microsoft : authentification et politiques des API gateways (pattern 8)
- NGINX : fonctionnement d’un reverse proxy (pattern 9)
- Microsoft : architecture événementielle (pattern 10)
- Chris Richardson : transactional outbox (pattern 10)
- Microsoft : CQRS, base partagée et projections (pattern 11)
- AWS Builders’ Library : délais d’attente, nouvelles tentatives et jitter