Vol. 5 · № 03 · Un atelier d'architectureAn architecture workshop

Event Sourcing, ou le grand livre qu'on ne peut plus fermerEvent Sourcing, or the ledger you can no longer close

On a toujours stocké l'état présent — status = 'shipped' — et chaque UPDATE écrase le passé. Event Sourcing inverse l'idée : on stocke les faits en append seul (payée, expédiée, remboursée), et l'état devient le repli de cette suite. La force est réelle — audit gratuit, histoire rejouable. Mais c'est le pattern le plus sur-vendu de la série : le prix est permanent (versionner les événements, recalculer l'état, gérer snapshots et migrations), et l'asymétrie doit faire hésiter — on l'ajoute quand on veut, on ne le retire presque jamais. On ne source que l'agrégat où l'histoire est le produit.We've always stored present state — status = 'shipped' — and each UPDATE overwrites the past. Event Sourcing inverts it: you store facts append-only (paid, shipped, refunded), and state becomes the fold of that sequence. The strength is real — free audit, replayable history. But it's the series' most over-sold pattern: the price is permanent (versioning events, recomputing state, managing snapshots and migrations), and the asymmetry should make you hesitate — you add it whenever you like, you almost never remove it. You source only the aggregate where history is the product.

AudienceAudience
Dev tenté par Event Sourcing — pour qui ce numéro est autant un frein qu'un guideDev tempted by Event Sourcing — for whom this issue is as much a brake as a guide
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026June 2026
≈ 19 min●●●○Données & étatEvent SourcingTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

On stocke l'état présent. Et si la vérité était la suite des faits ?We store present state. What if the truth were the sequence of facts?

On ferme le volume des données par son hypothèse la plus profonde, jamais questionnée jusqu'ici : on stocke l'état présent. status = 'shipped'. Chaque changement écrase le précédent — un UPDATE détruit ce qui était là. La colonne ne retient qu'une valeur : la dernière. Event Sourcing inverse l'ordre des choses : on ne stocke plus l'état, on stocke les faits qui l'ont produit — payée, expédiée, remboursée — en append seul, et l'état devient un calcul, le repli de cette suite. Ce qui varie : garde-t-on la photo, ou le film ? Mais — et ce numéro insiste — la réponse par défaut reste « la photo ». Le film coûte cher, pour toujours.We close the data volume with its deepest, until-now unquestioned assumption: we store present state. status = 'shipped'. Each change overwrites the last — an UPDATE destroys what was there. The column holds one value: the latest. Event Sourcing inverts the order of things: you no longer store the state, you store the facts that produced it — paid, shipped, refunded — append-only, and state becomes a computation, the fold of that sequence. What varies: do you keep the photo, or the film? But — and this issue insists — the default answer stays 'the photo'. The film is expensive, forever.

Le code naïf — l'UPDATE qui efface le passéThe naive code — the UPDATE that erases the past
# on stocke l'ÉTAT présent — et on écrase le passé à chaque pas
class Order:
    def pay(self):    self.status = "paid"       # ← l'ancien statut disparaît
    def ship(self):   self.status = "shipped"    # ← écrasé encore
    def refund(self): self.status = "refunded"   # ← et encore

db.query("UPDATE orders SET status='shipped' WHERE id=%s", id)
# la colonne status ne garde qu'une valeur : la dernière.
# « pourquoi cette commande a-t-elle été remboursée, et quand ? » → l'UPDATE
# a effacé la preuve. On a gardé l'état, on a jeté l'histoire qui l'a produit.
L'état vs les faitsState vs facts
status = « shipped »status = 'shipped'
l'état · une photo · écrasablethe state · a photo · overwritable
⇄
ouor
payée → expédiée → rembourséepaid → shipped → refunded
les faits · un film · en appendthe facts · a film · append-only

Stocker l'état, c'est garder le résultat et jeter l'historique. Stocker les faits, c'est garder l'historique et recalculer le résultat. Le premier est simple et oublie ; le second n'oublie rien — et n'oublier rien, c'est précisément ce qui coûte.Storing state keeps the result and discards the history. Storing facts keeps the history and recomputes the result. The first is simple and forgets; the second forgets nothing — and forgetting nothing is precisely what costs.

Le réflexe du volumeThe volume's reflex

« Est-ce que je lis et j'écris le même modèle — et est-ce que ça me coûte ? » Le #1 a séparé le métier de sa base, le #2 lire d'écrire. Le #3 touche le fond : qu'est-ce que je stocke, l'état ou les faits ? Event Sourcing répond « les faits » — séduisant, parce qu'on ne perd plus rien et que l'audit devient gratuit. Mais c'est le pattern le plus sur-employé de la série, et le réflexe à installer n'est pas « adopte-le » : c'est « ne le prends que là où l'histoire elle-même est le métier ».'Do I read and write the same model — and does it cost me?' #1 separated the domain from its database, #2 reading from writing. #3 hits bedrock: what do I store, the state or the facts? Event Sourcing answers 'the facts' — seductive, because you lose nothing and audit becomes free. But it's the series' most over-applied pattern, and the reflex to install isn't 'adopt it': it's 'take it only where the history itself is the domain'.

Le cousin du #2 : l'état comme projection.The #2 cousin: state as a projection.

Si l'état n'est plus stocké mais calculé en rejouant les faits, alors le modèle de lecture du #2 devient exactement ça : une projection du journal d'événements. Event Sourcing et CQRS se marient si naturellement qu'on les confond — mais ils restent orthogonaux. CQRS sépare lire d'écrire sans rien dire de la persistance ; Event Sourcing change ce qu'on persiste sans imposer CQRS. Les prendre ensemble par réflexe, c'est cumuler deux factures pour un seul besoin — le piège qu'on désamorcera au chapitre 4.If state is no longer stored but computed by replaying the facts, then #2's read model becomes exactly that: a projection of the event log. Event Sourcing and CQRS pair so naturally they get conflated — but they stay orthogonal. CQRS separates reading from writing without saying anything about persistence; Event Sourcing changes what you persist without mandating CQRS. Taking them together by reflex stacks two bills for one need — the trap we'll defuse in chapter 4.

№ 02La douleurThe pain4 min

L'UPDATE efface la preuve, et les questions du passé n'ont plus de réponse.The UPDATE erases the evidence, and the past's questions have no answer.

Ne stocker que l'état a trois coûts — réels, mais qui ne mordent que certains métiers. La preuve est détruite : un UPDATE écrase la valeur d'avant, et avec elle le « pourquoi » et le « quand ». Les bugs deviennent irreproductibles : la mutation a effacé le chemin qui y menait, impossible de rejouer. Et des questions métier deviennent insolubles : « combien de commandes remboursées après expédition le mois dernier ? » exige des données qu'on a jetées à chaque pas. Attention : ces coûts justifient Event Sourcing là où l'histoire est le produit — pas partout. Pour la plupart des CRUD, perdre le passé ne coûte rien.Storing only state has three costs — real, but biting only certain domains. The evidence is destroyed: an UPDATE overwrites the prior value, and with it the 'why' and the 'when'. Bugs become irreproducible: the mutation erased the path that led there, no replay possible. And business questions become unanswerable: 'how many orders refunded after shipping last month?' demands data discarded at every step. Caution: these costs justify Event Sourcing where history is the product — not everywhere. For most CRUD, losing the past costs nothing.

№ 1 · La preuve est détruiteThe evidence is destroyed

Un UPDATE écrase la valeur précédente. « Pourquoi #4217 est-elle annulée, et qui l'a fait à quelle heure ? » n'a pas de réponse : la base ne garde que l'état final. Aucun audit, aucune trace — sauf à les rajouter à la main, partiellement, après coup.An UPDATE overwrites the prior value. 'Why is #4217 cancelled, and who did it, when?' has no answer: the database keeps only the final state. No audit, no trace — unless you bolt one on by hand, partially, after the fact.

№ 2 · Les bugs sont irreproductiblesBugs are irreproducible

« La commande est dans un état impossible. » Mais la suite de mutations qui y a mené a été écrasée à mesure : on ne peut pas rejouer le chemin, seulement constater le résultat. On débogue une photo sans le film qui l'explique.'The order is in an impossible state.' But the sequence of mutations that led there was overwritten as it went: you can't replay the path, only observe the result. You debug a photo without the film that explains it.

№ 3 · Les questions du passé sont mortesThe past's questions are dead

« Quel était le panier moyen avant la promo ? », « combien de remboursements après expédition ? » : des questions qu'on ne s'était pas posées au moment d'écrire le schéma. Sans l'historique, la réponse n'existe nulle part — on ne peut pas interroger des données jetées.'What was the average basket before the promo?', 'how many refunds after shipping?': questions you hadn't asked when writing the schema. Without the history, the answer exists nowhere — you can't query data you threw away.

Le ticket de support auquel la base ne peut pas répondreThe support ticket the database can't answer
db.query("UPDATE orders SET status='cancelled' WHERE id=4217")   # 💥 passé écrasé

# trois semaines plus tard, ticket #882 :
#   « le client dit qu'il n'a jamais annulé. Que s'est-il passé ? »
SELECT status FROM orders WHERE id=4217;   →  'cancelled'
# … c'est tout ce que la base sait. Le qui, le quand, le pourquoi : effacés.
Le diagnostic — et sa prudence.The diagnosis — and its caution.

Le détecteur : « mon métier doit-il répondre à des questions sur le passé — qui, quand, pourquoi, dans quel ordre — comme une exigence, pas comme un confort ? ». Si oui (argent, audit, conformité, traçabilité légale), l'état seul est une perte de données structurelle, et Event Sourcing devient légitime : stocker les faits en append, dériver l'état. Si non — et c'est le cas le plus fréquent — l'UPDATE qui oublie est parfaitement sain, et bien moins cher. Le réflexe n'est pas « garde tout par précaution » : c'est « garde l'histoire seulement là où l'histoire est due ».The detector: 'must my domain answer questions about the past — who, when, why, in what order — as a requirement, not a comfort?'. If yes (money, audit, compliance, legal traceability), state alone is a structural data loss, and Event Sourcing becomes legitimate: store facts append-only, derive state. If no — and that's the commonest case — the forgetting UPDATE is perfectly healthy, and far cheaper. The reflex isn't 'keep everything just in case': it's 'keep the history only where the history is owed'.

№ 03Le grand livreThe ledger5 min

On n'écrase plus : on ajoute. Et l'état devient le repli des faits.We stop overwriting: we append. And state becomes the fold of facts.

Event Sourcing emprunte l'idée la plus vieille de la comptabilité : on n'efface jamais une ligne, on en ajoute une qui corrige. L'animation part de la case status qu'on écrase, la remplace par un journal en append seul, montre l'état présent se recalculer en repliant les faits, puis le même journal nourrir autant de projections qu'on veut — y compris une vue inventée après coup, reconstruite en rejouant tout. La dernière marche montre la superpuissance ; le chapitre suivant montrera ce qu'elle coûte.Event Sourcing borrows accounting's oldest idea: you never erase a line, you add one that corrects it. The animation starts from the status cell you overwrite, replaces it with an append-only log, shows present state recomputed by folding the facts, then the same log feeding as many projections as you want — including a view invented after the fact, rebuilt by replaying everything. The last step shows the superpower; the next chapter shows what it costs.

Animation — de la case écrasée au journal qu'on replieAnimation — from the overwritten cell to the log you fold
Départ — l'état s'écraseStart — state overwrites · 1/4
paidshipped
▶
status = refunded
une seule case · écraséeone cell · overwritten
chaque UPDATE détruit la valeur précédenteeach UPDATE destroys the prior value
Une seule case status. Chaque UPDATE remplace la valeur d'avant : payée, puis expédiée, puis remboursée — et il ne reste que la dernière. Le passé est détruit à mesure.A single status cell. Each UPDATE replaces the prior value: paid, then shipped, then refunded — and only the last remains. The past is destroyed as it goes.

Les faits sont la vérité, l'état est dérivé — 3 lignes à retrouverFacts are the truth, state is derived — 3 lines to recall
# on n'écrit que des faits, en append — jamais d'UPDATE, jamais de DELETEevents.append(OrderShipped(id, at="14:20"))
# l'état présent est un REPLI des faits — pas une donnée stockée
def current_state(events):# « refunded » n'est pas écrit nulle part : c'est ce que donne le fold.
3 ligne(s) à retrouver — écris-la de tête avant de cliquer3 line(s) to recall — write it in your head before clicking
Le motif transférable.The transferable pattern.

« N'efface jamais ; ajoute une ligne qui corrige. » Ce n'est pas une invention logicielle : c'est le grand livre comptable, vieux de six siècles. La même idée fait tourner ton relevé bancaire (des opérations, pas un solde écrasé), Git (des commits, pas un fichier remplacé), et le write-ahead log de ta base (les changements avant l'état). Event Sourcing applique ce principe à l'état métier. La force est partout la même — l'histoire complète, rejouable ; le prix aussi — on ne peut plus jamais effacer ni réécrire le passé.'Never erase; add a line that corrects.' Not a software invention: it's the accounting ledger, six centuries old. The same idea runs your bank statement (transactions, not an overwritten balance), Git (commits, not a replaced file), and your database's write-ahead log (the changes before the state). Event Sourcing applies that principle to business state. The strength is everywhere the same — the full, replayable history; so is the price — you can never again erase or rewrite the past.

Modèle mentalMental model
L'imageThe image

Le grand livre comptable, vieux de six siècles : on n'efface jamais une écriture, on en ajoute une qui corrige. Le solde n'est écrit nulle part — c'est la somme de toutes les lignes, recalculée. Ton relevé bancaire, Git, le write-ahead log de ta base fonctionnent pareil.The accounting ledger, six centuries old: you never erase an entry, you add one that corrects it. The balance is written nowhere — it's the sum of every line, recomputed. Your bank statement, Git, your database's write-ahead log all work the same way.

La règleThe rule

Stocke les faits en append seul, jamais l'état ; l'état présent est le repli du journal — état = fold(événements). Pour corriger, on n'« update » pas : on ajoute un fait compensatoire.Store the facts append-only, never the state; present state is the fold of the log — state = fold(events). To correct, you don't 'update': you append a compensating fact.

Où l'analogie casseWhere the analogy breaks

Le grand livre comptable existe là où la loi EXIGE l'historique — et le comptable le clôture chaque exercice, figeant un solde d'où il repart. Deux limites, donc. D'abord : le copier sur un agrégat où l'histoire n'est qu'un confort, c'est importer sa taxe permanente sans son bénéfice. Ensuite : ton journal, lui, ne se clôture jamais (c'est le titre du numéro) — le rejeu s'allonge sans fin, et il faut réinventer la clôture sous le nom de « snapshots ». Le défaut sain reste la photo ; le grand livre se mérite.The accounting ledger exists where law REQUIRES the history — and the accountant closes it each period, freezing a balance to start from. Two limits, then. First: copying it onto an aggregate where history is merely a comfort imports its permanent tax without its benefit. Second: your log never closes (that's the issue's title) — replay grows without end, and you must reinvent the close under the name 'snapshots'. The sane default stays the photo; the ledger is earned.

№ 04Le piègeThe trap5 min

Le pattern le plus séduisant de la série, et le plus regretté.The series' most seductive pattern, and the most regretted.

Voici le numéro où le panneau danger est plus grand que le réflexe. Event Sourcing est élégant — on ne perd rien, l'audit est gratuit, on rejoue l'histoire à volonté — et c'est exactement ce qui le rend dangereux : on l'adopte partout, par défaut, pour la beauté de l'idée, sans le besoin qui la justifie. Le prix n'est pas un coût ponctuel, c'est une taxe permanente : versionner des événements pour toujours, ne plus jamais pouvoir effacer ni réécrire, recalculer l'état par du code, gérer snapshots et migrations. Sur l'agrégat où l'histoire est le métier, ce prix s'achète. Partout ailleurs, c'est une dette qu'on ne peut plus rembourser. Bascule le toggle : compte les versions d'événements.This is the issue where the danger sign is bigger than the reflex. Event Sourcing is elegant — you lose nothing, audit is free, you replay history at will — and that's exactly what makes it dangerous: you adopt it everywhere, by default, for the beauty of the idea, without the need that justifies it. The price isn't a one-off cost, it's a permanent tax: versioning events forever, never again being able to erase or rewrite, recomputing state through code, managing snapshots and migrations. On the aggregate where history is the domain, that price buys something. Everywhere else, it's a debt you can no longer repay. Flip the toggle: count the event versions.

# Event Sourcing sur le SEUL agrégat où l'histoire EST le métier : le grand livre
class Ledger:                       # un compte : la loi exige l'historique complet
    def post(self, entry): self.events.append(entry)   # append seul, jamais d'UPDATE
    def balance(self): return sum(e.amount for e in self.events)   # solde = fold

# … et tout le reste du système reste en état mutable, simple et cohérent :
class Order:
    def ship(self): db.update(id, status="shipped")   # un CRUD honnête, pas d'ES
# l'histoire est due là où elle est due — et nulle part ailleurs.
Pourquoi c'est justeWhy it's right

On a réservé Event Sourcing au seul endroit où l'histoire complète est une exigence, pas un confort : un grand livre, où la loi et le métier demandent qu'on sache qui, quand, pourquoi, dans quel ordre, et qu'on ne réécrive jamais. Là, l'append seul et le solde-comme-fold ne sont pas une complexité ajoutée — ils sont la forme juste du domaine. Et partout ailleurs, le système reste en état mutable, simple, cohérent immédiatement : un Order qui s'UPDATE, un CRUD honnête. La règle tient parce qu'on a fait payer la taxe uniquement à l'agrégat qui en tire un vrai produit.You reserved Event Sourcing for the one place where the full history is a requirement, not a comfort: a ledger, where law and domain demand you know who, when, why, in what order, and never rewrite. There, append-only and balance-as-fold aren't added complexity — they're the domain's right shape. And everywhere else, the system stays in mutable state, simple, immediately consistent: an Order that UPDATEs, an honest CRUD. The rule holds because you made only the aggregate that gets a real product pay the tax.

La règle qui tranche.The rule that settles it.

Stocke des événements seulement quand le journal des changements est lui-même une fonctionnalité que ton métier doit fournir — audit légal, conformité, traçabilité financière, questions temporelles qu'on te facturera. Le test, brutal : « si je perds tout l'historique et ne garde que l'état présent, est-ce que je viole une exigence, ou est-ce que je perds juste un confort ? ». Si c'est un confort, tu n'as pas besoin d'Event Sourcing — un journal d'audit à côté suffit. Et la règle vaut par agrégat, jamais pour le système entier : un seul agrégat peut le mériter pendant que tout le reste reste un CRUD.Store events only when the log of changes is itself a feature your domain must provide — legal audit, compliance, financial traceability, temporal questions you'll be billed for. The blunt test: 'if I lose all the history and keep only present state, do I violate a requirement, or just lose a comfort?'. If it's a comfort, you don't need Event Sourcing — an audit log on the side is enough. And the rule holds per aggregate, never for the whole system: one aggregate may deserve it while everything else stays CRUD.

L'asymétrie qui doit faire hésiter.The asymmetry that should make you hesitate.

On peut presque toujours ajouter Event Sourcing à un agrégat plus tard ; on ne peut presque jamais le retirer. Le journal devient la source de vérité, du code en dépend, des projections s'y branchent — revenir à l'état mutable, c'est une migration douloureuse. Le défaut sain est donc l'état mutable, et la charge de la preuve revient à celui qui veut les événements. L'over-reach ultime : empiler ES + CQRS + microservices « parce que ça va ensemble » et obtenir un monolithe distribué de rejeux — trois factures, trois irréversibilités, pour un besoin qu'on n'a jamais mesuré.You can almost always add Event Sourcing to an aggregate later; you can almost never remove it. The log becomes the source of truth, code depends on it, projections plug into it — going back to mutable state is a painful migration. So the sane default is mutable state, and the burden of proof falls on whoever wants events. The ultimate over-reach: stacking ES + CQRS + microservices 'because they go together' and getting a distributed monolith of replays — three bills, three irreversibilities, for a need you never measured.

№ 05Bilan & éditoWrap-up & editorial2 min

Garder l'état, ou garder les faits qui l'ont produit.Keep the state, or keep the facts that produced it.

État mutable vs Event Sourcing, côte à côteMutable state vs Event Sourcing, side by side
état mutable (défaut)mutable state (default)event sourcingevent sourcing
Ce qu'on stockeWhat you storel'état présentpresent stateles faits, en appendthe facts, appended
Le passéThe pastécrasé à chaque pasoverwritten each stepintact, rejouableintact, replayable
Lire l'étatRead the stateun SELECT directa direct SELECTun fold (ou un snapshot)a fold (or a snapshot)
Corriger une erreurFix an errorun UPDATEan UPDATEun événement compensatoirea compensating event
Changer le schémaChange the schemaune migration, une foisa migration, onceun upcaster, pour toujoursan upcaster, forever
On l'enlève ?Can you remove it?——presque jamaisalmost never

Quand ne pas faire d'Event SourcingWhen not to do Event Sourcing

Ici la liste compte plus que partout ailleurs, parce que le défaut sain est de ne PAS faire d'Event Sourcing. On reste en état mutable — et c'est presque toujours le bon choix — quand :Here the list matters more than anywhere else, because the sane default is NOT to do Event Sourcing. Stay with mutable state — and it's almost always the right choice — when:

  • l'histoire est un confort, pas une exigence : si perdre le passé ne viole aucune règle métier ni légale, tu n'as pas besoin d'ES. Un simple journal d'audit à côté répond au « qui a fait quoi » sans transformer toute la persistance en grand livre. C'est YAGNI (Vol 0 #3) appliqué à la donnée.history is a comfort, not a requirement: if losing the past violates no business or legal rule, you don't need ES. A plain audit log on the side answers 'who did what' without turning all persistence into a ledger. It's YAGNI (Vol 0 #3) applied to data.
  • l'équipe ne connaît pas le pattern : ES demande de penser en faits, en projections, en rejeu, en versions d'événements. Adopté sans cette culture, il produit un système que personne ne sait déboguer — où « quel est l'état ? » devient une énigme. Le coût d'apprentissage est réel et permanent.the team doesn't know the pattern: ES demands thinking in facts, projections, replay, event versions. Adopted without that culture, it yields a system no one can debug — where 'what's the state?' becomes a riddle. The learning cost is real and permanent.
  • tu ne te poses pas de questions temporelles : pas d'audit, pas de « pourquoi cet état », pas de reconstruction rétroactive de vues — alors l'append seul, le fold, les snapshots ne paient rien. Tu portes une taxe pour un bénéfice que tu n'encaisses jamais.you ask no temporal questions: no audit, no 'why this state', no retroactive view rebuilding — then append-only, fold, snapshots buy nothing. You carry a tax for a benefit you never collect.
  • dans le doute : comme on ne peut presque jamais retirer ES, l'incertitude tranche en faveur de l'état mutable. On peut toujours sourcer un agrégat plus tard ; on ne peut quasiment jamais dé-sourcer. Le défaut, c'est la photo ; le film se mérite.in doubt: since you can almost never remove ES, uncertainty rules for mutable state. You can always source an aggregate later; you can hardly ever un-source one. The default is the photo; the film is earned.

Le réflexe n'est donc pas « source tes événements, tu ne perdras rien » : c'est « le journal des changements est-il une fonctionnalité que mon métier doit fournir ? Si oui, sur cet agrégat-là, source. Sinon — et c'est le cas par défaut — garde l'état, et un audit à côté si besoin. »So the reflex isn't 'source your events, you'll lose nothing': it's 'is the log of changes a feature my domain must provide? If yes, on that aggregate, source it. If not — and that's the default case — keep the state, with an audit log on the side if needed.'

Mot de l'éditeurFrom the editor

Ce numéro est volontairement le plus prudent de la série, parce qu'Event Sourcing est le pattern le plus sur-vendu par la hype. Son idée est magnifique — n'efface jamais, garde les faits, l'état n'est qu'un calcul — et c'est précisément cette beauté qui pousse à l'adopter sans le besoin. Retiens l'asymétrie : on ajoute le sourcing à un agrégat presque quand on veut, on ne le retire presque jamais. Le défaut sain n'est donc pas « garde tout par sécurité », c'est « garde l'état, et ne source que là où l'histoire est, littéralement, le produit ». La sophistication n'est pas une vertu ; la sobriété en est une.This issue is deliberately the series' most cautious, because Event Sourcing is the pattern most over-sold by hype. Its idea is beautiful — never erase, keep the facts, state is just a computation — and it's precisely that beauty that pushes adoption without the need. Remember the asymmetry: you add sourcing to an aggregate almost whenever you like, you almost never remove it. So the sane default isn't 'keep everything to be safe', it's 'keep the state, and source only where history is, literally, the product'. Sophistication isn't a virtue; sobriety is.

Ce numéro referme le volume « Données & état ». Son arc tenait dans une seule relation, prise sous trois angles : le #1 a demandé où vivent les données et a rendu le métier ignorant de sa base ; le #2 a demandé si lire et écrire sont le même métier et les a séparés ; le #3 a demandé si l'on garde l'état ou les faits, et a posé le panneau danger. Trois questions sur le lien entre un domaine et la donnée qui lui survit. La suite change d'échelle : jusqu'ici on rangeait l'intérieur d'un système. Le Vol 6 demande où le couper — « cette frontière suit-elle une vraie couture métier, ou juste un découpage technique ? » — et commence, à rebours de la mode, par le monolithe modulaire.This issue closes the 'Data & state' volume. Its arc held in a single relationship, taken from three angles: #1 asked where the data lives and made the domain ignorant of its database; #2 asked whether reading and writing are the same job and separated them; #3 asked whether you keep the state or the facts, and planted the danger sign. Three questions about the bond between a domain and the data that outlives it. What follows changes scale: until now we organized the inside of a system. Vol 6 asks where to cut it — 'does this boundary follow a real business seam, or just a technical split?' — and starts, against the fashion, with the modular monolith.

Les voisins que ce numéro débloqueThe neighbors this issue unlocks

  • Snapshots — le remède au rejeu qui s'allonge : on fige périodiquement l'état replié, pour ne rejouer que les événements depuis le dernier instantané. Indispensable dès que le journal grossit — mais un cache à invalider à chaque évolution du fold, donc une complexité de plus à tenir.the cure for ever-lengthening replay: you periodically freeze the folded state, to replay only the events since the last snapshot. Indispensable once the log grows — but a cache to invalidate on every change to the fold, hence one more complexity to maintain.
  • CQRS + ES — le mariage naturel mais non obligatoire : le journal d'événements est le modèle d'écriture, les projections sont les modèles de lecture du #2. Puissant, et c'est là qu'on rejoint vraiment Event Sourcing — mais c'est cumuler deux factures, à ne faire que si les deux besoins existent pour de vrai.the natural but non-mandatory marriage: the event log is the write model, the projections are #2's read models. Powerful, and where Event Sourcing truly comes into its own — but it stacks two bills, to be done only if both needs genuinely exist.
  • Tables temporellesTemporal tables — le compromis tiède : la base elle-même garde l'historique des versions de chaque ligne, sans repenser le modèle en faits. On gagne le « comment était-ce à telle date » sans la taxe complète d'ES — souvent la réponse suffisante quand on ne voulait que l'audit, pas le rejeu.the lukewarm compromise: the database itself keeps the version history of each row, without rethinking the model as facts. You gain 'how was it on this date' without ES's full tax — often the sufficient answer when all you wanted was audit, not replay.

Le kata — écris-le, ne le relis pasThe kata — write it, don't reread it

Le titre du numéro : le grand livre qu'on ne peut plus fermer. Ici on l'écrit — un compte en append seul, dont le solde n'est jamais stocké mais replié depuis les faits : état = fold(événements). Copie le fichier et fais-le passer au vert sans jamais écraser ni supprimer une écriture ; corriger, c'est ajouter un fait compensatoire. Les tests intermédiaires te font gagner l'audit et les questions temporelles « gratuitement ». Le dernier est le sournois : il prouve que rejouer n'est pas sommer — l'ordre des faits porte le sens, et un fold naïf qui l'ignore laisse passer un découvert.The issue's title: the ledger you can no longer close. Here you write it — an append-only account whose balance is never stored but folded from the facts: state = fold(events). Copy the file and turn it green without ever overwriting or deleting an entry; to correct, you append a compensating fact. The middle tests hand you audit and temporal questions 'for free'. The last is the sneaky one: it proves that replaying isn't summing — the order of facts carries meaning, and a naive fold that ignores it lets an overdraft slip through.

test_event_sourced_ledger.py
# test_event_sourced_ledger.py — la spec EST le harnais : fais-le passer au vert.
# un grand livre en append seul ; l'état (le solde) est le REPLI des faits.
# jamais d'UPDATE, jamais de DELETE — on corrige en ajoutant.

def test_solde_est_le_fold_des_ecritures():
    acc = Account()
    acc.post(Deposited(100)); acc.post(Withdrawn(30))
    assert acc.balance() == 70                  # solde = fold(faits), écrit nulle part

def test_journal_est_append_seul():
    acc = Account()
    acc.post(Deposited(100)); assert len(acc.events) == 1
    acc.post(Withdrawn(30));  assert len(acc.events) == 2   # on empile, on n'écrase rien

def test_on_corrige_en_ajoutant_pas_en_effacant():
    acc = Account()
    acc.post(Deposited(100)); acc.post(Deposited(999))   # saisie fautive…
    acc.post(Corrected(-999))                   # …annulée par un fait compensatoire
    assert acc.balance() == 100
    assert len(acc.events) == 3                 # le faux dépôt reste VISIBLE dans l'histoire

def test_rejouer_donne_l_etat_a_une_date():
    acc = Account()
    acc.post(Deposited(100, at=1)); acc.post(Withdrawn(30, at=2))
    assert fold(acc.events, until=1) == 100     # rejoue jusqu'à t=1 : l'état d'alors
    assert fold(acc.events, until=2) == 70      # la question temporelle, gratuite

def test_snapshot_evite_de_tout_rejouer():
    acc = Account()
    for _ in range(1000): acc.post(Deposited(1))
    snap = acc.snapshot()                        # fige le solde replié à cet instant
    acc.post(Deposited(5))
    assert acc.balance_from(snap) == 1005        # ne rejoue QUE les faits après le snapshot

def test_l_ordre_des_faits_change_le_repli():
    acc = Account()
    acc.post(Withdrawn(50)); acc.post(Deposited(100))
    with raises(Overdrawn): fold_strict(acc.events)   # retirer AVANT de déposer = découvert
    # rejouer, ce n'est pas sommer : l'ordre porte le sens. le fold n'est pas commutatif.
Chez toi, cette semaine.In your codebase, this week.

Faites l'inventaire, agrégat par agrégat, avec le test brutal du chapitre 4 : « si je perds tout l'historique et ne garde que l'état présent, est-ce que je viole une exigence — ou est-ce que je perds juste un confort ? ». Liste d'un côté ceux où l'histoire est le produit (un solde, un dossier de conformité, un registre légal), de l'autre tout le reste. Puis cherche les faux besoins déjà couverts : grep -rn "created_at\|updated_at\|audit\|history" src/ — souvent, un simple journal d'audit à côté répondait déjà au « qui a fait quoi », sans transformer toute la persistance en grand livre.Take inventory, aggregate by aggregate, with chapter 4's blunt test: 'if I lose all the history and keep only present state, do I violate a requirement — or just lose a comfort?'. List on one side those where history is the product (a balance, a compliance file, a legal register), on the other everything else. Then hunt the false needs already covered: grep -rn "created_at\|updated_at\|audit\|history" src/ — often a plain audit log on the side already answered 'who did what', without turning all persistence into a ledger.