2 points par GN⁺ 2023-12-18 | 1 commentaires | Partager sur WhatsApp
  • Le modèle CRUD relationnel montre bien la structure de stockage, mais il a tendance à écraser le processus métier, ce qui rend difficile de suivre ce qui s’est réellement passé dans le système
  • L’Event Sourcing conserve les événements immuables produits après chaque action dans un Event Stream, puis relit cette liste au moment de prendre une décision pour déterminer l’état courant
  • La modélisation consiste d’abord à identifier les événements, puis à relier les commandes (commands) à l’intention d’action de l’utilisateur et aux règles métier afin de comprendre le processus
  • Pour trouver des candidats événements dans des données relationnelles existantes, on peut examiner les colonnes d’état, les colonnes de date, le caractère nullable et les relations 1:N, mais il est risqué de supposer qu’on peut reconstituer un historique complet à partir des seules valeurs d’état
  • Lorsqu’on migre des données où seul l’état final subsiste, il est plus réaliste de commencer avec un événement d’import explicite comme Order Imported plutôt que d’essayer de reconstruire de force les événements passés, puis de valider le tout de manière répétée dans un environnement sûr

Voir les données CRUD sous un angle orienté événements

  • Un modèle de données relationnel montre quelles données sont stockées, mais il est difficile d’y voir ce qui s’est passé dans le système et comment les processus interagissent
  • L’approche CRUD classique peut écraser les données et faire perdre des informations métier importantes
  • L’Event Sourcing privilégie la qualité de l’information plutôt que la taille du stockage, en enregistrant sous forme d’événements les faits produits après chaque action

Modèle de base de l’Event Sourcing

  • Un événement est un fait concernant quelque chose qui s’est déjà produit, et c’est une donnée immuable qui ne peut plus être modifiée une fois enregistrée
  • Un Event Stream est une liste ordonnée de tout ce qui s’est produit pour un enregistrement donné
  • On ne peut pas modifier les événements passés, mais on peut corriger une erreur précédente en ajoutant un nouvel événement à la fin
  • Au moment de prendre une décision, on lit et on vérifie la liste des événements pour déterminer l’état actuel et l’action suivante

Ordre de modélisation du processus

  • La modélisation commence d’abord par la découverte des événements
  • On identifie ensuite les commandes (commands) afin de définir l’intention derrière l’action à exécuter
  • Enfin, on formalise les règles métier
  • Les événements deviennent l’axe central permettant aux équipes techniques et métier de comprendre ensemble le processus
  • Avec EventStorming d’Alberto Brandolini, par exemple, on peut comprendre un processus en observant ensemble événements, commandes et règles

Trouver des candidats événements dans des données relationnelles existantes

  • 1. Examiner les colonnes d’état

    • Les valeurs d’une colonne status peuvent refléter les étapes du cycle de vie des données
    • Si une commande possède des états comme initiated, shipped ou paid, chacun peut devenir un candidat événement tel que Order Initiated, Order Shipped ou Order Paid
    • Cependant, une valeur d’état peut n’être qu’une interprétation aplatie du processus métier ; il ne faut donc pas supposer qu’elle est complète
    • Il faut éviter de nommer les événements d’après des actions CRUD comme Order Created, Order Updated ou Order Deleted
    • State Obsession est présenté comme une approche à éviter
  • 2. Vérifier les colonnes de date

    • Les colonnes de date peuvent indiquer les moments importants dans le cycle de vie du processus
    • CreatedDate et ModifiedDate apportent peu d’informations, alors que ShipmentDate, DeliveryDate et OrderPlacementDate donnent de meilleurs indices
    • Exemples :
      • ShipmentDate peut être un indice en faveur de l’introduction de l’événement Order Shipped
      • OrderPlacementDate suggère que Order Placed pourrait être un meilleur nom que Order Initiated
      • DeliveryDate montre qu’un événement Order Delivered peut être nécessaire
    • Ces indices doivent être validés avec des experts du domaine pour les aligner sur le processus métier réel
  • 3. Analyser le caractère nullable des colonnes

    • Une colonne non nullable correspond à une donnée qui doit toujours être fournie
    • Une colonne nullable peut être renseignée plus tard par une autre action, ou être optionnelle
    • Dans un Ordering Process, si une colonne est obligatoire, alors cette donnée doit aussi figurer dans le premier événement Order Initiated
    • Un seul type d’événement n’est pas toujours le point de départ du stream ; il peut y avoir plusieurs événements initiaux
  • 4. Repérer les tables avec beaucoup de relations 1:N

    • Pour trouver les frontières d’un stream, on peut commencer par examiner les tables qui ont beaucoup de relations 1:N
    • Les tables qui portent de nombreuses relations côté « one » sont de bons candidats pour définir un type de stream
    • Il faut aussi évaluer logiquement si les données peuvent exister indépendamment les unes des autres
      • un shipment peut relever d’un processus distinct de l’order
      • un order line existe difficilement sans order
    • La discussion sur ces frontières permet de découvrir davantage d’événements et d’élargir la compréhension du processus

Ne pas créer de faux événements pendant la migration

  • Les données relationnelles sont un état final aplati ; si l’on essaie d’en déduire les événements détaillés du passé à partir de ce seul état, on risque l’échec ou l’inexactitude
  • Plutôt que de fabriquer de force de petits événements historiques, mieux vaut fournir explicitement un événement Order Imported contenant l’état courant complet et le code d’interprétation
  • Un événement d’import montre clairement comment les données ont été intégrées, ce qui peut être crucial pour la résolution de problèmes et le diagnostic

Valider avec un prototype

  • Une migration doit être testée sous forme de prototype dans un environnement sûr, afin de vérifier comment le modèle se comporte réellement
  • Il faut comparer le résultat aux attentes et l’ajuster de manière itérative
  • Au lieu de se précipiter, il faut avancer sans perdre les informations existantes, puis améliorer le modèle par la suite en s’appuyant sur elles
  • La stratégie générale de migration de données relationnelles vers un modèle orienté document est également liée à General strategy for migrating relational data to document-based

1 commentaires

 
GN⁺ 2023-12-18
Avis de Hacker News
  • 2c : si d’autres parties de l’app ont aussi besoin de PostgreSQL, mieux vaut stocker également les données d’événements dans PostgreSQL + des outils de reporting FOSS (Apache Superset, Metabase, etc.) et tenir grosso modo jusqu’à 2 To
    Ensuite, il faut décider s’il est nécessaire de garder les 2 To entièrement en ligne, ou si des résumés par jour/heure suffisent. Dans le second cas, continuer avec PostgreSQL est largement suffisant[1]
    Un client traite plus de 10 To, 1 500 événements par seconde, 600 octets par enregistrement (80 Go par jour avant indexation), ne garde en ligne que 2 jours de données détaillées, résume le reste et déplace le détail vers S3, où il reste interrogeable avec Athena SQL[2]
    Le coût total, portail de reporting client inclus, est inférieur à 2 000 dollars, et les insertions comme les requêtes de reporting tournent sur AWS RDS multi-AZ avec basculement automatique (db.m7g.2xlarge), avec une charge inférieure à 2 %. Comme l’équipe métier crée elle-même les graphiques et visualisations, un ingénieur consacre moins de 5 heures par mois à la maintenance
    Avec des outils propriétaires, quelques graphiques sont certes « fournis par défaut », mais avec pgsql les données sont au même endroit, il n’y a qu’un seul système à apprendre, un seul système à maintenir en ligne/répliquer/sauvegarder/restaurer, un seul système à sécuriser/faire monter en charge, un seul fournisseur à gérer, et des millions d’ingénieurs connaissent ce système
    Dans des systèmes comme Preset ou Metabase, créer 12 graphiques prend une heure, et des personnes non techniques peuvent le faire
    À titre indicatif, je suis biaisé, mais j’ai vu pendant plus de 20 ans des bases de données et des systèmes de reporting apparaître puis disparaître, tandis que le bon vieux PostgreSQL s’améliore chaque année
    https://instances.vantage.sh/aws/rds/db.m7g.2xlarge?region=u...
    [1] Si c’est vraiment nécessaire, il existe aussi des systèmes compatibles PostgreSQL pour étendre davantage. Aurora peut monter à 3–5×, TimescaleDB à 10×, CitusDB à 10× ou plus. Chacun implique de devenir un peu non standard, donc je ne les recommande pas avant d’en avoir réellement besoin
    [2] Le tableau de bord de reporting client doit répondre en moins d’une seconde, ce que PostgreSQL fournit en interrogeant des tables de synthèse indexées. Athena répond en environ 1 à 2 secondes grâce à des scans parallèles

    • Dans cette approche, même quand on a besoin de « voyage dans le temps », de « restauration d’un état écrasé » ou de « réinterprétation d’événements passés », un simple journal d’audit peut suffire dans certains cas
      Il suffit de conserver des instantanés des données avant enregistrement, de disposer de scripts qui identifient et collectent des séquences d’événements particulières, puis de faire valider par un humain avant d’appliquer rétroactivement en masse les effets d’une nouvelle logique
      Des outils comme https://django-simple-history.readthedocs.io/en/latest/ constituent une solution simple et à peu près fiable pour créer des tables d’audit, et si l’accès direct à la base de données doit aussi être audité, on peut ajouter des triggers Postgres
      En théorie, j’aime l’event sourcing, mais en pratique il impose trop de boilerplate pour ajouter de nouveaux flux CRUD ou déployer rapidement et de manière fiable les interventions et hotfixes qu’une startup en phase initiale ou intermédiaire doit souvent faire face à des situations imprévues
      À moins de mettre en place quelque chose comme des rails de traitement des paiements, l’event sourcing n’est pas forcément le bon choix
      Il y a aussi une bonne discussion sur les inconvénients de l’event sourcing sur https://news.ycombinator.com/item?id=17817375 (2018)
    • Ce commentaire a plus de valeur que l’article lui-même
      Le seul problème de PostgreSQL est qu’il présente des problèmes de scalabilité intéressants côté insertion. On recommande généralement de placer une file entre la source d’événements et la DB
    • Je me demande si, en gros, cela revient à avoir une table définie comme {id:uuid,created_at:timestamptz,data:jsonb}
      En particulier, quand la structure des événements varie beaucoup et que leurs définitions évoluent, il est difficile de tirer pleinement parti des fonctionnalités d’index JSONB
      Il faudra sans doute que je maîtrise mieux cette documentation : https://www.postgresql.org/docs/current/datatype-json.html#J...
    • Je me demande où trouver un article détaillé sur la mise en place d’un tel système
  • Il y a quelque temps, dans mon équipe, nous avons sérieusement envisagé l’event sourcing, mais à mes yeux cela ressemblait à une solution en quête de problème.
    Cela aurait peut-être pu fonctionner chez nous, mais les bénéfices n’étaient pas immédiatement évidents, et les risques et tâtonnements liés à l’adoption d’une nouvelle approche ne semblaient finalement pas être ce qu’il y avait de mieux pour le projet ou l’entreprise, donc nous avons abandonné.
    C’était peut-être le genre de décision qui nous a fait manquer une occasion d’apprentissage avec un outil, mais je ne regrette pas de ne pas être descendu dans ce terrier sans qu’un renard nous poursuive.

    • Un système ennuyeux et traditionnel qui fonctionne bien devient une menace pour une équipe d’ingénierie hypertrophiée qui n’a rien à faire et cherche de quoi embellir les CV.
      Le « problème » que cette solution résout, c’est précisément celui-là.
    • Par exemple, les bases de données temporelles ont pas mal de sens pour les données financières.
      Mais dans la plupart des cas, il suffit d’utiliser une base de données classique et de stocker l’historique des changements passés dans des tables auxiliaires. La base principale se comporte alors comme une sorte de vue matérialisée.
    • Presque toutes les données que l’on stocke en SQL seraient en fait mieux adaptées à une base de données documentaire, mais comme personne n’est vraiment à l’aise avec cette approche, on continue comme avant.
      Je n’ai pas de gros grief, et je ne pense pas non plus que c’était forcément un mauvais choix, mais des problèmes apparaissent dans la façon de gérer les changements de modèle de données.
      J’ai l’impression que la plupart des modes de stockage des données n’ont pas suivi la façon dont les logiciels sont construits aujourd’hui, et que des choses comme les événements et les files résultent de l’ajout des fonctionnalités nécessaires par-dessus les systèmes existants.
      Aujourd’hui, beaucoup de relations entre données se font entre plusieurs services, c’est-à-dire hors de la base de données. C’est ainsi que se présente l’environnement IT moderne de nombreuses organisations.
      Il existe des données maîtres internes qui soutiennent plusieurs équipes métier et interagissent avec plus de 300 systèmes et applications IT pour simplifier les opérations.
      Avec les microservices, il est plus facile de garder la logique métier et le modèle de données propres, mais en contrepartie il faut gérer les événements, les files, l’état des données et les stockages dépendants, et aujourd’hui c’est devenu beaucoup trop complexe.
      J’aime SQL, mais honnêtement, les systèmes que nous construisons aujourd’hui pourraient presque tous tenir dans SQLite.
  • Ce qui manque dans ce genre de discussion, c’est de savoir quand une architecture pilotée par les événements est appropriée.
    En bref, si le client a fait quelque chose et attend une réponse, ce n’est pas de l’événementiel, c’est simplement du requête/réponse.
    L’approche événementielle s’applique lorsque quelque chose se produit hors bande. Par exemple, quand un push de code sur GitHub déclenche un build.
    Dans cet exemple, rafraîchir la page pour voir le code mis à jour relève du requête/réponse, mais le build CI placé dans une file est piloté par les événements.
    J’espère que cela aidera.

    • Ce n’est pas aussi simple. Le fait que ce soit ou non du requête-réponse n’est pas le critère pour choisir l’event sourcing ou une architecture pilotée par les événements.
      Même avec l’event sourcing ou l’événementiel, on peut construire des flux requête-réponse, inline, bloquants et circulaires.
      À l’inverse, on peut très bien faire de l’asynchrone sans event sourcing ni architecture événementielle, avec des workers, des files, des acteurs, du multithreading, etc.
  • Modéliser des événements de domaine est utile pour expliquer le problème à résoudre avec les experts du domaine, et il peut être judicieux de les conserver dans la documentation lors de la planification de la solution.
    Si l’on veut réellement implémenter un système fournissant une piste d’audit pour une machine à états de longue durée, il vaut probablement mieux utiliser des outils comme Temporal.io ou durable functions.
    Ces outils utilisent l’event sourcing pour leur persistance interne et fournissent un modèle de programmation qui ajoute des contraintes différentes au code qui orchestre les fonctionnalités (workflows) et au code qui interagit avec le monde réel (activities), ce qui force à réfléchir à la déduplication et à l’idempotence.

    • Durable functions a un problème de manque d’observabilité.
      J’aimerais entendre des suggestions sur la façon de dépasser ce problème.
  • Le concept est intéressant, mais l’article n’explique pas bien son fonctionnement
    Je me demande comment on reconstruit efficacement l’état actuel à partir d’un flux d’événements, et comment on modélise ce flux d’événements dans une base de données

    • L’auteur a plusieurs présentations
      https://www.youtube.com/watch?v=gG6DGmYKk4I
      https://www.youtube.com/watch?v=jnDchr5eabI
      https://www.youtube.com/watch?v=ArcypYS5XBQ
      https://www.youtube.com/watch?v=uODSwR2CIV4
      Il maintient aussi des exemples sur GitHub
      https://github.com/oskardudycz/EventSourcing.NetCore
      https://github.com/oskardudycz/EventSourcing.NodeJS
      https://github.com/oskardudycz/EventSourcing.JVM
    • Il y a deux méthodes
      La première consiste à utiliser une base de données conçue pour cet usage. On peut citer Google BigQuery, Amazon Redshift, ClickHouse, etc.
      Toutes les données actuelles sont, par nature, une forme d’agrégation. Autrement dit, c’est comme une requête group-by sur une base d’événements
      S’il y a des événements, on peut techniquement recréer l’état actuel ou un état passé avec une requête d’agrégation, donc ça se tient
      La seconde consiste à renommer le stockage relationnel en couche de cache à côté du système d’événements
      Fonctionnellement, c’est la même chose, mais cela n’allume pas les voyants d’alerte chez ceux qui tiennent absolument à ce que tout soit piloté par les événements
      L’architecture décrite dans l’article existe réellement. Elle est simplement extrêmement complexe, si bien que les services qui l’exploitent font généralement des choses très ciblées. Pensez à Google Analytics, Datadog, Splunk, etc.
    • Il n’existe pas un seul « état actuel ». Cette façon de penser vient du fait de tout centraliser dans une seule DB
      On peut créer différents états dans différents systèmes selon les besoins
      Si l’on construit un système d’e-commerce, avec des achats et des clients, un service peut lire les événements et créer des tables relationnelles à des fins financières
      Un autre service peut lire les événements et créer un stockage clé-valeur des données client, et un troisième peut alimenter un service OpenSearch pour la recherche de produits
      Un flux d’événements est une liste. Si l’on utilise quelque chose d’adapté comme Kafka, cela devient plusieurs listes, c’est-à-dire des topics, partitions, etc.
    • Cela a davantage de sens pour des flux spécifiques où les changements sont nombreux et où les données sont assez intéressantes pour voir ce qui s’est passé en cours de route
      Mais même cela peut se résoudre dans un modèle relationnel
  • C’est la différence entre top-down et bottom-up, ou entre sur mesure et généraliste
    Le top-down consiste à partir du domaine métier, puis à mapper l’implémentation sur les technologies, outils et fournisseurs disponibles
    Le bottom-up consiste à partir des technologies, outils et fournisseurs disponibles, puis à les assembler pour produire une solution qui fonctionne
    Le sur mesure inclut DDD, CQRS/ES, Sagas, TBUI (UI basée/pilotée par les tâches), GraphQL, les types de données algébriques, etc.
    Le généraliste inclut les RDBMS, CRUD, REST, les transactions ACID, CDC, les UI d’administration génériques, le no-code/low-code, les types limités/génériques, etc.

  • Je vais simplement continuer à utiliser de bonnes vieilles données relationnelles

    • Très bien, faites comme ça jusqu’à ce que ça ne marche plus. L’idée, c’est de ne pas utiliser un marteau sur une vis
  • Je suis d’accord avec les architectures événementielles, mais cet article semble avoir du mal à faire passer son message
    Je me concentrerais sur la différence entre les relations de données et les actions métier
    Dès qu’on commence à raisonner en termes d’actions et d’activités métier, le mouvement qui nous éloigne des stockages relationnels opérationnels devient beaucoup plus clair

    • À un niveau abstrait, les événements peuvent eux aussi être modélisés comme des relations
  • L’event sourcing a beaucoup de bonnes propriétés, donc c’est intéressant
    Mais n’a-t-on pas toujours besoin de relations ? Si oui, comment les implémente-t-on ?
    Si la réponse est « tout est implicitement dans le code de la couche applicative », j’ai du mal à l’accepter
    Il faut tout de même pouvoir interroger les relations, maintenir à jour une vue relationnelle, ou quelque chose de similaire
    Ce n’est pas grave si les relations ne sont pas au cœur du modèle de persistance, mais elles doivent être implémentées quelque part dans la couche de données, et je ne vois pas cela mentionné ici
    Firestore a le même problème. Tout le monde gère les relations d’une manière ou d’une autre, mais cela finit en code applicatif spaghetti qui ne passe pas à l’échelle

    • Dans un système d’event sourcing, on projette le flux d’événements dans des modèles de lecture, qui peuvent être multiples : relationnels, séries temporelles, etc.
      Si vous êtes familier avec la programmation fonctionnelle, c’est fondamentalement équivalent à une opération fold qui replie le flux d’événements en un état
      Pour avoir travaillé par le passé sur des systèmes d’event sourcing, l’existence d’un historique d’événements explicitement stocké est un avantage, mais cela ajoute aussi beaucoup de complexité
      Des questions se posent : comment générer concrètement les modèles de lecture, comment gérer les versions des modèles, faut-il conserver des snapshots des modèles de lecture, etc.
      D’après mon expérience, dans la plupart des contextes où ce pattern a été appliqué, la complexité supplémentaire n’en valait pas la peine
  • Ce qu’il faut, c’est une file de commandes. Les événements de commande ne sont pas des événements de domaine