L’asynchrone : RabbitMQ, ou l’art de ne plus mourir ensemble
Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar
Contenu
Dernière séance : les événements du Lab 3 quittent la JVM et traversent le réseau via RabbitMQ. Les notifications consomment depuis une queue durable ; le module paiement déménage en Python (pika) — deuxième extraction du cours, cette fois asynchrone : sans façade, sans timeout, sans 503. Et la démonstration miroir de la panne du Lab 4 : un consommateur éteint, ce sont des messages qui attendent — pas une panne.
Nous avons laissé trois blessures ouvertes — volontairement :
livraison-service éteint \(\Rightarrow\) la confirmation meurt en 503. Compréhensible pour l’assignation (il faut une réponse)… mais les SMS et le paiement, eux, n’exigeaient aucune réponse — pourquoi partager le sort de qui que ce soit ?Le diagnostic commun
Ces trois douleurs ont la même racine : nos services ne savent communiquer qu’en direct et à l’instant. Il leur manque un endroit où déposer un fait pour qu’il soit traité quand le destinataire pourra — une boîte aux lettres durable.
Deux façons de joindre quelqu’un
L’appel téléphonique : il faut que l’autre décroche, maintenant. S’il dort, téléphone éteint — échec, rappelez plus tard. Le message WhatsApp : vous l’envoyez et passez à autre chose. Son téléphone est éteint ? Le message attend. À l’allumage, il reçoit tout, dans l’ordre — rien ne s’est perdu.
Dans notre lab, mot pour mot :
livraison-service « décroche » — sinon, 503 ;paiement-service arrêté : les messages attendent dans sa queue ;(RabbitMQ est le serveur qui garde les messages — la « boîte » entre l’envoyeur et le destinataire.)
Suivons CommandeConfirmee de bout en bout :
senresto.evenements) avec une étiquette (la clé de routage) : commande.confirmee.commande.* » ; celle du paiement, seulement commande.confirmee.À retenir
Le backend ne connaît que le guichet et l’étiquette. Il ignore qui est abonné, combien ils sont, et s’ils sont réveillés. C’est le découplage du Lab 3 — étendu au réseau et au temps.
Nos trois étiquettes face à nos deux boîtes :
| Clé du message | notifications | paiement |
|---|---|---|
(abonnée à commande.*) |
(abonnée à commande.confirmee) |
|
commande.creee |
reçoit | — |
commande.confirmee |
reçoit | reçoit |
commande.statut |
reçoit | — |
* de l’abonnement remplace un mot : commande.* attrape les trois clés.commande.# : zéro modification chez le backend — comme le module paiement du Lab 3, version réseau.| Exchange | le guichet de tri : reçoit chaque message et le route |
|---|---|
| Clé de routage | l’étiquette du message (commande.confirmee) |
| Queue durable | la boîte d’un consommateur ; survit aux redémarrages |
| Binding | l’abonnement d’une queue (commande.* = tout) |
| Ack | l’accusé du consommateur ; sans lui, le message reste |
À retenir
Cinq mots, et vous savez lire l’UI de gestion de RabbitMQ — votre poste d’observation de tout le lab. La garantie qu’ils construisent ensemble arrive à la slide suivante.
L’ack est l’accusé de réception du consommateur. Tout tient dans le moment où on l’envoie. Deux scénarios de crash :
La garantie at-least-once
Queue durable + message persistant + ack après traitement = chaque message est livré au moins une fois, à travers pannes et redémarrages. « Au moins » — donc parfois deux fois : c’est un choix, pas un défaut. L’alternative (au plus une fois) accepte de perdre des messages — inacceptable pour un paiement.
Nos modules publient déjà des faits (Lab 3). Un pont suffit — il écoute les événements internes et les réexpédie en JSON :
CommandeMetier n’a pas bougé d’une ligne : il publie ses faits comme au Lab 3, sans savoir qu’un pont les fait désormais voyager. Les frontières du modulith paient une dernière fois.Côté monolithe, notifications troque son @EventListener contre une queue durable — le Strangler, encore : l’ancien chemin reste sous @Profile("!amqp"), le nouveau vit sous @Profile("amqp") :
Le paiement quitte le monolithe pour son propre conteneur — pika, le dialecte AMQP de Python :
def sur_message(canal, methode, proprietes, corps):
evt = json.loads(corps)
print(f"[PAIEMENT] Encaissement mobile money simule : "
f"{evt['montantTotal']} FCFA pour {evt['numero']}")
canal.basic_ack(methode.delivery_tag) # l'ack APRES le traitement
canal.basic_consume(queue="paiement.confirmations", on_message_callback=sur_message)La deuxième extraction — comparez au Lab 4 !
Extraire paiement n’a demandé ni façade, ni timeout, ni 503 : personne n’attend sa réponse. L’extraction asynchrone est radicalement moins chère — quand la nature de l’échange le permet.
Rejouons le crash de la slide « ack » avec notre paiement :
paiement-service reçoit CommandeConfirmee pour CMD-2026-0042, affiche [PAIEMENT] Encaissement... 3\,500 FCFA…Le point important
Ce n’est pas un bug de RabbitMQ : c’est la conséquence logique du choix « ne jamais perdre » (at-least-once). La question n’est donc pas « est-ce que ça arrivera ? » mais « que fait mon code quand ça arrivera ? » — réponse à la slide suivante.
Définition
Un traitement est idempotent si le rejouer ne change rien : traiter deux fois le même message = le traiter une fois. C’est le prix de l’at-least-once — et la règle d’or de tout webhook de paiement réel (Wave, Orange Money…).
[PAIEMENT] deja traite, ignore.À retenir
L’idempotence n’est pas une option avancée : c’est le compagnon obligatoire de l’at-least-once. Chez nous : un set des identifiants traités. En production : une table dédiée, ou une contrainte d’unicité en base.
L’état du système à la fin du semestre — chaque brique a été ajoutée pour une raison précise :
| Conteneur | Techno | Pourquoi il existe |
|---|---|---|
backend |
Spring Boot | le monolithe modulaire : catalogue, commandes, notifications |
livraison-service |
Python FastAPI | extraction synchrone (Lab 4) : écosystème géo, déploiement à part |
paiement-service |
Python pika | extraction asynchrone (Lab 5) : sans façade ni 503 |
rabbitmq |
Erlang | la boîte aux lettres durable entre les trois |
postgres |
— | les données du monolithe |
frontend |
React | l’interface — et la carte qui a tout révélé |
Trois langages qui coopèrent sans se connaître : par contrats HTTP et par messages — jamais par partage de code.
Départ : votre lab4-final. Rôles : l’Appariteur pilote paiement-service (pika) ; le Notaire les queues et bindings ; l’Huissier compose + RabbitMQ ; le Rédacteur l’UI de gestion et la preuve du rattrapage ; le Doyen la démo finale et l’ADR no5 : « Synchrone ou asynchrone ? » — le tableau de décision de toutes vos intégrations.
À retenir de la séance
Un broker transforme les pannes en retards : queue durable + ack = at-least-once, dont le prix est l’idempotence. La règle de décision : il faut une réponse \(\Rightarrow\) synchrone (et l’on assume le couplage temporel) ; on informe d’un fait \(\Rightarrow\) asynchrone (et les consommateurs vivent leur vie).
Le chemin parcouru en cinq séances
Un God controller qui souffrait (S1) \(\to\) des couches qui séparent la technique (S1) \(\to\) un hexagone qui protège le métier (S2) \(\to\) des modules aux frontières gardées par un test (S3) \(\to\) un premier microservice, extrait avec précaution et payé au prix du synchrone (S4) \(\to\) des événements qui voyagent, et des services qui ne meurent plus ensemble (S5). Vous n’avez pas appris des architectures : vous avez appris à faire évoluer une architecture — sous tests, par étapes, chaque décision tracée dans un ADR. C’est exactement le métier.