Aller au contenu
Cloud high

Loose coupling et stateless design : les 2 piliers cloud-native

15 min de lecture

L’élasticité automatique, le déploiement sans interruption et la tolérance aux pannes ne tombent pas du ciel — ils reposent sur deux principes architecturaux non négociables : le loose coupling (couplage faible entre services) et le stateless design (pas d’état local critique sur les serveurs applicatifs). Sans ces deux propriétés, vous pouvez configurer un autoscaler ou un blue/green deployment, ils ne fonctionneront pas correctement. Cette page explique les deux principes, leurs implications concrètes, les anti-patterns classiques, et défend une opinion claire : ces deux disciplines sont plus dures qu’elles n’en ont l’air, et la majorité des architectures « cloud-native » que l’on rencontre les ratent partiellement.

  • Le loose coupling : définition, bénéfices, patterns d’implémentation
  • Le stateless design : pourquoi c’est exigé pour l’élasticité, comment l’atteindre
  • Les 5 anti-patterns classiques d’état local que l’on rencontre fréquemment
  • Comment externaliser sessions, cache, fichiers, locks
  • Le piège du « presque stateless » qui tue les déploiements

Prérequis : avoir compris l’élasticité. Si besoin, lisez d’abord Élasticité vs scalabilité.

Le loose coupling désigne une architecture où les composants communiquent via des interfaces explicites et stables (API, messages, events), sans dépendance de mémoire partagée, de fichiers locaux, ou de structures internes mutuelles.

Deux services en couplage faible peuvent être :

  • Déployés indépendamment sans coordonner leurs releases.
  • Scalés indépendamment selon leurs propres profils de charge.
  • Reécrits dans des langages différents sans casser l’autre.
  • Maintenus par des équipes différentes sans réunion permanente.

C’est l’antithèse du tight coupling où un changement dans un service casse mécaniquement les autres parce qu’ils partagent des structures internes (objets sérialisés, schémas DB, fichiers locaux partagés).

Deux personnes qui communiquent par téléphone sont en couplage faible : elles peuvent être à l’autre bout du monde, l’une peut raccrocher et rappeler plus tard, elles peuvent changer de téléphone sans changer leur identité. La conversation est asymétrique et résiliente.

Deux personnes qui communiquent en se criant dessus dans la même pièce sont en couplage fort : si l’une bouge, l’autre n’entend plus, si l’une fatigue ses cordes vocales tout s’arrête, elles doivent rester physiquement proches en permanence.

Le cloud impose le téléphone — pas le cri.

API REST ou gRPC. La forme la plus courante de loose coupling. Un service expose une API contractuelle (versionnée, documentée), les autres l’appellent. Tant que le contrat tient, l’implémentation interne peut changer librement.

Message queues. RabbitMQ, AWS SQS, Azure Service Bus, GCP Pub/Sub. Un service publie un message, un autre le consomme sans connaître l’identité du producteur ou consommateur. Couplage encore plus faible que REST.

Event-driven. Kafka, AWS EventBridge, Azure Event Grid. Un service émet un événement, plusieurs services peuvent y réagir indépendamment. Très adapté aux architectures microservices à grande échelle.

Service mesh. Istio, Linkerd, Consul. Couche d’infrastructure qui formalise et observe les communications entre services. Permet de gérer mTLS, retry, circuit breakers, observability sans modifier le code applicatif.

BénéficeSans loose couplingAvec loose coupling
DéploiementCoordination de toutes les équipes pour chaque releaseChaque service livre indépendamment
ScalingScaling de tout le monolithe ou rienScaling fin par service
RésilienceUne panne d’un composant tue toutCircuit breaker isole la panne
Évolution techniqueMigration techno = réécriture de toutMigration techno = service par service
OnboardingNouveau dev doit comprendre tout le systèmeNouveau dev productif sur un service en 2 jours

2. Stateless design — pas d’état local critique

Section intitulée « 2. Stateless design — pas d’état local critique »

Une application est stateless si chaque requête peut être traitée par n’importe quelle instance du service, sans information préalable conservée en local. L’état nécessaire (session utilisateur, données métier, fichiers temporaires) vit ailleurs, dans des stockages externes (base, cache, object storage).

Une application stateful au contraire conserve de l’état local critique sur l’instance qui traite la requête. Si cette instance disparaît, l’état disparaît avec elle.

Quand l’autoscaler tue une instance au scale-in :

  • Application stateless : aucune perte. Les utilisateurs en cours sont redirigés vers une autre instance qui reprend le contexte depuis le stockage externe.
  • Application stateful : les utilisateurs perdent leur session, leur panier, leur progression. Vu côté utilisateur, c’est une panne même si techniquement le service est UP.

Quand le scaling-out lance une nouvelle instance :

  • Application stateless : la nouvelle instance est immédiatement utile. Les requêtes y arrivent et fonctionnent.
  • Application stateful : la nouvelle instance ne sait rien des utilisateurs existants, elle ne peut pas les servir tant qu’elle n’a pas reconstruit le contexte.

🔐 Sessions utilisateurs

Le piège : sessions stockées en mémoire de l’instance ou dans des fichiers locaux du serveur (/tmp/sessions/).

Solution : un cache partagé entre toutes les instances (Redis ou Memcached). Tous les serveurs lisent et écrivent au même endroit. Une session survit au crash d’une instance.

Alternative : sessions sous forme de token signé stocké côté navigateur (pattern JWT), sans stockage serveur du tout — plus complexe à invalider mais réellement stateless.

📦 Caches applicatifs

Le piège : un cache local en mémoire dans chaque instance applicative. Chaque instance recharge les mêmes données depuis la base, multipliant la charge sur la base.

Solution : un cache partagé entre toutes les instances (Redis ou Memcached). Une seule charge depuis la base, partagée par tout le monde.

Alternative hybride : cache local court (30 secondes) pour les requêtes très fréquentes + cache partagé pour le reste.

📤 Fichiers uploadés

Le piège : uploads écrits sur le disque local de l’instance (/var/www/uploads/ par exemple). Un fichier uploadé sur l’instance A n’est pas visible depuis l’instance B.

Solution : stockage objet partagé (S3, OOS, Blob Storage) avec URL signées. Aucun fichier n’est stocké sur les instances applicatives elles-mêmes.

🔒 Locks et synchronisation

Le piège : verrous en mémoire d’une instance (les synchronized Java, Mutex Go ou Python). Le verrou ne vaut que pour cette instance, les autres instances peuvent agir en parallèle sans le voir.

Solution : un verrou partagé entre toutes les instances (lock dans Redis, ou contrainte d’unicité en base). La cohérence vaut entre toutes les instances.

Un cas souvent oublié : les fichiers de configuration mutables locaux. Si une instance modifie son config.yaml en réponse à une commande admin, cette modification ne se propage pas aux autres instances. Un autoscaler qui crée une nouvelle instance la lance avec la config initiale, pas la config courante. Les comportements divergent silencieusement.

Solution : configuration centralisée externe — Consul, etcd, AWS Parameter Store, Azure App Configuration, ou tout simplement variables d’environnement injectées au démarrage par l’orchestrateur.

Les sticky sessions sont une fonctionnalité des load balancers qui envoient toujours le même utilisateur sur la même instance. C’est techniquement possible, et c’est souvent vendu comme solution au stateful — « on garde nos sessions locales, on active sticky sessions, problème résolu ».

C’est une fausse bonne solution. Trois raisons.

Raison 1 — Le scale-in casse tout. Quand l’instance qui détient la session est tuée par l’autoscaler, l’utilisateur perd sa session. Sticky sessions n’ont pas résolu le problème, elles l’ont masqué tant qu’aucun scale-in n’arrive.

Raison 2 — La répartition de charge devient inefficace. Si un utilisateur ouvre 20 onglets et fait beaucoup de trafic, toute la charge va sur la même instance. Les autres instances sont sous-utilisées.

Raison 3 — Le déploiement zéro-downtime devient impossible. Quand on remplace l’instance d’un utilisateur sticky, sa session disparaît. Pour un blue/green deployment, c’est rédhibitoire.

Conclusion : sticky sessions sont acceptables comme optimisation de cache (pour bénéficier du cache CPU local), pas comme solution de session. Les sessions doivent vivre dans Redis, point final.

Voici les 5 problèmes que l’on retrouve souvent dans des architectures qui se prétendent « cloud-native » sans en avoir tous les prérequis.

Anti-pattern 1 — Sessions sur disque local. Le code stocke les sessions utilisateur dans des fichiers locaux du serveur (/tmp/sessions/, /var/lib/sessions/). L’application semble bien fonctionner en multi-AZ tant qu’aucune instance ne meurt — ce qui finit toujours par arriver, et là tous les utilisateurs connectés à cette instance perdent leur session.

Anti-pattern 2 — Cache local non partagé. Les développeurs ont mis un cache en mémoire propre à chaque instance parce que c’était simple. Chaque instance recharge les mêmes données depuis la base, multipliant la charge. Sous trafic, la base sature avant que les caches locaux ne se remplissent.

Anti-pattern 3 — Uploads sur disque local. Le code écrit les fichiers uploadés dans un dossier local de l’instance (/var/www/uploads/ par exemple). Si on déploie sur 3 instances, un fichier uploadé sur l’instance A n’est pas visible depuis l’instance B. La fonctionnalité « marche en dev » avec une seule instance et plante en production.

Anti-pattern 4 — Crons sur une instance désignée. Pour éviter que le cron tourne 5 fois sur 5 instances, on l’installe sur une seule instance. Si elle tombe, le cron ne tourne plus. La bonne pratique : un planificateur partagé au niveau de l’orchestrateur (Kubernetes a un mécanisme CronJob natif par exemple), pas dans une instance applicative.

Anti-pattern 5 — Versions d’API incompatibles entre instances. Lors d’un déploiement progressif (rolling update), certaines requêtes arrivent sur l’ancienne version, d’autres sur la nouvelle. Si les contrats d’API ne sont pas rétro-compatibles, des erreurs aléatoires apparaissent pendant la fenêtre de déploiement. La discipline : maintenir les anciens contrats actifs pendant au moins une release, et utiliser des feature flags pour activer les nouveaux comportements une fois tout le monde sur la nouvelle version.

Le piège le plus subtil est l’application « presque stateless » — celle qui externalise les sessions et les uploads, mais conserve une petite chose locale qu’on n’a pas vue. Quelques exemples :

  • Un fichier de flags applicatifs modifiable à chaud localement.
  • Une liste de connexions WebSocket ouvertes dans la mémoire de l’instance.
  • Des timers locaux qui programment des actions futures.
  • Un état d’animation ou de wizard multi-étapes mémorisé dans la session navigateur, mais aussi côté serveur.

Ces petites entorses au stateless passent les tests automatisés (qui ne stressent qu’une instance) et fonctionnent en dev (où il n’y a souvent qu’une instance). Elles révèlent des bugs étranges en production sous charge — utilisateurs déconnectés aléatoirement, animations qui sautent, actions perdues.

Discipline pratique : avant de déclarer une application stateless, faire l’exercice suivant — « si je tue 50 % de mes instances en plein trafic, qu’est-ce qui casse pour les utilisateurs ? ». La réponse honnête révèle souvent des dépendances locales oubliées.

6. Comment tenir la discipline stateless dans la durée

Section intitulée « 6. Comment tenir la discipline stateless dans la durée »

Le loose coupling et le stateless sont des principes simples à énoncer mais difficiles à maintenir au fil des évolutions. Quelques points d’attention permettent de garder la cohérence sans tomber dans les pièges classiques.

Distinguer loose coupling apparent et réel. Une architecture présentée comme microservices peut, dans les faits, partager un schéma de base de données entre plusieurs services. Quand une équipe modifie une table, toutes les autres équipes doivent suivre. Cette dépendance cachée annule le bénéfice du découpage en services. La discipline visée est une base par service (database per service), avec une API stable comme seule interface. C’est exigeant et demande une vraie séparation des données, pas seulement du code.

Identifier les états résiduels. Beaucoup d’applications décrites comme stateless conservent en pratique au moins un cas oublié : un cron qui ne tourne que sur une instance désignée, des connexions WebSocket en mémoire, un cache local non partagé, un upload temporaire sur disque. L’objectif n’est pas la pureté absolue mais l’identification consciente de ces points d’état, et la mise en place de mitigations explicites (queue partagée, store externe, mécanisme de leader election).

Investir tôt plutôt que rétrofitter plus tard. Démarrer en stateful « parce que c’est plus simple » avec l’intention de migrer demande, le moment venu, de toucher à toutes les couches en même temps : sessions, caches, locks, uploads, jobs périodiques. Démarrer stateless dès le départ demande un peu plus de réflexion initiale mais évite ce chantier transversal. C’est la trajectoire la plus économique en charge cognitive cumulée.

Faire l’exercice du chaos mental. Avant de déclarer une application stateless, se poser la question : « si je tue 50 % de mes instances en plein trafic, qu’est-ce qui casse ? ». La réponse honnête révèle souvent des dépendances locales oubliées. Cet exercice peut être complété par un test réel sur un environnement de pré-production, en arrêtant brutalement des instances et en observant le comportement utilisateur.

  • Loose coupling = communication entre services via interfaces stables (API, messages, events), sans dépendance de structure interne.
  • Stateless design = pas d’état local critique sur les serveurs applicatifs, l’état vit dans des stockages externes (Redis, S3, base).
  • 4 catégories d’état à externaliser : sessions, caches, fichiers uploadés, locks.
  • Sticky sessions sont une fausse bonne solution — elles masquent le problème jusqu’au prochain scale-in.
  • 5 anti-patterns récurrents : sessions disque, cache in-memory non partagé, uploads locaux, cron sur instance désignée, build-id baked.
  • Le « presque stateless » est le piège classique — l’exercice mental « si je tue 50 % des instances » révèle les états résiduels.
  • Démarrer stateless dès le départ coûte moins cher en charge cognitive cumulée que de rétrofitter une architecture stateful.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn