De l'étiquette de prix dans une boîte aux patterns de lecture avancés : le guide qui couvre ES et Event Modeling ensemble, galères comprises.
Pourquoi ce livre
L'Event Sourcing et l'Event Modeling sont omniprésents dans les offres d'emploi, les conférences d'architecture et les réunions d'équipe. La plupart des ressources donnent soit une définition de trois lignes, soit cinq cents lignes de code Axon sans contexte. Ce livre, écrit par Martin Dilger, consultant allemand qui accompagne des équipes sur Event Modeling depuis 2021, fait quelque chose de plus rare : il couvre les deux méthodes ensemble, de la modélisation sur tableau blanc aux patterns de lecture en base de données, et il dit franchement ce qui est dur.
L'autre livre de Dilger sur ce site, The little Eventmodeling Book, est un guide Q&A de 72 pages. Understanding Eventsourcing est sa thèse complète : 650 pages, deux préfaces signées Adam Dymitruk (le créateur d'Event Modeling) et Gabriel Schenker (Head of Platform Engineering chez iptiQ). Le code est en Kotlin avec le framework Axon. Les concepts sont portables. La plomberie Java ne l'est pas.
Les idées qui restent
Dix idées à emporter, dans l'ordre de la logique du livre.
1L'étiquette dans la boîte
Les enfants de Dilger vendent leurs jouets dans la rue. Chaque vente : une petite étiquette de prix, rouge pour le fils, jaune pour la fille, glissée dans une boîte à chaussures. En fin de journée, ils vident la boîte, comptent les étiquettes par couleur, et chacun sait ce qu'il a gagné. Ils n'ont jamais mis à jour un tableau ni écrasé un solde. Ils ont naturellement inventé l'Event Store.
C'est l'analogie que Martin Dilger utilise au chapitre 2, et c'est la plus claire que j'aie rencontrée pour expliquer l'Event Sourcing sans jargon. La boîte est l'Event Store : une base où on ne fait qu'ajouter, jamais modifier ni supprimer. Chaque étiquette est un événement. Un événement (le vocabulaire du domaine reste en anglais : event, Event Store, replay) est un fait passé, écrit au passé, à jamais immuable. La règle que pose Dilger : « Whatever happens in the system, you need to record it as an event. If there is no event, it didn't happen. » (p. 40), ce qu'on traduit par « tout ce qui se passe dans le système doit être enregistré comme un événement. Si l'événement n'existe pas, la chose n'a pas eu lieu ».
Pour connaître l'état actuel, on rejoue les événements dans l'ordre (replay). C'est la différence fondamentale avec le CRUD (Create, Read, Update, Delete), la façon classique de gérer les données : en CRUD, un UPDATE écrase l'état précédent et l'ancienne valeur disparaît. En Event Sourcing, on n'écrase jamais, on insère un événement de plus. Un panier e-commerce qui démarre vide, reçoit trois articles, en retire un, se termine avec deux. Les trois mouvements sont conservés.
Deux termes à retenir. Un Event Stream est la séquence chronologique de tous les événements pour une entité précise, un panier ou un client, identifiée par un Stream-Id (l'équivalent d'une clé primaire). Une Projection est une vue construite en rejouant ces événements vers un autre format : une table SQL, un index Elasticsearch, ou même un fichier CSV.
24 post-its pour dessiner le système avant d'écrire le code
Adam Dymitruk écrit en préface que la cause principale des échecs logiciels n'est pas technique : c'est l'absence d'un langage commun entre les développeurs et le métier. L'Event Modeling est la réponse. C'est une méthode de planification visuelle fondée sur 4 types de blocs, lisible par tout le monde.
Les 4 patterns :
- State Change (Command en bleu → Event en orange) : la seule façon de modifier le système. Un utilisateur déclenche une commande, qui produit un événement si les règles métier sont respectées.
- State View (Event orange → Read Model vert) : la façon de lire les données. Un événement alimente une vue qui peut être consultée.
- Automation (symbole engrenage) : un processus de fond déclenché par un événement, un timer ou une action utilisateur. Par exemple, envoyer un email de confirmation après une commande.
- Translation : réception d'un événement externe (API tierce, message Kafka, fichier CSV) et sa conversion en événement ou lecture interne.
Ce que Dilger appelle le "completeness check" (vérification de complétude) est le vrai apport de la méthode. Chaque attribut d'un Read Model doit pouvoir être tracé vers un Event existant dans le modèle. Si un champ "email" dans une vue n'a pas de Event source correspondant, la donnée n'existe tout simplement pas encore dans le système. Cette règle empêche les fausses suppositions sur la disponibilité des données, une des causes les plus fréquentes de retards de projet.
Une anecdote du livre : à Vienne, au printemps 2024, deux personnes débattent avec véhémence d'une même fonctionnalité lors d'un workshop Dymitruk. Il affiche les wireframes. Le désaccord se résout en cinq minutes : elles décrivaient deux écrans différents. Dilger estime que la planification devrait représenter 60 à 70 % de l'effort total d'un projet.
3CQRS : lire et écrire autrement
Bertrand Meyer, dans les années 1980, formulait le principe CQS (Command-Query Separation, séparation commande-requête) : dans une classe, chaque méthode est soit une commande (elle modifie l'état, ne retourne rien) soit une requête (elle retourne des données, ne modifie rien). Une méthode qui fait les deux est une source de confusion. C'est le niveau du code.
CQRS (Command Query Responsibility Segregation) applique la même idée à l'échelle de l'architecture. On maintient deux modèles distincts : le Write Model (qui gère les commandes et l'écriture des événements) et le Read Model (optimisé pour la lecture, sans les règles métier).
Ce n'est pas la même chose que l'Event Sourcing. Les deux se complètent, mais sont indépendants. Dilger donne un exemple concret tiré de sa propre pratique : une table PostgreSQL customer_registrations propagée vers Elasticsearch via CDC (Change Data Capture, un outil qui détecte les modifications d'une source et les propage automatiquement vers une autre). Pas un événement en vue, et pourtant c'est du CQRS. « You can implement CQRS perfectly fine without using Event Sourcing. » (p. 82)
La cohérence éventuelle est la conséquence naturelle de la séparation. Quand le Write Model et le Read Model se synchronisent de façon asynchrone, la vue peut avoir un léger retard sur la réalité. Dilger cite l'exemple Amazon : achetez un ebook et allez immédiatement sur votre page "Contenus numériques". Le livre n'est pas encore là. Environ trente secondes. C'est de la cohérence éventuelle visible. Ce n'est pas un bug : c'est la façon dont le système fonctionne. La réponse de design : prévoir ce délai dans l'interface, ne pas faire semblant qu'il n'existe pas.
4L'anatomie : qui fait quoi dans le cycle
Un système event-sourcé, c'est quatre composants qui se passent le relais. Le cycle complet (fig. 6.8 du livre) en six étapes :
- Une requête arrive. Elle est traduite en une Command : "AssignContactAddressCommand". Une commande est une instruction d'action.
- Le Command Handler (gestionnaire de commande) la reçoit. Il valide les règles de structure (champs obligatoires, format du code postal) et délègue à l'Aggregate.
- L'Aggregate (agrégat, emprunté au DDD, Domain-Driven Design) est le videur (bouncer) : c'est lui qui décide ce qui a le droit d'entrer dans l'Event Store. Il vérifie les règles métier ("un client ne peut pas avoir deux adresses de livraison identiques"). Si tout est valide, il ordonne l'écriture de l'événement résultant dans l'Event Store. C'est lui qui fait autorité sur la cohérence des données.
- L'événement est écrit dans l'Event Store (append-only). C'est la preuve que quelque chose a changé.
- Des Projectors (des handlers d'événements) réagissent à ce nouvel événement. Ils le traduisent vers une table SQL, un index de recherche, un cache. Ce sont eux qui alimentent les Read Models.
- Pour lire les données, le système émet une query. Le Query Handler sait où aller chercher et dans quel format. Il abstrait la technologie de stockage : changer le backend de lecture ne casse pas les clients.
// Aggregate — le bouncer du système (Kotlin ; concept portable à tout langage objet)
class CustomerAggregate {
fun handle(command: AssignContactAddressCommand) {
// Vérifie : pas d'adresse dupliquée, maximum 2 adresses par client
storeEvent(ContactAddressAssigned(...)) // preuve que ça s'est passé
}
}
5Événements internes vs contrats publics
Un argument séduisant pour l'Event Sourcing : « vous avez accès à tous les événements, donc tous les systèmes peuvent consommer votre stream ». Dilger dit franchement que c'est faux. Consommer les événements internes d'un autre service, c'est techniquement identique à lire directement sa base de données. Les autres équipes deviennent dépendantes de la structure interne de vos événements. Chaque refactoring devient une négociation.
Dilger distingue deux catégories :
- Domain Events (événements de domaine) : les faits internes qui capturent les changements d'état. Ils appartiennent au domaine et sont libres d'évoluer au rythme des règles métier.
- Integration Events (ou External Events) : des contrats stables publiés à destination d'autres systèmes. Ils résument les données dans un format versionné et convenu — et les modifier demande de la coordination.
Un exemple du livre : les événements internes du panier e-commerce sont "Item Added", "Item Removed", "Voucher Applied". Un service externe qui les consomme doit comprendre toutes les règles de calcul du panier. Si ces règles changent, tous les consommateurs cassent. La bonne approche : publier un événement "Cart Submitted" qui contient déjà le total calculé dans un format propre. C'est le contrat.
Le versionning des événements suit deux règles :
- Changement compatible (backward-compatible change) : ne casse pas les consommateurs existants — ajouter un champ optionnel.
- Changement cassant (breaking change) : les casse — supprimer ou renommer un champ. La stratégie : versionner l'événement (
CustomerRegistered_V2) et gérer les deux versions dans le code jusqu'à migration complète.
68 façons de lire les mêmes événements
La Part IV du livre est un des rares endroits en littérature d'architecture qui donne des noms précis à des décisions de design que la plupart des équipes prennent implicitement. Huit patterns de lecture, chacun pour un problème distinct :
| Pattern | Problème résolu | Caractéristique clé |
|---|---|---|
| Database Projected Read Model | Requêtes complexes, multi-streams | Table SQL dédiée, async, reconstruite par replay |
| Live Model | Consistance forte sur un stream court | Reconstruit from scratch à chaque requête, pas de base secondaire |
| Synchronous Projection | Projection async avec un point critique de consistance | Buffer in-memory (LinkedBlockingQueue) comble le délai |
| Logic Read Model | Où placer calculs et agrégations | Logique dans le Read Model, zéro effet de bord, zéro appel externe |
| Snapshots | Performance sur streams longs | Cache d'état à un instant T : exception, pas règle |
| Processor-TODO-List | Automations complexes sans orchestrateur Saga | Read Model = liste de tâches ; l'event résultant = cocher la tâche |
| Reservation Pattern | Unicité d'une ressource sans transaction ACID distribuée | Aggregate dont l'ID = la ressource (email, ticket) |
| Lookup Tables | Données descriptives (noms, images) non dupliquées dans les events | Projection ID → info, locale à chaque slice |
Le Processor-TODO-List est la contribution la plus originale de Dilger. Au lieu d'un orchestrateur de Saga avec une définition de processus explicite, un Read Model calcule la liste des tâches en attente. Un scheduler vérifie cette liste, émet une commande par tâche, et l'event résultant la coche. Simple, testable, sans orchestrateur. Dilger résume : « We do not define a specific Process-Orchestrator or Saga-Process-Definition but simply act on the facts in the system. That's it. » (ch. 35), ce qui donne : on n'invente pas d'orchestrateur, on agit juste sur les faits déjà dans le système.
Sur les Snapshots, sa position est délibérée : « You have a problem, and you cache it. Now you have two problems. » La vraie solution est de limiter la longueur des streams via des concepts métier, comme la notion comptable de "clôture mensuelle". Les Snapshots sont un patch de performance, pas un pattern de conception.
7Le prix du passé
L'Event Sourcing conserve l'historique complet. Le RGPD (Règlement général sur la protection des données) exige le droit à l'oubli. La tension est réelle. Mais Dilger la recadre : le problème n'est pas spécifique à l'Event Sourcing. Les logs, les backups et les exports Excel des systèmes CRUD ont exactement le même problème.
La règle numéro un est de design, pas technique : Data Minimalism (minimalisme des données). « The simplest data to handle is the data that doesn't exist. » (p. 551) Ne stocker que ce qui est strictement nécessaire.
Pour le reste, deux techniques :
- Crypto Shredding (déchiquetage cryptographique) : les données personnelles (PII, Personally Identifiable Information, c'est-à-dire les données permettant d'identifier une personne : nom, adresse, email) sont chiffrées dans les événements avec une clé par utilisateur, stockée dans un service de gestion de clés (AWS KMS, HashiCorp Vault). Pour "oublier" un utilisateur : supprimer sa clé. Les événements restent dans le store, mais deviennent illisibles. Le passé est toujours là, simplement opaque.
- Forgettable Payload (terme de Mathias Verraes, "charge utile oubliable") : les données personnelles ne sont pas dans les événements. Elles sont stockées dans une table séparée, référencée par un ID dans les événements. Pour oublier : supprimer la ligne dans la table. Les événements restent intacts.
Après suppression, certaines projections doivent être rejouées. L'Event Model est précieux ici : il permet d'identifier précisément quelles projections utilisent ces données et lesquelles doivent être reconstruites.
8Le Processor-TODO-List : des automations sans orchestrateur
Tout système a des workflows automatisés. Après qu'une commande est confirmée, envoyer l'email de confirmation. Après le paiement, générer la facture. Après l'expiration d'une période d'essai, désactiver le compte. La réponse habituelle est une Saga (un pattern qui définit le workflow entier comme une séquence d'étapes explicites, avec des transactions compensatoires si quelque chose échoue) ou un Process Manager (un objet dont le seul rôle est de router les messages entre les services et de savoir quelle commande envoyer après chaque événement). Les deux nécessitent une définition nommée et maintenue du processus lui-même.
Dilger n'utilise ni l'un ni l'autre. Son Processor-TODO-List Pattern part d'une question différente : qu'est-ce que le système sait déjà ? La réponse est dans l'Event Store. Une commande a été confirmée. Un email de confirmation n'a pas encore été envoyé. Un entrepôt n'a pas encore été notifié. Ce ne sont pas des plans futurs : ce sont des faits, déjà exprimables à partir des événements. L'écart entre "ceci s'est passé" et "cela ne s'est pas encore produit" est la liste de tâches. Il suffit de construire un Read Model qui la calcule.
Le mécanisme se déroule en trois étapes qui se répètent. D'abord, les événements arrivent et un Projector écrit des lignes dans le Read Model "TODO List" : une ligne par tâche en attente, avec un statut (en cours ou fait) déduit des événements déjà dans le store. Ensuite, un scheduler, un job de fond qui tourne toutes les quelques secondes, interroge le Read Model pour les lignes en attente. Pour chacune, il émet une Command. Le Command Handler la valide et délègue à l'Aggregate, qui écrit l'événement résultant. Enfin, c'est cet événement que le Projector utilise pour passer la ligne à "fait". La TODO List se met à jour d'elle-même, depuis le même stream qu'elle écoute déjà.
Le résumé de Dilger : « We do not define a specific Process-Orchestrator or Saga-Process-Definition but simply act on the facts in the system. That's it. » (ch. 35) Traduit : on n'invente pas d'orchestrateur, on agit simplement sur les faits déjà dans le système. L'état complet du workflow est implicite dans les événements ; pas de machine d'état, pas de processus orchestrateur à surveiller ni à redémarrer.
// TODO List Read Model : concept Kotlin, portable dans n'importe quel langage
// Une ligne par tâche d'automatisation, statut déduit des événements
data class TacheAutomatisation(
val commandeId: String,
val tache: String, // "envoyer_email_confirmation"
val statut: String // "en_attente" | "fait"
)
// Le Projector met à jour la table à chaque événement :
// CommandeConfirmée → INSERT ligne (statut=en_attente)
// ConfirmationEmailEnvoyée → UPDATE ligne (statut=fait)
// Le Scheduler : tourne toutes les 5 secondes
fun traiterEnAttente(taches: List<TacheAutomatisation>) {
taches.filter { it.statut == "en_attente" }.forEach { tache ->
commandBus.dispatch(EnvoyerEmailConfirmationCommand(tache.commandeId))
}
}
Ce que le pattern ne fait pas : le workflow reste implicite. Aucun fichier unique ne dit "si commande confirmée, envoyer l'email, puis notifier l'entrepôt". La logique est répartie entre Projectors et Handlers. Pour des workflows complexes avec des modes d'échec explicites et des transactions compensatoires, une Saga reste l'outil adapté. Dilger préfère manifestement ce pattern, et admet ouvertement ne pas être certain d'utiliser le terme "Saga" correctement, mais le choix dépend de combien l'équipe a besoin de voir le workflow en un seul endroit.
9Le Reservation Pattern : l'unicité sans transaction distribuée
Certaines règles métier exigent l'unicité à l'échelle du système entier. Une adresse email ne peut être enregistrée qu'une seule fois. Un siège de concert ne peut être réservé que par une seule personne. Un code promo ne peut être utilisé qu'une seule fois. Dans une base SQL classique, on l'impose avec un index unique : la base rejette tout doublon au niveau de la colonne. Mais dans un système event-sourcé, où les événements vont dans un store append-only et où les projections sont en cohérence éventuelle, il n'y a pas d'équivalent évident. Deux utilisateurs peuvent simultanément essayer de réserver le même siège, tous les deux lire "disponible" dans la projection (qui peut avoir un léger retard), tous les deux émettre un événement SiegeReservé, et le siège est double-réservé avant que quiconque s'en aperçoive.
Le Reservation Pattern résout ça en faisant de l'ID de l'Aggregate la ressource elle-même. Au lieu d'un aggregate "concert" avec une liste de sièges, on crée un Aggregate dont l'ID est la ressource spécifique : siege-A15-concert-2024-06-20. L'Event Store utilise la concurrence optimiste : chaque écriture spécifie la version de stream attendue. Si deux utilisateurs essaient de créer le même Aggregate simultanément, ils partent tous les deux de la version 0. La première écriture réussit, la version passe à 1. La deuxième écriture, qui attendait aussi la version 0, échoue avec un conflit de version. Le premier arrivé gagne. L'Event Store devient le verrou distribué.
// Reservation Pattern — l'ID de l'Aggregate EST la ressource // Deux tentatives de réservation simultanées pour le même siège : // Utilisateur A Utilisateur B // lit le stream : version=0 lit le stream : version=0 // (siège apparaît disponible) (siège apparaît disponible) // // écrit SiegeReservé écrit SiegeReservé // versionAttendue: 0 versionAttendue: 0 // → OK, version passe à 1 → CONFLIT : attendu 0, trouvé 1 // // Utilisateur A : siège confirmé Utilisateur B : réponse "siège indisponible" // (événement SiegeReservé écrit) (exception de verrou optimiste capturée, // traduite en erreur métier)
Ce qui rend ça propre : pas de commit en deux phases, pas de coordinateur distribué, pas de saga pour gérer le timeout du verrou. La garantie d'unicité vit dans la vérification de version intégrée à l'Event Store, que tout Event Store de production implémente (EventStoreDB, Marten, Axon supportent tous les écritures avec version attendue). Le compromis : chaque ressource unique devient son propre Aggregate, ce qui peut mener à un très grand nombre de streams courts. Pas un problème en pratique, mais à connaître avant d'appliquer à grande échelle.
Dilger étend le pattern au problème de la réservation en deux temps : d'abord bloquer, puis confirmer. Une billetterie peut vouloir retenir un siège deux minutes pendant que l'utilisateur finalise son paiement. Deux commandes : BloquerSiege produit SiegeBloqué (retenu 2 min) ; ConfirmerSiege produit SiegeConfirmé. Le Processor-TODO-List de l'idée 8 gère l'expiration : un scheduler trouve les réservations dont l'événement SiegeBloqué est vieux de plus de deux minutes sans SiegeConfirmé correspondant, et émet une commande LibérerSiege. Deux patterns qui se composent naturellement.
10Tester un système event-sourcé : Given-When-Then
L'Event Sourcing cache un cadeau pour les tests que la plupart des introductions ne mentionnent jamais. Dans un système CRUD, tester la logique métier signifie préparer l'état en base, appeler une méthode, puis requêter la base pour vérifier le résultat. Le test dépend des migrations de schéma, des connexions à la base, des scripts de fixtures. N'importe lequel de ces éléments peut tomber avant même d'atteindre le code qu'on voulait tester. Dilger montre une autre approche : les tests d'Aggregate sont des fonctions pures.
L'Aggregate n'a besoin que de deux choses pour tourner : des événements passés (pour reconstruire son état) et une nouvelle Command. Il produit des Events. Rien d'autre. Pas de connexion à la base, pas d'appel HTTP, pas de dépendance externe. La structure du test reprend exactement la notation des slices d'Event Modeling, Given-When-Then, et se lit comme une spécification :
// Test Given-When-Then pour un Aggregate Panier
// Pas de mock, pas de base de données, pas de contexte Spring
@Test
fun `ajouter un article à un panier soumis est rejeté`() {
// GIVEN : événements passés qui reconstituent l'état de l'aggregate
val historique = listOf(
PanierCréé(panierId = "panier-42"),
ArticleAjouté(panierId = "panier-42", articleId = "livre-1", qté = 1),
PanierSoumis(panierId = "panier-42")
)
// WHEN : une nouvelle commande est émise
val aggregate = PanierAggregate.réhydrater(historique)
val résultat = aggregate.handle(AjouterArticleCommand(panierId = "panier-42", articleId = "livre-2"))
// THEN : le résultat est une erreur métier, pas un nouvel événement
assertThat(résultat).isInstanceOf(PanierDéjàSoumisErreur::class.java)
}
@Test
fun `soumettre un panier produit PanierSoumis et vide le flag en attente`() {
val historique = listOf(
PanierCréé(panierId = "panier-42"),
ArticleAjouté(panierId = "panier-42", articleId = "livre-1", qté = 2)
)
val aggregate = PanierAggregate.réhydrater(historique)
val événements = aggregate.handle(SoumettreCommand(panierId = "panier-42"))
assertThat(événements).containsExactly(
PanierSoumis(panierId = "panier-42", totalArticles = 2)
)
}
Chaque test est une histoire autonome. La section Given est une liste explicite de faits passés — l'équivalent d'une fixture de base de données, mais lisible, typée et versionnée avec le test. La section When est la commande à tester. La section Then est la liste des Events produits (ou l'erreur métier levée). Pas d'état caché, pas de setup partagé, pas de nettoyage après. La réhydratation depuis la liste historique est le même replay que le système de production utilise ; le test emprunte exactement le même chemin que le runtime réel.
Dilger montre aussi les tests de Read Model, qui suivent le même pattern mais vérifient l'état de la projection résultante : Given [liste d'événements], Then [voilà ce à quoi ressemble la table du Read Model]. Ces tests vérifient que les Projectors traduisent correctement les événements en lignes optimisées pour la lecture. Ensemble, les deux types de test couvrent complètement une fonctionnalité event-sourcée : le côté écriture (Aggregate + Command Handler) et le côté lecture (Projector + Read Model) — sans base de données vivante ni serveur démarré.
Trois choses que je ne savais pas
- CQRS et Event Sourcing sont vraiment indépendants. Le CQRS sans ES n'est pas seulement théoriquement possible : Dilger l'a construit en production, avec une table PostgreSQL propagée vers Elasticsearch via Change Data Capture, sans un seul event store. On confond les deux parce qu'ils se complètent, pas parce que l'un exige l'autre.
- Dilger avoue ne pas utiliser les Sagas. Il préfère son propre Processor-TODO-List Pattern, et reconnaît ouvertement qu'il n'est pas certain d'utiliser les termes "orchestration" et "chorégraphie" correctement : « I don't even claim that I'm using the terms correctly, as my understanding of distributed processes constantly changes and evolves. » (ch. 9). Honnêteté rare dans un livre de 650 pages.
- Le Dynamic Consistency Boundary (chapitre 44) n'est pas un concept de Dilger : il vient de Bastian Waidelich et Sara Pellegrini (l'initiative dcb.events). L'idée : plutôt que des frontières d'aggregate statiques, tagger les événements et utiliser des "conditional appends" pour exprimer des règles de consistance qui traversent plusieurs entités, par opération. Greg Young l'a récemment critiqué. Dilger conclut lui-même : « We simply don't have enough experience with this approach yet to fully understand how it will impact systems at scale. »
Mon avis, honnêtement
Je suis dev web PHP/JS, pas développeur Java/Spring. Ce livre a deux visages. La première moitié (Foundations et Modeling) est parmi ce que j'ai lu de plus clair sur l'Event Sourcing et l'Event Modeling. La métaphore de la boîte de jouets, le completeness check, la distinction events internes vs Integration Events : j'aurais voulu avoir ça il y a cinq ans, quand CQRS revenait dans les réunions sans que je comprenne de quoi on parlait. Le catalogue de patterns de la Part IV est aussi vraiment utile : des noms, des périmètres, des justifications.
La deuxième moitié (Part III, l'implémentation Axon) est une autre planète. Le Kotlin avec les QueryGateway, HandlerEnhancerDefinition et @EventHandler d'Axon : lisible, mais ça suppose un écosystème Spring/Java que la plupart des devs web n'ont pas. Six cent cinquante pages pour les deux publics ensemble, c'est un engagement.
Ce qui voyage : les huit patterns de lecture, la frontière events internes/Integration Events, le Processor-TODO-List, le Reservation Pattern, la structure de test Given-When-Then, le completeness check. Rien de tout ça ne dépend de Java. À lire en parallèle avec Exploring CQRS and Event Sourcing (le guide Microsoft patterns & practices) pour voir les mêmes idées appliquées en situation réelle, puis décider quelle stack convient à votre contexte.
Odilon
Toujours valable en 2026 ?
Oui, plus que le guide Microsoft de 2012. La partie Foundations vieillira lentement ; les patterns et la méthode Event Modeling encore moins. La partie Axon vieillira plus vite, mais les patterns qu'elle illustre sont portables. Un angle s'est ajouté depuis la rédaction : les agents IA consomment aujourd'hui les mêmes event streams 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 LLM qui génère du code que pour un développeur qui debug une régression.
Pour qui ?
Lisez-le si
- Vous rejoignez une équipe qui parle CQRS/ES/Event Modeling et vous n'avez pas encore décrypté le vocabulaire
- Vous planifiez un système distribué et cherchez une méthode de modélisation collaborative
- Vous travaillez en Java/Spring et envisagez d'adopter l'Event Sourcing
- Vous connaissez la théorie ES mais pas les patterns de lecture nommés (les huit de la Part IV)
Passez si
- Vous cherchez un guide ES agnostique de framework : les exemples sont trop spécifiques à Axon
- Vous avez besoin d'une couverture complète du versionning d'événements : le livre renvoie au Leanpub de Greg Young
- 650 pages vous fait peur : les chapitres 1 à 9 (Foundations) se lisent seuls, environ 150 pages
Pour aller plus loin
Pour la vision conceptuelle de l'Event Sourcing appliqué à un projet réel avec les galères incluses, Exploring CQRS and Event Sourcing (Microsoft patterns & practices) suit un vrai système à travers ses itérations successives. Pour l'Event Modeling seul, The little Eventmodeling Book (Dilger aussi) est un guide Q&A de 72 pages centré sur la méthode de planification. Pour le seul problème difficile que ce livre laisse ouvert, le versionning des événements, Versioning in an Event Sourced System de Greg Young (Leanpub, 2017) est le seul traitement dédié.
Commentaires (0)