Le guide pratique A→Z : CQRS, Event Sourcing, Process Managers et Sagas expliqués sur un vrai projet, avec les trade-offs honnêtes et les leçons apprises.
Pourquoi ce guide
CQRS et Event Sourcing : deux termes omniprésents dans les conférences d'architecture, les offres d'emploi, les communautés DDD (Domain-Driven Design — méthode qui structure le code autour des concepts métier plutôt que des couches techniques). Mais la plupart des ressources donnent soit une définition abstraite, soit cent lignes de code sans contexte. Ce guide, produit par l'équipe patterns & practices de Microsoft, fait quelque chose de plus rare : il documente un vrai projet de bout en bout, avec les impasses, les repentirs et les regrets. Greg Young, celui qui a inventé le terme CQRS, a écrit la préface. Et le guide est gratuit.
Le projet est un système de gestion de conférences (Contoso Conference Management) construit sur .NET et Windows Azure. Le guide se divise en deux pistes : les chapitres Reference expliquent chaque concept depuis ses bases ; les chapitres Journey suivent l'implémentation réelle à travers plusieurs itérations. On peut lire les Reference seuls pour une introduction conceptuelle, ou les entrelacer avec les Journey pour une lecture théorie-et-pratique en parallèle.
Les idées qui restent
Six idées à emporter de ce guide, dans l'ordre de la logique du livre.
1CQRS : deux objets là où il n'y en avait qu'un
Imaginez une boutique en ligne avec une seule classe Produit. Le même objet fait deux métiers sans rapport. Métier un : modifier les données. À chaque vente il décrémente le stock, vérifie qu'il ne passe pas sous zéro, bloque la commande d'un article retiré du catalogue. Ça, c'est de la vraie logique métier. Métier deux : afficher les données. La page d'accueil veut le nom, le prix, une vignette et la note moyenne des avis, le tout joint et rapide. À mesure que la boutique grossit, ces deux métiers tirent la classe dans des directions opposées : la forme pratique pour mettre à jour le stock en sécurité est une forme maladroite pour afficher une page d'accueil, et chaque changement d'un côté menace de casser l'autre. C'est cette tension que CQRS résout.
D'abord, trois définitions simples.
- Commande : un ordre de changer quelque chose — « décrémente le stock du produit 157 de un ».
- Requête : une demande d'information qui ne change rien — « donne-moi la liste produits de la page d'accueil ».
- Handler : la fonction qui reçoit l'un ou l'autre et fait le travail : un handler de commande applique le changement, un handler de requête va chercher la donnée et la renvoie.
CQRS (Command Query Responsibility Segregation) dit : arrêtez de forcer un seul objet à faire les deux. Greg Young, qui a nommé le pattern, le formule le plus simplement possible : « CQRS est simplement la création de deux objets là où il n'y en avait précédemment qu'un. » Un objet possède le côté écriture (il traite les commandes et porte les règles métier). Un autre objet possède le côté lecture (il traite les requêtes et renvoie la donnée dans la forme exacte dont l'écran a besoin).
Concrètement : le côté écriture garde les règles, « le stock ne peut pas être négatif », « un produit retiré ne peut pas être commandé ». Le côté lecture n'est qu'une copie prête à afficher, une table plate où une seule ligne contient déjà tout ce que montre la page d'accueil, sans jointure, sans règle, taillée pour la vitesse. Les deux peuvent vivre dans des bases différentes et scaler séparément. Le coût : on maintient désormais deux modèles au lieu d'un, et il faut les tenir synchronisés, c'est du vrai travail. D'où l'avertissement présent dès le guide : Udi Dahan a écrit « La plupart des gens qui utilisent CQRS n'auraient pas dû le faire. » Ça paie quand le côté écriture a des règles vraiment complexes et qui changent souvent, ou quand lectures et écritures se font à des volumes très différents. Pour un banal écran formulaire-et-tableau, c'est du surcoût pur : gardez un seul objet.
2Event Sourcing : stocker ce qui s'est passé, pas juste l'état final
Regardez votre relevé bancaire. Il ne stocke pas une seule ligne « solde = 350 € ». Il stocke l'historique : +1000 € salaire, −200 € loyer, −150 € courses, −300 € vacances. Le solde n'est stocké nulle part, il se calcule en additionnant l'historique. C'est exactement l'idée de l'Event Sourcing.
Un événement est un fait déjà arrivé, écrit au passé et jamais modifié ensuite, « 300 € ont été retirés ». Il est immuable : on ne corrige pas un fait passé, comme la banque ne réécrit jamais la transaction d'hier. Comparez les deux façons de sauvegarder. La façon traditionnelle, celle qu'utilisent la plupart des ORM (les bibliothèques qui font correspondre objets et lignes de base), est un UPDATE : on écrase solde = 350 et la valeur précédente disparaît à jamais. La façon Event Sourcing est un INSERT d'un événement immuable de plus à la fin d'une liste. On n'écrase jamais ; on ne fait qu'ajouter.
Cette liste vit dans un event store : une base append-only, on peut ajouter des événements et les relire, mais jamais les modifier ni les supprimer. Pour obtenir l'état courant, on rejoue les événements (replay) : on les lit depuis le début, dans l'ordre, et on applique chacun pour reconstruire la réponse (partir de 0 €, ajouter le salaire, retirer le loyer… arriver à 350 €). L'état est toujours un calcul sur l'historique, jamais un fait stocké.
Garder l'historique complet offre ce qu'un UPDATE ne pourra jamais donner.
- Reproduire un bug à l'identique : copier le flux d'événements sur un environnement de test et le rejouer — on revit exactement ce que l'utilisateur a fait, étape par étape.
- Corriger un calcul faux : corriger le code et rejouer les événements ; l'état corrigé se recalcule tout seul, sans patch manuel de données.
- Construire un rapport tout neuf : rejouer les mêmes événements dans une nouvelle vue pour répondre à une question que personne ne se posait quand la donnée a été écrite.
« Rejouer des milliers d'événements à chaque fois, ce n'est pas lent ? » Si, et c'est ce que règle le snapshot : une photo enregistrée de l'état à un instant T. Au lieu de rejouer depuis l'événement n°1, on charge le snapshot à l'événement n°5000 et on rejoue seulement les quelques événements depuis. Comme dit Yves Reynhout dans le guide : « ça dépend, dépassez-le, choisissez sagement, et surtout : faites vos propres erreurs. » Et le revers honnête : si l'historique n'a aucune valeur pour vous, ne le payez pas. Quand vous n'avez vraiment jamais besoin de savoir comment vous êtes arrivé à l'état courant, un simple UPDATE est le bon outil.
3CQRS + Event Sourcing : pourquoi ils vont bien ensemble
CQRS nous a laissé un fil qui dépasse. Le côté écriture et le côté lecture sont maintenant deux choses séparées. Alors quand le côté écriture change quelque chose, comment le côté lecture apprend-il qu'il doit rafraîchir sa copie prête à afficher ? Il lui faut un signal.
L'Event Sourcing produit déjà exactement ce signal : les événements. À chaque fois que le côté écriture fait quelque chose, il enregistre un événement, « stock décrémenté pour le produit 157 ». Le côté lecture n'a qu'à écouter ces événements et à mettre à jour sa copie dès qu'il en arrive un. Les deux patterns sont indépendants (chacun s'utilise seul), mais voilà pourquoi on les associe si souvent : les événements que l'Event Sourcing stocke sont précisément les messages dont CQRS a besoin pour garder ses deux côtés synchronisés.
Cela donne deux termes que vous entendrez à chaque réunion. Une projection est une vue construite en écoutant des événements : pensez à un tableau récapitulatif que vous mettez à jour au fur et à mesure que les faits arrivent. « Vu un événement de commande → ajoute 30 € au chiffre d'affaires du jour. » Un read model est la base que le côté lecture interroge : un stockage taillé uniquement pour des lectures rapides, alimenté par une ou plusieurs projections.
Le bénéfice : le read model est jetable. Vous voulez le restructurer (éclater une table, ajouter une colonne, changer toute la disposition) ? Pas de migration de données risquée. Vous jetez le read model, vous créez la nouvelle forme vide, et vous rejouez tous les événements depuis le début pour le reconstruire. Les événements sont la seule vérité durable ; le read model n'est qu'une vue dérivée, regénérable à tout moment. Comme le dit Rinat Abdullin dans le guide : « Tant qu'on a un flux d'événements, on peut le projeter dans n'importe quelle forme, même une base SQL conventionnelle. »
4Process Manager vs Saga : la distinction qui compte
Prenons un flux concret : acheter un billet de concert. Trois choses doivent se produire, et elles vivent dans trois parties différentes du système : réserver la place (la partie réservation), débiter la carte (la partie paiement), envoyer l'email de confirmation (la partie notification).
Faites-le sans coordinateur, et chaque partie doit connaître celle qui vient après : la réservation, une fois faite, appelle le paiement ; le paiement, une fois fait, appelle la notification. Du coup la règle « que faire après une place réservée » est codée en dur dans la réservation, l'ordre des étapes est étalé sur trois modules, et changer le flux oblige à toucher les trois. C'est du couplage fort, et le flux est illisible d'un seul coup d'œil.
Un Process Manager règle ça. C'est un objet dont l'unique métier est de router les messages entre les parties : il sait « après la réservation de la place, dire au paiement de débiter ; après le débit, dire à la notification d'envoyer l'email ». Surtout, il ne contient aucune logique métier : il décide quel message envoyer ensuite, rien de plus. Pensez à un chef d'orchestre qui lit la partition et lance chaque musicien à son tour, mais ne joue d'aucun instrument. Désormais tout le flux vit dans un seul objet lisible, et les trois parties ne se connaissent plus entre elles.
Une Saga ressemble à ça mais répond à une autre question : et l'échec ? La définition originale (Garcia-Molina & Salem, 1987) est un mécanisme pour un processus long qui traverse plusieurs systèmes indépendants et peut échouer en cours de route. Son outil est la transaction compensatoire : une action qui défait une étape précédente. Cas concret : vous réservez un vol, puis vous tentez de réserver la chambre d'hôtel, et la chambre s'avère indisponible. Impossible d'annuler le vol comme une transaction de base de données : il est déjà confirmé dans le système d'une autre entreprise. La Saga lance donc une action compensatoire : rembourser/annuler automatiquement le vol pour ramener le monde dans un état cohérent.
| Process Manager | Saga | |
|---|---|---|
| But | Routage : quel message vient après | Gestion des échecs : défaire ce qui a été fait |
| Périmètre | Dans un seul système | À travers plusieurs systèmes indépendants |
| Outil | Envoie des commandes en séquence | Transactions compensatoires |
| Logique métier ? | Aucune | Seulement les règles « comment annuler » |
La seule règle qui compte : ne jamais mettre de logique métier dans un Process Manager. Dès que vous vous surprenez à écrire « si le total dépasse 500 €, appliquer une remise » dedans, vous avez mis une règle de domaine au mauvais endroit. Cette règle appartient à la partie qui possède le calcul du prix : le Process Manager ne fait que router ; il ne décide jamais.
5Ce que le vrai projet a appris à l'équipe
Le meilleur du guide, c'est qu'il suit un vrai système en train de se construire : Contoso Conference Management, un logiciel de réservation de places de conférences. Et il raconte les galères, pas une success story lissée.
Trois problèmes ont rattrapé l'équipe, que les tutoriels passent tous sous silence. L'infrastructure de messagerie (la plomberie qui transporte les événements du côté écriture vers le côté lecture) a pris deux fois plus de temps que prévu. Faire deux choses à la fois, enregistrer un événement et le publier, s'est avéré difficile à rendre atomique, c'est-à-dire tout-ou-rien : si l'enregistrement réussit mais que la publication échoue, le côté lecture n'entend jamais parler d'un changement qui a pourtant eu lieu. La page de confirmation, elle, s'affichait immédiatement après la réservation, mais le côté lecture n'avait pas encore rattrapé le côté écriture : le total affiché était parfois celui d'avant la validation. C'est ce qu'on appelle la cohérence éventuelle.
Ce terme mérite une définition soignée, car vous le rencontrerez sans cesse. La cohérence éventuelle signifie qu'il y a un petit délai entre le moment où le côté écriture enregistre un changement et le moment où le côté lecture l'a rattrapé. Pendant une brève fenêtre, le read model est en retard. Ce n'est pas un bug, c'est la conséquence inévitable d'avoir deux côtés séparés. La leçon : la concevoir exprès (afficher « réservation en cours », re-charger un instant plus tard) plutôt que de la subir par surprise. On prévoit le délai ; on ne fait pas semblant qu'il n'existe pas.
Une question se pose naturellement ici : quand vous rejouez les événements pour reconstruire un read model, comment évitez-vous de renvoyer des emails ou de re-déclencher des paiements ? La protection vient d'une distinction que le framework impose : le Process Manager expose deux types de méthodes. apply(event) met à jour l'état, sans aucun effet de bord — c'est elle qui est appelée sur replay. handle(command) décide d'une action et la déclenche — elle n'est appelée qu'en live. Sur replay, le PM reconstruit donc son état ("j'ai déjà envoyé cet email") sans re-décider ni re-déclencher quoi que ce soit. Mais ça ne suffit pas si le process crashe entre l'écriture de l'événement et l'envoi réel. C'est là qu'intervient le pattern Outbox : plutôt que d'envoyer directement, le PM écrit dans une table d'attente dans la même transaction que l'événement. Un processus séparé lit cette table, envoie, puis marque la ligne comme traitée. Si le process redémarre, la ligne est soit déjà marquée (rien ne part), soit toujours en attente (elle part une seule fois).
Ce qu'ils feraient différemment, dans leurs mots : construire l'infrastructure avant le code métier ; tester le routage du Process Manager seul, isolé ; et tracer tôt les frontières entre parties du système. Ce dernier point demande une définition. Un bounded context (contexte délimité) est une tranche du système dans laquelle chaque mot a un sens précis et stable. Dans le contexte « réservation », « place » veut dire un siège physique. Dans le contexte « facturation », « place » veut dire une ligne de facture. Même mot, deux contextes, deux sens, deux modèles séparés. Faire comme si c'était le même modèle, c'est ainsi que les systèmes pourrissent. Leur recommandation la plus forte se cache là : sur cinq bounded contexts dans leur système, ils n'ont utilisé CQRS que pour un seul : le contexte « réservation » sous pression, où beaucoup de gens se disputent la dernière place. Les quatre autres : du CRUD ordinaire. L'application sélective, et non CQRS partout, est la vraie recommandation.
6Quand s'arrêter et faire du CRUD
La chose la plus utile que fait ce guide, c'est de vous dire quand ne pas utiliser tout ça. La référence à laquelle il compare tout, c'est le CRUD, Create, Read, Update, Delete (créer, lire, mettre à jour, supprimer), la façon classique de parler à une base : une table, des lignes dedans, et des updates qui les écrasent. La plupart des écrans que vous bâtirez sont du CRUD, et c'est parfaitement bien.
Sortez CQRS seulement quand au moins l'un de ces points est vraiment vrai : le domaine est collaboratif et disputé (plein d'utilisateurs se battent pour la même donnée, comme la dernière place) ; les règles métier sont complexes et changent sans arrêt ; lectures et écritures se font à des volumes très différents (des millions de vues, un filet d'updates) et vous voulez les scaler séparément ; ou vous avez déjà une infrastructure de messagerie en place, donc le coût est en partie payé. Rien de tout ça ? Faites du CRUD.
Sortez l'Event Sourcing seulement quand l'historique lui-même a une valeur métier : une piste d'audit légale, le débogage de flux complexes en rejouant exactement ce qui s'est passé, ou plusieurs rapports différents alimentés par les mêmes faits. Si vous n'avez jamais besoin de savoir comment vous êtes arrivé à l'état courant, un simple UPDATE gagne.
Et soyez honnête sur la facture. Deux coûts accompagnent l'Event Sourcing qu'aucun tutoriel ne signale à l'avance.
- Versioning des événements : les événements sont immuables et vivent pour toujours, donc le jour où vous devez ajouter ou renommer un champ, il faut continuer à rejouer des événements vieux de plusieurs années qui ont l'ancienne forme, sans rien casser.
- Changement de modèle mental : toute l'équipe passe de « je mets à jour une ligne » à « j'ajoute un fait et je recalcule », et chacun doit intégrer la cohérence éventuelle.
Ces patterns sont puissants là où ils collent, et un poids mort là où ils ne collent pas. Choisir le CRUD exprès est une décision de senior, pas une facilité.
Trois choses que je ne savais pas
- Le terme « CQRS » n'existait dans aucun papier ni aucun livre avant les documents de Greg Young en 2010. Il est né dans des commentaires de blog et des fils de discussion communautaires. Ce guide est la première publication à le traiter de façon exhaustive, douze ans après que le pattern a commencé à circuler.
- Le papier original de 1987 sur les Sagas (Garcia-Molina & Salem) portait sur les transactions longues en base de données, pas les systèmes distribués du tout. La communauté DDD a réutilisé le mot et en a complètement changé le sens. Le guide est l'une des rares sources à retracer cette étymologie explicitement et à expliquer pourquoi « Process Manager » est le terme le plus propre pour ce que les gens désignent vraiment.
- L'équipe n'a choisi CQRS que pour un seul des cinq bounded contexts de leur système. Les autres utilisent du CRUD ordinaire. Cette application sélective, pas CQRS partout, est la vraie recommandation du guide, et elle est facile à rater dans le titre « nous avons construit un système CQRS ».
Mon avis, honnêtement
Je fais de l'Event Sourcing et du CQRS au quotidien. Ce que ce guide fait qu'aucun article de blog ne fait, c'est de vous forcer à regarder le coût de l'infrastructure avant de vous engager : deux fois plus de temps que prévu, la cohérence éventuelle dans l'IU, les problèmes de store-and-publish atomique. La plupart des introductions à CQRS/ES vendent les bénéfices et passent la plomberie sous silence. Celui-ci montre la plomberie, en détail, avec les verrues.
Le chapitre sur les Sagas vaut à lui seul la lecture. En trois ans à travailler sur des systèmes événementiels, j'ai vu la confusion Process Manager / Saga provoquer de vraies erreurs architecturales : des équipes qui construisent de la machinerie de transactions compensatoires pour un flux qui vit entièrement dans un seul bounded context, ou un coordinateur qui contient de la logique métier qu'il n'a pas à porter. Le guide dissipe ça en dix pages mieux que tout ce que j'ai lu.
La limite à assumer : le code d'exemple cible Windows Azure 2012, et la stack de messagerie .NET de cette époque n'est pas ce qu'on construit aujourd'hui. Le lire pour les patterns et le raisonnement, pas pour l'implémentation. Et pour le seul vrai vide qu'il laisse, comment faire évoluer les schémas d'événements une fois qu'on a un système en production, il faut Versioning in an Event Sourced System de Greg Young (Leanpub) en complément.
Odilon
Toujours valable en 2026 ?
Plus que le code d'exemple, oui. Les patterns (CQRS, Event Sourcing, la distinction Saga/Process Manager, l'argument pour une application sélective) n'ont fait que gagner en pertinence à mesure que les architectures événementielles et microservices sont devenues mainstream. L'infrastructure est datée ; le raisonnement ne l'est pas. Un angle s'est ajouté depuis : les agents IA consomment aujourd'hui les mêmes événements et read models que les humains, avec le même besoin de flux bien nommés et auto-descriptifs. Un langage d'événements propre est aussi précieux pour un modèle de langage qui génère du code que pour un développeur qui lit un stream.
Pour qui ?
Lisez-le si
- Vous allez adopter CQRS ou Event Sourcing et voulez voir le vrai coût avant de vous engager
- Vous avez entendu « Saga » et « Process Manager » utilisés indistinctement et voulez la distinction réelle
- Vous voulez un traitement complet de A à Z de la pile de patterns, gratuit, rien à acheter
- Vous avez lu Learning DDD et voulez le deep-dive CQRS/ES vers lequel il pointe
Passez si
- Vous voulez du code d'infrastructure moderne : la stack Azure/.NET 2012 n'est pas ce que vous construisez
- Vous avez besoin du versioning d'événements : le guide pose le problème mais ne le résout pas, lisez le Leanpub de Greg Young à la place
- Vous êtes dans un domaine CRUD simple : le guide lui-même vous dirait de ne pas vous embêter
Pour aller plus loin
La fondation conceptuelle des patterns présentés ici est dans Learning Domain-Driven Design (Khononov), qui place CQRS et Event Sourcing dans le cadre de décision core vs. supporting subdomains. Pour les Sagas en contexte microservices, Microservices Patterns (Richardson, Manning 2018) va plus loin sur la chorégraphie vs. l'orchestration. Et pour le seul problème difficile que ce guide laisse ouvert, Versioning in an Event Sourced System de Greg Young (Leanpub, 2017) est le seul traitement dédié. Sur ce blog : la série idempotence en CQRS (en deux parties) montre une implémentation Go réelle des clés d'idempotence, du verrouillage optimiste et du pattern Outbox.
Commentaires (0)