Si j'utilisais un SGBDR (par exemple, SQL Server) pour stocker des données de source d'événements, à quoi le schéma pourrait-il ressembler?
J'ai vu quelques variations évoquées dans un sens abstrait, mais rien de concret.
Par exemple, disons que l'on a une entité «Produit» et que les modifications apportées à ce produit peuvent prendre la forme de: Prix, Coût et Description. Je ne sais pas si je:
- Avoir une table "ProductEvent", qui contient tous les champs pour un produit, où chaque changement signifie un nouvel enregistrement dans cette table, plus "qui, quoi, où, pourquoi, quand et comment" (WWWWWH) le cas échéant. Lorsque le coût, le prix ou la description sont modifiés, une toute nouvelle ligne est ajoutée pour représenter le produit.
- Stockez le coût, le prix et la description du produit dans des tables séparées jointes à la table Product avec une relation de clé étrangère. Lorsque des modifications de ces propriétés se produisent, écrivez de nouvelles lignes avec WWWWWH, le cas échéant.
- Stocker WWWWWH, plus un objet sérialisé représentant l'événement, dans une table "ProductEvent", ce qui signifie que l'événement lui-même doit être chargé, désérialisé et relu dans mon code d'application afin de recréer l'état de l'application pour un produit donné .
En particulier, je m'inquiète de l'option 2 ci-dessus. Poussée à l'extrême, la table de produits serait presque une table par propriété, où charger l'état d'application pour un produit donné nécessiterait de charger tous les événements pour ce produit à partir de chaque table d'événements de produit. Cette explosion de table me fait mal.
Je suis sûr que «cela dépend», et bien qu'il n'y ait pas de «bonne réponse» unique, j'essaie d'avoir une idée de ce qui est acceptable et de ce qui ne l'est pas du tout. Je suis également conscient que NoSQL peut aider ici, où les événements peuvent être stockés sur une racine agrégée, ce qui signifie qu'une seule demande à la base de données pour obtenir les événements à partir de laquelle reconstruire l'objet, mais nous n'utilisons pas de base de données NoSQL à la moment donc je cherche des alternatives.
la source
Réponses:
Le magasin d'événements ne devrait pas avoir besoin de connaître les champs ou les propriétés spécifiques des événements. Sinon, toute modification de votre modèle entraînerait la migration de votre base de données (tout comme dans le cas d'une bonne persistance basée sur l'état à l'ancienne). Par conséquent, je ne recommanderais pas du tout les options 1 et 2.
Voici le schéma utilisé dans Ncqrs . Comme vous pouvez le voir, la table "Evénements" stocke les données associées sous forme de CLOB (ie JSON ou XML). Cela correspond à votre option 3 (Seulement qu'il n'y a pas de table "ProductEvents" car vous n'avez besoin que d'une table "Events" générique. Dans Ncqrs, le mappage vers vos racines agrégées se fait via la table "EventSources", où chaque EventSource correspond à un réel Racine agrégée.)
Le mécanisme de persistance SQL de l'implémentation Event Store de Jonathan Oliver consiste essentiellement en une table appelée «Commits» avec un champ BLOB «Payload». C'est à peu près la même chose que dans Ncqrs, mais il sérialise les propriétés de l'événement au format binaire (ce qui, par exemple, ajoute la prise en charge du chiffrement).
Greg Young recommande une approche similaire, comme largement documentée sur le site Web de Greg .
Le schéma de sa table prototypique "Événements" se lit comme suit:
la source
Le projet GitHub CQRS.NET a quelques exemples concrets de la façon dont vous pourriez faire des EventStores dans quelques technologies différentes. Au moment de la rédaction de cet article, il existe une implémentation en SQL utilisant Linq2SQL et un schéma SQL qui va avec, il y en a un pour MongoDB , un pour DocumentDB (CosmosDB si vous êtes dans Azure) et un utilisant EventStore (comme mentionné ci-dessus). Il y a plus dans Azure comme le stockage de table et le stockage Blob qui est très similaire au stockage de fichiers plats.
Je suppose que le point principal ici est qu'ils sont tous conformes au même principal / contrat. Ils stockent tous les informations dans un seul endroit / conteneur / table, ils utilisent des métadonnées pour identifier un événement à partir d'un autre et `` juste '' stocker l'événement entier tel qu'il était - dans certains cas sérialisé, dans des technologies de support, pour ainsi dire. Donc, selon que vous choisissez une base de données de documents, une base de données relationnelle ou même un fichier plat, il existe plusieurs façons d'atteindre la même intention d'un magasin d'événements (c'est utile si vous changez d'avis à tout moment et que vous avez besoin de migrer ou de prendre en charge plusieurs technologies de stockage).
En tant que développeur du projet, je peux partager quelques idées sur certains des choix que nous avons faits.
Premièrement, nous avons trouvé (même avec des UUID / GUID uniques au lieu d'entiers) pour de nombreuses raisons, les ID séquentiels se produisent pour des raisons stratégiques, ainsi le simple fait d'avoir un ID n'était pas assez unique pour une clé, nous avons donc fusionné notre colonne de clé ID principale avec les données / type d'objet pour créer ce qui devrait être une clé vraiment unique (au sens de votre application). Je sais que certaines personnes disent que vous n'avez pas besoin de le stocker, mais cela dépendra du fait que vous soyez un nouveau site ou que vous deviez coexister avec des systèmes existants.
Nous nous sommes contentés d'un seul conteneur / table / collection pour des raisons de maintenabilité, mais nous avons joué avec une table séparée par entité / objet. Nous avons trouvé dans la pratique que cela signifiait que l'application avait besoin d'autorisations "CREATE" (ce qui n'est généralement pas une bonne idée ... en général, il y a toujours des exceptions / exclusions) ou à chaque fois qu'une nouvelle entité / objet est apparue ou a été déployée, une nouvelle des conteneurs / tables / collections de stockage doivent être réalisés. Nous avons constaté que cela était extrêmement lent pour le développement local et problématique pour les déploiements de production. Vous ne pouvez pas, mais c'était notre expérience du monde réel.
Une autre chose à retenir est que demander à l'action X de se produire peut entraîner de nombreux événements différents, connaissant ainsi tous les événements générés par une commande / un événement / ce qui est utile. Ils peuvent également concerner différents types d'objets. Par exemple, pousser «acheter» dans un panier peut déclencher des événements de compte et d'entreposage. Une application consommatrice peut vouloir savoir tout cela, nous avons donc ajouté un CorrelationId. Cela signifiait qu'un consommateur pouvait demander tous les événements soulevés à la suite de sa demande. Vous verrez cela dans le schéma .
Plus précisément avec SQL, nous avons constaté que les performances devenaient vraiment un goulot d'étranglement si les index et les partitions n'étaient pas correctement utilisés. N'oubliez pas que les événements doivent être diffusés dans l'ordre inverse si vous utilisez des instantanés. Nous avons essayé quelques index différents et avons constaté qu'en pratique, certains index supplémentaires étaient nécessaires pour déboguer les applications du monde réel en production. Encore une fois, vous verrez cela dans le schéma .
D'autres métadonnées en production ont été utiles lors des enquêtes basées sur la production, les horodatages nous ont donné un aperçu de l'ordre dans lequel les événements ont été persistés par rapport à leur déclenchement. Cela nous a donné une certaine assistance sur un système particulièrement axé sur les événements qui a soulevé de grandes quantités d'événements, nous donnant des informations sur les performances de choses comme les réseaux et la distribution des systèmes sur le réseau.
la source
Eh bien, vous voudrez peut-être jeter un coup d'œil à Datomic.
Datomic est une base de données de faits flexibles basés sur le temps , prenant en charge les requêtes et les jointures, avec une évolutivité élastique et des transactions ACID.
J'ai écrit une réponse détaillée ici
Vous pouvez regarder une conférence de Stuart Halloway expliquant la conception de Datomic ici
Étant donné que Datomic stocke les faits dans le temps, vous pouvez l'utiliser pour des cas d'utilisation de source d'événements, et bien plus encore.
la source
Je pense que la solution (1 & 2) peut devenir un problème très rapidement à mesure que votre modèle de domaine évolue. De nouveaux champs sont créés, certains changent de sens et certains peuvent ne plus être utilisés. Finalement, votre table aura des dizaines de champs Nullable, et le chargement des événements sera désordonné.
Souvenez-vous également que le magasin d'événements ne doit être utilisé que pour les écritures, vous ne l'interrogez que pour charger les événements, pas les propriétés de l'agrégat. Ce sont des choses séparées (c'est l'essence même du CQRS).
Solution 3 ce que les gens font habituellement, il existe de nombreuses façons d'y parvenir.
Par exemple, EventFlow CQRS lorsqu'il est utilisé avec SQL Server crée une table avec ce schéma:
où:
Cependant, si vous créez à partir de zéro, je vous recommande de suivre le principe YAGNI et de créer avec les champs minimaux requis pour votre cas d'utilisation.
la source
Un indice possible est la conception suivie de "Dimension à changement lent" (type = 2) devrait vous aider à couvrir:
La fonction de pliage à gauche devrait également être correcte à implémenter, mais vous devez penser à la complexité future des requêtes.
la source
Je pense que ce serait une réponse tardive, mais je tiens à souligner que l'utilisation du SGBDR comme stockage de source d'événements est tout à fait possible si votre exigence de débit n'est pas élevée. Je voudrais simplement vous montrer des exemples d'un grand livre de recherche d'événements que je construis pour illustrer.
https://github.com/andrewkkchan/client-ledger-service Ce qui précède est un service Web de registre de recherche d'événements. https://github.com/andrewkkchan/client-ledger-core-db Et ce qui précède, j'utilise le SGBDR pour calculer les états afin que vous puissiez profiter de tous les avantages d'un SGBDR comme le support des transactions. https://github.com/andrewkkchan/client-ledger-core-memory Et j'ai un autre consommateur à traiter en mémoire pour gérer les rafales.
On dirait que le magasin d'événements ci-dessus vit toujours à Kafka - car le SGBDR est lent à insérer, en particulier lorsque l'insertion est toujours en cours.
J'espère que le code vous aidera à vous donner une illustration en dehors des très bonnes réponses théoriques déjà fournies pour cette question.
la source