Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Auf Deutsch lesen

Section 7.5 sur 7.8 · Avancé · 26 min de lecture

CQRS et event sourcing

Leçon vidéo

5 min · narré en anglais, avec sous-titres
Écouter seulement (audio)
Lire la transcription

Cette leçon présente deux patrons souvent utilisés ensemble : séparer le modèle qui modifie les données des modèles qui les lisent, et stocker les données sous la forme d’un historique d’événements. Les deux sont puissants, et les deux ajoutent une réelle complexité.

Pensez au grand livre d’une banque. Chaque dépôt et chaque retrait y est inscrit, ligne après ligne. Rien n’est jamais effacé. Les erreurs se corrigent en ajoutant une ligne de correction. Votre solde est simplement le total de toutes les lignes. Et votre relevé mensuel est un résumé soigné, établi à partir du grand livre. L’event sourcing stocke les données comme le fait le grand livre. CQRS sépare le grand livre des relevés.

En 1988, Bertrand Meyer a proposé qu’une méthode change l’état ou renvoie une réponse, mais pas les deux. Vers 2010, Greg Young a appliqué cette séparation à des modèles entiers, et l’a appelée Command Query Responsibility Segregation, ou CQRS. Écrire et lire sont deux métiers différents. L’écriture exige des règles strictes, chaque fait n’étant stocké qu’une fois. La lecture exige de la vitesse, et des formes qui correspondent aux écrans. Un modèle unique qui essaie de faire les deux a tendance à mal faire les deux.

Voici comment les pièces s’assemblent, event sourcing compris. Côté écriture, une commande, comme retirer cinquante livres, parvient à un gestionnaire de commandes. Il charge le compte, applique les règles métier et ajoute de nouveaux événements à un event store en ajout seul. Une commande est une demande, elle peut donc être refusée. Côté lecture, un projecteur écoute ces événements et tient les modèles de lecture à jour : ici, une vue des soldes et une vue des relevés. Les requêtes reçoivent leur réponse de ces vues, sans jointures, sans logique métier et sans verrous côté écriture. Et comme les modèles de lecture sont dérivés, on peut toujours les jeter et les reconstruire.

La plupart des systèmes stockent l’état courant. Quand vous retirez de l’argent, le solde change et l’ancienne valeur disparaît. L’event sourcing renverse cette logique : chaque changement est stocké sous la forme d’un événement immuable dans un journal en ajout seul. Les événements sont nommés au passé, comme « argent retiré », car ils enregistrent des faits déjà acceptés. Un événement ne peut pas être refusé, seulement compensé par un événement ultérieur. L’état courant est ce que l’on obtient en rejouant les événements dans l’ordre. Les événements sont la source de vérité ; le solde est un calcul. Cette relecture doit toujours donner la même réponse : elle ne peut donc jamais appeler une horloge ou un autre service. Toute valeur venue de l’extérieur, comme un taux de change, est enregistrée dans l’événement.

Suivons un compte. Le premier événement l’ouvre, sans découvert autorisé. Le deuxième dépose cinq cents livres. Trois autres événements suivent : un retrait de cent vingt livres, un dépôt de soixante-quinze livres cinquante, et un retrait de trois cents livres. Le solde est maintenant de cent cinquante-cinq livres cinquante. Le client essaie maintenant de retirer deux cents livres. Le solde passerait à moins quarante-quatre livres cinquante, sous la limite, donc la commande est refusée et rien n’est ajouté. Un retrait de vingt livres est autorisé. Le gestionnaire ajoute l’événement six, en s’attendant à ce que la version six soit libre. Si une autre commande est passée avant, l’ajout échoue et il réessaie. Et si quelqu’un demande pourquoi le solde est de cent trente-cinq livres cinquante, les six événements sont la réponse complète.

Ce schéma montre le même flux. Sous chaque événement figure le solde cumulé après son application, une étape à la fois. Replier cinq événements est instantané, mais un compte très actif peut en avoir des centaines de milliers. Un snapshot stocke l’état à une version connue, si bien qu’une reconstruction n’applique que les événements qui le suivent. Un snapshot est un cache, pas la source de vérité. Et les questions sur le passé deviennent faciles. Le solde à la version trois est simplement le pli des trois premiers événements : trois cent quatre-vingts livres. Pas besoin de table d’historique.

Les coûts sont réels. Quand les projections s’exécutent en arrière-plan, un utilisateur peut effectuer un retrait puis voir un solde qui ne l’inclut pas encore. Les événements sont stockés pour toujours, si bien que leur schéma ne peut jamais être simplement modifié. Les anciens événements sont convertis dans la forme actuelle au moment de leur lecture, une technique appelée upcasting. Supprimer des données personnelles d’un journal immuable est difficile. Une technique courante est le crypto-shredding : chiffrer les champs personnels avec une clé par client, puis détruire la clé. Savoir si cela vaut effacement relève d’une appréciation juridique. Et Martin Fowler avertit que, pour la plupart des systèmes, CQRS ajoute une complexité risquée. Utilisez ces patrons pour un domaine dont les besoins de lecture et d’écriture sont réellement différents, comme un grand livre ou le cycle de vie d’une commande, jamais pour un système entier par défaut.

La section complète contient le code, plus de détails sur les snapshots et le versionnement des événements, et un guide de décision. Ensuite, dans la section sept point six, nous verrons la mise en cache. À très vite.

Ce qu’il faut savoir

De la séparation commande-requête à CQRS

L’idée part de peu. Dans Object-Oriented Software Construction (première édition en 1988), Bertrand Meyer a proposé la séparation commande-requête : chaque méthode doit être soit une commande, qui change l’état et ne renvoie rien, soit une requête, qui renvoie une réponse et ne change rien. Poser une question ne devrait jamais changer la réponse. Martin Fowler fait remarquer que la règle admet des exceptions raisonnables (dépiler un élément d’une pile modifie la pile et renvoie l’élément), mais, comme règle par défaut, elle rend le code plus facile à comprendre, car les requêtes peuvent être appelées librement, dans n’importe quel ordre, sans effets de bord.

Vers 2010, Greg Young a étendu le principe des méthodes à des modèles entiers et a appelé le résultat Command Query Responsibility Segregation (CQRS), la séparation des responsabilités entre commandes et requêtes. Au lieu d’un seul modèle objet, et généralement d’un seul ensemble de tables, servant à la fois aux mises à jour et aux lectures, le système dispose d’un modèle d’écriture qui traite les commandes et applique les règles métier, et d’un ou plusieurs modèles de lecture façonnés pour les questions que posent les utilisateurs. L’idée clé est qu’écrire et lire sont deux métiers différents. L’écriture exige des règles strictes, de petites transactions et une structure normalisée, pour qu’un fait soit stocké une seule fois. La lecture exige de la vitesse, des formes dénormalisées qui correspondent aux écrans et aux rapports, et doit souvent absorber bien plus de trafic. Un modèle unique qui essaie de bien faire les deux a tendance à mal faire les deux dès que le domaine devient riche.

Ce que CQRS change concrètement

Côté écriture, une commande comme WithdrawMoney(account 42, £50) arrive à un gestionnaire de commandes (command handler). Le gestionnaire charge l’agrégat concerné (le groupe de données qui doit rester cohérent au sein d’une même transaction, un terme de la conception pilotée par le domaine présenté à la section 5.7), lui demande d’appliquer les règles métier et enregistre le résultat. Les commandes sont des demandes et peuvent être refusées : le compte peut être gelé ou les fonds insuffisants. Côté lecture, une requête comme montre-moi les 30 dernières transactions avec les soldes cumulés reçoit sa réponse d’un modèle de lecture construit exactement pour cet écran, sans jointures, sans logique métier et sans verrous sur les tables d’écriture.

Il faut bien que quelque chose tienne les modèles de lecture à jour. Cette tâche revient à une projection : du code qui écoute les changements côté écriture et met à jour un modèle de lecture. Une projection peut maintenir une table des soldes, une autre un index de recherche des transactions, d’autres encore un document de relevé mensuel et un feature store de scoring de fraude, chacun à partir du même flux de changements. Les modèles de lecture sont donc des données dérivées : on peut toujours les jeter et les reconstruire à partir du côté écriture. C’est cette propriété qui permet d’ajouter sans risque un nouveau modèle de lecture l’an prochain, ou de modifier la forme d’un ancien.

CQRS se décline par degrés, et le degré le plus léger est souvent tout ce dont un système a besoin. Au plus léger, les modèles d’écriture et de lecture sont des classes différentes au-dessus de la même base de données, les lectures étant servies par des vues ou par du code de requête dédié. Au niveau intermédiaire, les modèles de lecture vivent dans des tables séparées, mises à jour dans la même transaction que l’écriture. Au plus lourd, les modèles de lecture vivent dans des stockages différents (un moteur de recherche, une base de données orientée documents, un cache) et sont mis à jour de façon asynchrone à partir d’événements, ce qui apporte scalabilité et flexibilité, mais aussi la cohérence à terme. CQRS n’exige ni event sourcing, ni courtier de messages (message broker), ni microservices ; ce sont des décisions distinctes, souvent prises ensemble, mais pas nécessairement.

CQRS avec event sourcing

CQRS avec event sourcing : les commandes passent par un gestionnaire et un agrégat vers un event store en ajout seul ; un projecteur en tire des modèles de lecture, servis par un gestionnaire de requêtesClientapp oupage webÉCRITURELECTUREHandlercommandesAgrégatcompteEvent storelog en ajoutseulcommandelire, ajouterévénementsProjecteurVue des soldesVue des relevésHandlerrequêtesrequête
Les commandes et les requêtes empruntent des chemins différents ; l’event store est la source de vérité, et chaque modèle de lecture en est dérivé par un projecteur.

Event sourcing : l’état comme suite de faits

La plupart des systèmes stockent l’état courant. Quand Asha retire £120, la ligne de accounts passe de £500 à £380 et l’ancienne valeur disparaît, à moins que quelqu’un n’écrive aussi une ligne d’audit. L’event sourcing, décrit par Martin Fowler en 2005, renverse cette logique : le système stocke chaque changement sous la forme d’un événement immuable dans un journal en ajout seul, et l’état courant est ce que l’on obtient en rejouant les événements dans l’ordre. Les événements sont la source de vérité ; le solde est un calcul.

Les événements sont nommés au passé, car ils enregistrent des faits qui se sont déjà produits et ont été acceptés : AccountOpened, MoneyDeposited, MoneyWithdrawn. Une commande (WithdrawMoney) est une demande qui peut être refusée ; un événement (MoneyWithdrawn) ne peut plus être refusé, seulement compensé par un événement ultérieur. Chaque agrégat a son propre flux d’événements, numéroté à partir de la version 1, et l’event store garantit que les événements d’un flux sont conservés dans l’ordre.

Reconstruire l’état est un pli à gauche (left fold) : partir de rien, appliquer le premier événement, appliquer le deuxième au résultat, et ainsi de suite. La fonction d’application doit être pure et déterministe : elle ne peut appeler ni une horloge, ni un générateur de nombres aléatoires, ni un autre service, car rejouer les mêmes événements doit toujours donner le même état. Toute valeur venue de l’extérieur (un taux de change, un horodatage, un score de fraude) est enregistrée dans l’événement au moment où elle est décidée pour la première fois.

L’écriture suit un cycle fixe. Le gestionnaire de commandes charge le flux, le replie en état courant, exécute la règle métier et ajoute les nouveaux événements avec la version qu’il s’attend à écrire. Si une autre commande a ajouté des événements au même flux entre-temps, l’ajout échoue et le gestionnaire réessaie avec un état à jour. C’est la concurrence optimiste de la section 7.3, appliquée flux par flux, et c’est pourquoi deux retraits simultanés ne peuvent pas dépenser tous deux les mêmes £100.

Exemple commenté

Exemple commenté : un compte bancaire sous forme d’événements

Le compte courant d’Asha est le flux account-42. Les montants sont stockés en pence, comme la section 0.2 le recommande pour l’argent. Cinq événements ont été ajoutés ; elle essaie ensuite de retirer £200.

v1 AccountOpened (découvert autorisé £0)État : solde £0,00, version 1
v2 MoneyDeposited £500,00£0,00 + £500,00 = £500,00, version 2
v3 MoneyWithdrawn £120,00£500,00 − £120,00 = £380,00, version 3
v4 MoneyDeposited £75,50£380,00 + £75,50 = £455,50, version 4
v5 MoneyWithdrawn £300,00£455,50 − £300,00 = £155,50, version 5
Commande : WithdrawMoney £200,00Règle : le solde moins le montant ne doit pas descendre sous moins le découvert autorisé. £155,50 − £200,00 = −£44,50, sous £0. Commande refusée ; aucun événement n’est ajouté.
Commande : WithdrawMoney £20,00£155,50 − £20,00 = £135,50 est autorisé. Le gestionnaire ajoute v6 MoneyWithdrawn £20,00, en s’attendant à ce que la version 6 soit libre.
Requête temporelle : solde après v3Replier seulement v1 à v3 : £380,00. Aucune table d’historique n’a été nécessaire.
Question d’audit : pourquoi le solde est-il de £135,50 ?Les six événements sont la réponse complète, dans l’ordre, chacun indiquant qui, quand et pourquoi.

La commande refusée n’a laissé aucune trace dans le flux. Si la banque doit enregistrer les refus (pour l’analyse de la fraude, par exemple), elle ajoute un événement distinct WithdrawalRefused, qui ne modifie pas le solde.

Un grand livre en partie double est le plus ancien système en event sourcing qui soit : les comptables n’effacent jamais une ligne, ils passent une écriture de correction. Les domaines bancaires se prêtent donc naturellement à l’event sourcing, ce qui explique qu’on l’enseigne souvent avec un compte.

# A bank account as a stream of events, folded into current state.
# Amounts are in pence so that no floating-point rounding creeps in.
from functools import reduce

events = [
    {"v": 1, "type": "AccountOpened",  "overdraft_limit": 0},
    {"v": 2, "type": "MoneyDeposited", "amount": 50000},
    {"v": 3, "type": "MoneyWithdrawn", "amount": 12000},
    {"v": 4, "type": "MoneyDeposited", "amount": 7550},
    {"v": 5, "type": "MoneyWithdrawn", "amount": 30000},
]

def apply(state, e):                       # pure: old state + event -> new state
    if e["type"] == "AccountOpened":
        return {"balance": 0, "limit": e["overdraft_limit"], "version": e["v"]}
    sign = 1 if e["type"] == "MoneyDeposited" else -1
    return {**state, "balance": state["balance"] + sign * e["amount"], "version": e["v"]}

def decide_withdraw(state, amount):        # command side: check the rule, emit events
    if state["balance"] - amount < -state["limit"]:
        raise ValueError("insufficient funds")
    return [{"v": state["version"] + 1, "type": "MoneyWithdrawn", "amount": amount}]

now = reduce(apply, events, None)          # {'balance': 15550, 'limit': 0, 'version': 5}
as_at_v3 = reduce(apply, events[:3], None) # temporal query: balance 38000
Replier le flux en état courant, décider d’une commande d’après cet état, et répondre à une requête temporelle en ne repliant qu’une partie du flux.
-- An event store can be an ordinary table. The primary key enforces
-- optimistic concurrency: only one writer can append version 6.
CREATE TABLE account_events (
  account_id  uuid        NOT NULL,
  version     integer     NOT NULL,
  event_type  text        NOT NULL,
  data        jsonb       NOT NULL,
  recorded_at timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (account_id, version)
);

-- The command handler read the stream up to version 5, decided, and now appends:
INSERT INTO account_events (account_id, version, event_type, data)
VALUES ('2f9c0d6e-4a51-4c7e-9a43-6d1f2b7e8a10', 6, 'MoneyWithdrawn', '{"amount": 2000}');
-- If a concurrent command already appended version 6, this fails with a
-- unique violation; the handler reloads the stream and decides again.
Un event store minimal dans une base de données relationnelle : la clé primaire composite transforme un ajout concurrent en conflit détectable.

Pli, snapshots et requêtes temporelles

Cinq événements d’un flux de compte sont repliés dans l’ordre en soldes cumulés ; un snapshot à la version 4 permet de reconstruire en n’appliquant que la version 5Flux account-42 : événements dans l’ordre de leur ajoutv1AccountOpenedlimite £0£0.00v2MoneyDeposited£500,00£500.00v3MoneyWithdrawn£120,00£380.00v4MoneyDeposited£75,50£455.50v5MoneyWithdrawn£300,00£155.50Solde cumulé après chaque événement appliqué (pli à gauche)Snapshot stocké à v4 : solde £455,50reconstr. = snapshot + v5 seulRequête temporelle : le solde à v3 est le pli de v1 à v3 seulement, soit £380,00.
L’état courant est le pli de tous les événements ; un snapshot raccourcit le pli, et replier un préfixe du flux répond aux questions sur le passé.

Ce que vous apporte l’event sourcing

Une piste d’audit complète par construction. Dans un système fondé sur l’état, le journal d’audit est une seconde écriture que quelqu’un peut oublier, et il peut contredire les données. Dans un système en event sourcing, le journal est la donnée : il ne peut donc pas diverger. Les régulateurs de la banque, de l’assurance et de la santé y attachent une grande valeur.

Requêtes temporelles. Quel était le solde à 23 h 59 le 31 mars ? ou Quelles commandes étaient au statut PREPARING quand l’écran de la cuisine est tombé en panne ? deviennent des plis sur un préfixe de chaque flux, plutôt qu’un travail d’enquête sur les sauvegardes.

Reconstructions et nouveaux modèles de lecture. Comme les modèles de lecture sont dérivés, une équipe peut corriger un bug dans une projection, supprimer ce qu’elle a produit et rejouer tous les événements pour obtenir une vue corrigée. Elle peut aussi construire un modèle de lecture auquel personne n’avait pensé lors de l’enregistrement des événements, comme un rapport sur la fréquence à laquelle les clients retirent de l’argent liquide dans l’heure qui suit le versement de leur salaire, sur tout l’historique.

Débogage et analyse de scénarios (what-if). Copier un flux dans un environnement de test et le rejouer reproduit exactement l’état qui a causé un bug. Rejouer les mêmes événements avec des règles modifiées montre ce qu’auraient fait les nouvelles règles.

Une source naturelle d’événements d’intégration. Les événements peuvent alimenter d’autres services, même s’il vaut mieux traiter les événements stockés dans un flux comme un détail privé du service propriétaire. Les publier directement couple chaque consommateur au modèle interne ; beaucoup d’équipes les traduisent plutôt en un ensemble plus restreint d’événements d’intégration publics, conçus comme les contrats publiés et durables décrits à la section 6.4.

Snapshots et flux longs

Replier cinq événements est instantané ; replier 400 000 événements pour un compte commerçant très actif à chaque commande ne l’est pas. Un snapshot (instantané) stocke l’état replié d’un flux à une version connue, si bien que le chargement devient lire le dernier snapshot, puis n’appliquer que les événements qui le suivent. Les snapshots sont un cache, pas une source de vérité : ils peuvent être supprimés et recréés à tout moment, en arrière-plan, sans arrêter le système. Prenez-en un tous les N événements ou selon un calendrier, et stockez avec eux la version du code d’application, car un snapshot produit par un ancien code est faux dès que la logique du pli change.

Avant de recourir aux snapshots, vérifiez la conception des flux. Des flux très longs signifient souvent que l’agrégat est trop gros. Un compte bancaire qui vit 40 ans peut être modélisé comme une série de flux plus courts, un par période de relevé, chacun ouvert par un événement BalanceCarriedForward qui enregistre le solde de clôture de la période précédente. Cela reflète la façon dont les comptables clôturent leurs comptes, et cela garde chaque flux assez court pour être chargé rapidement.

Les coûts, en toute honnêteté

Complexité et manque de familiarité. L’event sourcing change la façon dont chaque développeur de l’équipe pense la persistance. Des questions simples comme quels clients habitent à Leeds ? ne peuvent pas du tout être posées à l’event store ; elles exigent une projection. Les outils pour inspecter, migrer et réparer les event stores sont moins matures que pour les tables relationnelles, et moins d’ingénieurs ont fait tourner un tel système en production.

Cohérence à terme des modèles de lecture. Quand les projections s’exécutent de façon asynchrone, un utilisateur peut effectuer un retrait puis voir un solde qui ne l’inclut pas encore. Les parades habituelles consistent à renvoyer la nouvelle version du flux depuis la commande et à laisser le côté lecture attendre que sa projection ait traité cette version (une garantie de lecture de ses propres écritures, comme à la section 7.4) ; à mettre à jour l’interface utilisateur de manière optimiste à partir du résultat de la commande ; ou à mettre à jour les modèles de lecture critiques de façon synchrone, dans la même transaction que l’ajout. Les projections doivent aussi être idempotentes et enregistrer la position qu’elles ont traitée, car des événements peuvent être livrés plus d’une fois après un crash.

Versionnement des événements. Les événements sont stockés pour toujours, si bien que leur schéma ne peut jamais être simplement modifié. Quand MoneyDeposited gagne un champ de devise, des années d’anciens événements ne l’ont toujours pas. Les techniques standard, cataloguées par Greg Young dans Versioning in an Event Sourced System, comprennent les lecteurs tolérants, qui fournissent des valeurs par défaut pour les champs manquants, l’upcasting (une fonction qui convertit une ancienne version d’événement dans la forme actuelle au moment de la lecture, afin que le code du domaine ne voie qu’une seule version), l’introduction d’un nouveau type d’événement à côté de l’ancien et, en dernier recours, la copie d’un flux entier dans un nouveau flux avec des événements transformés. La section 7.7 présente les règles générales de compatibilité.

Supprimer des données personnelles. Un journal immuable se heurte au droit à l’effacement prévu par la législation sur la protection des données. La technique courante est le crypto-shredding (effacement cryptographique) : les champs personnels des événements sont chiffrés avec une clé propre à chaque client, conservée dans un stockage de clés séparé, et l’effacement consiste à détruire cette clé afin que le texte chiffré ne puisse plus être lu. L’autre solution consiste à garder entièrement les données personnelles hors des événements et à ne stocker qu’une référence à un enregistrement qui peut être supprimé. Savoir si la destruction d’une clé vaut effacement relève d’une appréciation juridique plutôt que technique ; convenez donc de l’approche avec le délégué à la protection des données (DPD). La section 8.7 présente les obligations plus larges.

Charge d’exploitation. Reconstruire une projection sur un milliard d’événements peut prendre des heures ; les équipes ont donc besoin d’un moyen d’exécuter la nouvelle projection à côté de l’ancienne et de basculer quand elle a rattrapé son retard. Les event stores croissent sans limite, et le stockage ainsi que les temps de sauvegarde et de relecture croissent avec eux.

Guide de décision

Guide de décision : cette partie du système doit-elle utiliser CQRS ou l’event sourcing ?

Posez ces questions pour un contexte délimité, pas pour le système entier.

  1. Cette zone se résume-t-elle surtout à de la création, de la lecture, de la mise à jour et de la suppression simples, avec peu de règles métier ?
    OuiUtilisez un modèle classique avec une seule base de données. Aucun des deux patrons ne sera rentable ici.
    NonContinuez : le domaine a un véritable comportement qui mérite d’être modélisé.
  2. Les lectures et les écritures diffèrent-elles fortement par leur forme ou leur volume (par exemple, de nombreux écrans dénormalisés, de la recherche, ou mille lectures pour une écriture) ?
    OuiEnvisagez CQRS. Commencez par la forme la plus légère : un code de lecture séparé ou des vues sur la même base de données.
    NonGardez un modèle unique ; ajoutez une réplique en lecture ou un cache si la charge de lecture est le seul problème.
  3. L’historique a-t-il une valeur en soi : audit réglementaire, requêtes temporelles, relecture avec une logique corrigée, ou nombreux futurs modèles de lecture à partir des mêmes faits ?
    OuiEnvisagez l’event sourcing pour ce contexte, idéalement un domaine de type grand livre, comme les comptes, les paiements ou le cycle de vie des commandes.
    NonStockez l’état courant ; publiez des événements avec une outbox si d’autres services doivent être informés des changements.
  4. L’équipe a-t-elle l’expérience, les outils et le temps nécessaires pour gérer le versionnement des événements, la reconstruction des projections et la cohérence à terme ?
    OuiAllez-y, gardez la zone en event sourcing réduite, et consignez la décision et ses raisons dans un ADR.
    NonStockez l’état courant plus une table d’audit en ajout seul, qui apporte l’essentiel du bénéfice d’audit pour une fraction du coût.

Quand n’utiliser ni l’un ni l’autre, et erreurs courantes

Aucun des deux patrons ne convient à un domaine qui consiste surtout en formulaires sur des données (forms over data), à une petite équipe qui construit sa première version, ou à un système dont le seul problème est la charge de lecture (une réplique en lecture ou un cache coûte bien moins cher). L’event sourcing est aussi mal adapté lorsque des données doivent être réellement supprimées à la demande et en grand volume, ou lorsque la plupart des requêtes recoupent de nombreux agrégats, puisque chacune de ces requêtes a besoin de sa propre projection.

Les erreurs courantes sont prévisibles. Les équipes appliquent les patrons à l’ensemble du système au lieu d’un seul contexte. Elles exposent les événements stockés comme contrat public, figeant ainsi leur modèle interne. Elles mettent des valeurs dérivées ou des appels externes dans la fonction d’application, si bien que la relecture donne des réponses différentes. Elles conçoivent les événements comme des blocs génériques AccountUpdated, qui indiquent que quelque chose a changé mais pas ce qui s’est passé ni pourquoi, et perdent l’essentiel de leur valeur. Enfin, elles oublient qu’un modèle de lecture est un cache : elles se mettent à y écrire directement, après quoi il ne peut plus être reconstruit.

Utilisée de façon sélective, la combinaison est puissante. Une plateforme de livraison de repas peut mettre en event sourcing le cycle de vie des commandes (passée, acceptée, en préparation, récupérée, livrée, remboursée), car les litiges et les remboursements exigent l’historique exact, tout en conservant les cartes, les profils de restaurants et les paramètres utilisateur dans des tables ordinaires. Une banque peut mettre en event sourcing le grand livre et les paiements tout en stockant de façon classique les coordonnées des clients, ce qui garde aussi l’essentiel des données personnelles hors du journal immuable.

Vérifiez votre compréhension

Pourquoi la fonction qui applique les événements à l’état ne doit-elle jamais appeler l’horloge système ?

Parce que rejouer les mêmes événements doit toujours produire le même état. Une horloge (ou un nombre aléatoire, ou un appel externe) ferait donner à la relecture des réponses différentes ; une telle valeur doit être capturée dans l’événement au moment où elle est décidée pour la première fois.

Quelle est la différence entre la commande WithdrawMoney et l’événement MoneyWithdrawn ?

La commande est une demande qui peut être refusée par les règles métier ; l’événement est un fait enregistré qui a déjà été accepté et ne peut être que compensé par un événement ultérieur, jamais supprimé.

Une projection comporte un bug qui a produit des relevés erronés pendant trois mois. En quoi l’event sourcing aide-t-il ?

Comme le modèle de lecture est dérivé, l’équipe corrige la projection, construit un nouveau modèle de lecture en rejouant tous les événements (généralement à côté de l’ancien) et bascule dessus, sans avoir à réparer les données à la main.

Un snapshot fait-il partie de la source de vérité ?

Non. C’est un pli mis en cache d’un flux à une version donnée, qui peut être supprimé et recréé à partir des événements à tout moment.

Suivant · 7.6Mise en cachePourquoi les caches accélèrent les systèmes et ce qu’ils coûtent : cache-aside, read-through, write-through, write-behind et refresh-ahead, TTL et invalidation, ruées sur le cache, niveaux de cache, en-têtes HTTP et éviction.Continuer →

Sources et lectures complémentaires

Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.

Demander : « Notre équipe des paiements veut mettre toute la plateforme en event sourcing. Quelles parties en tireraient un réel bénéfice, et comment traiterions-nous les demandes d’effacement au titre du RGPD ? »