Si estuviera usando un RDBMS (por ejemplo, SQL Server) para almacenar datos de origen de eventos, ¿cómo sería el esquema?
He visto algunas variaciones de las que se habla en un sentido abstracto, pero nada concreto.
Por ejemplo, digamos que uno tiene una entidad "Producto", y los cambios en ese producto podrían venir en forma de: Precio, Costo y Descripción. Estoy confundido acerca de si:
- Tener una tabla "ProductEvent", que tenga todos los campos de un producto, donde cada cambio significa un nuevo registro en esa tabla, más "quién, qué, dónde, por qué, cuándo y cómo" (WWWWWH) según corresponda. Cuando se cambia el costo, el precio o la descripción, se agrega una fila completamente nueva para representar el Producto.
- Almacene el costo, el precio y la descripción del producto en tablas separadas unidas a la tabla Producto con una relación de clave externa. Cuando se produzcan cambios en esas propiedades, escriba nuevas filas con WWWWWH según corresponda.
- Almacenar WWWWWH, más un objeto serializado que representa el evento, en una tabla "ProductEvent", lo que significa que el evento en sí debe cargarse, eliminarse de la serialización y reproducirse en el código de mi aplicación para reconstruir el estado de la aplicación para un Producto dado. .
Particularmente me preocupa la opción 2 anterior. Llevada al extremo, la tabla de productos sería casi una tabla por propiedad, donde cargar el Estado de la aplicación para un producto dado requeriría cargar todos los eventos para ese producto desde cada tabla de eventos de producto. Esta explosión de mesa me huele mal.
Estoy seguro de que "depende", y aunque no hay una única "respuesta correcta", estoy tratando de tener una idea de lo que es aceptable y lo que es totalmente inaceptable. También soy consciente de que NoSQL puede ayudar aquí, donde los eventos podrían almacenarse en una raíz agregada, lo que significa que solo una solicitud a la base de datos para obtener los eventos para reconstruir el objeto, pero no estamos usando una base de datos NoSQL en el momento, así que estoy buscando alternativas.
fuente
Respuestas:
La tienda de eventos no debería necesitar conocer los campos o propiedades específicos de los eventos. De lo contrario, cada modificación de su modelo resultaría en tener que migrar su base de datos (al igual que en la persistencia basada en el estado a la antigua). Por lo tanto, no recomendaría la opción 1 y 2 en absoluto.
A continuación se muestra el esquema que se utiliza en Ncqrs . Como puede ver, la tabla "Eventos" almacena los datos relacionados como un CLOB (es decir, JSON o XML). Esto corresponde a su opción 3 (Solo que no hay una tabla "ProductEvents" porque solo necesita una tabla "Eventos" genérica. En Ncqrs, la asignación a sus Raíces agregadas se realiza a través de la tabla "EventSources", donde cada EventSource corresponde a un Raíz agregada.)
El mecanismo de persistencia SQL de la implementación de Event Store de Jonathan Oliver consiste básicamente en una tabla llamada "Commits" con un campo BLOB "Payload". Esto es más o menos lo mismo que en Ncqrs, solo que serializa las propiedades del evento en formato binario (que, por ejemplo, agrega soporte de cifrado).
Greg Young recomienda un enfoque similar, ampliamente documentado en el sitio web de Greg .
El esquema de su tabla prototípica de "Eventos" dice:
fuente
El proyecto CQRS.NET de GitHub tiene algunos ejemplos concretos de cómo podría hacer EventStores en algunas tecnologías diferentes. En el momento de escribir este artículo, hay una implementación en SQL que usa Linq2SQL y un esquema SQL que lo acompaña, hay uno para MongoDB , uno para DocumentDB (CosmosDB si está en Azure) y otro que usa EventStore (como se mencionó anteriormente). Hay más en Azure como Table Storage y Blob Storage, que es muy similar al almacenamiento de archivos planos.
Supongo que el punto principal aquí es que todos se ajustan al mismo principio / contrato. Todos almacenan información en un solo lugar / contenedor / tabla, usan metadatos para identificar un evento de otro y 'simplemente' almacenan el evento completo tal como estaba, en algunos casos serializado, en tecnologías de soporte, como estaba. Entonces, dependiendo de si elige una base de datos de documentos, una base de datos relacional o incluso un archivo plano, hay varias formas diferentes de alcanzar la misma intención de una tienda de eventos (es útil si cambia de opinión en cualquier momento y descubre que necesita migrar o brindar soporte más de una tecnología de almacenamiento).
Como desarrollador del proyecto, puedo compartir algunas ideas sobre algunas de las decisiones que tomamos.
En primer lugar, encontramos (incluso con UUID / GUID únicos en lugar de enteros) por muchas razones, las ID secuenciales ocurren por razones estratégicas, por lo que tener una ID no era lo suficientemente única para una clave, por lo que fusionamos nuestra columna de clave de ID principal con los datos / tipo de objeto para crear lo que debería ser una clave verdaderamente única (en el sentido de su aplicación). Sé que algunas personas dicen que no es necesario almacenarlo, pero eso dependerá de si es totalmente nuevo o si tiene que coexistir con los sistemas existentes.
Nos quedamos con un solo contenedor / tabla / colección por razones de mantenimiento, pero jugamos con una tabla separada por entidad / objeto. En la práctica, descubrimos que eso significaba que la aplicación necesitaba permisos de "CREAR" (lo que en general no es una buena idea ... en general, siempre hay excepciones / exclusiones) o cada vez que una nueva entidad / objeto entra en existencia o se implementa, una nueva se necesitaban hacer contenedores / mesas / colecciones de almacenamiento. Descubrimos que esto era dolorosamente lento para el desarrollo local y problemático para las implementaciones de producción. Puede que no, pero esa fue nuestra experiencia en el mundo real.
Otra cosa para recordar es que pedir la acción X puede resultar en que ocurran muchos eventos diferentes, conociendo así todos los eventos generados por un comando / evento / lo que sea útil. También pueden estar en diferentes tipos de objetos, por ejemplo, presionar "comprar" en un carrito de compras puede activar eventos de cuenta y almacenamiento. Es posible que una aplicación consumidora desee saber todo esto, por lo que agregamos un CorrelationId. Esto significaba que un consumidor podía solicitar todos los eventos planteados como resultado de su solicitud. Verá eso en el esquema .
Específicamente con SQL, descubrimos que el rendimiento realmente se convertía en un cuello de botella si los índices y las particiones no se usaban adecuadamente. Recuerde que los eventos deberán transmitirse en orden inverso si está utilizando instantáneas. Probamos algunos índices diferentes y descubrimos que, en la práctica, se necesitaban algunos índices adicionales para depurar aplicaciones del mundo real en producción. Nuevamente lo verá en el esquema .
Otros metadatos en producción fueron útiles durante las investigaciones basadas en la producción, las marcas de tiempo nos dieron una idea del orden en que los eventos persistieron frente a los que se produjeron. Eso nos brindó algo de ayuda en un sistema particularmente impulsado por eventos que generó grandes cantidades de eventos, brindándonos información sobre el rendimiento de cosas como las redes y la distribución de los sistemas en la red.
fuente
Bueno, quizás quieras echarle un vistazo a Datomic.
Datomic es una base de datos flexible, basada en el tiempo , que admite consultas y uniones, con escalabilidad elástica y transacciones ACID.
Escribí una respuesta detallada aquí
Puedes ver una charla de Stuart Halloway explicando el diseño de Datomic aquí
Dado que Datomic almacena datos a tiempo, puede usarlo para casos de uso de abastecimiento de eventos y mucho más.
fuente
Creo que la solución (1 y 2) puede convertirse en un problema muy rápidamente a medida que evoluciona su modelo de dominio. Se crean nuevos campos, algunos cambian de significado y algunos pueden dejar de utilizarse. Eventualmente, su tabla tendrá docenas de campos que aceptan valores NULL y cargar los eventos será un desastre.
Además, recuerde que el almacén de eventos debe usarse solo para escrituras, solo lo consulta para cargar los eventos, no las propiedades del agregado. Son cosas separadas (esa es la esencia de CQRS).
Solución 3 lo que la gente suele hacer, hay muchas formas de lograrlo.
Como ejemplo, EventFlow CQRS cuando se usa con SQL Server crea una tabla con este esquema:
dónde:
Sin embargo, si está creando desde cero, le recomiendo seguir el principio YAGNI y crear con los campos mínimos requeridos para su caso de uso.
fuente
Una posible pista es el diseño seguido de "Dimensión que cambia lentamente" (tipo = 2) que debería ayudarlo a cubrir:
La función de plegado a la izquierda también debería estar bien para implementar, pero debe pensar en la complejidad de la consulta futura.
fuente
Creo que esta sería una respuesta tardía, pero me gustaría señalar que el uso de RDBMS como almacenamiento de origen de eventos es totalmente posible si su requisito de rendimiento no es alto. Solo les mostraría ejemplos de un libro mayor de fuentes de eventos que construí para ilustrar.
https://github.com/andrewkkchan/client-ledger-service El anterior es un servicio web de registro de fuentes de eventos. https://github.com/andrewkkchan/client-ledger-core-db Y lo anterior, uso RDBMS para calcular estados para que pueda disfrutar de todas las ventajas que ofrece un RDBMS como el soporte de transacciones. https://github.com/andrewkkchan/client-ledger-core-memory Y tengo otro consumidor para procesar en la memoria para manejar ráfagas.
Uno podría argumentar que el almacén de eventos real anterior todavía vive en Kafka, ya que RDBMS es lento para insertar, especialmente cuando la inserción siempre se agrega.
Espero que el código le ayude a dar una ilustración además de las muy buenas respuestas teóricas que ya se han proporcionado para esta pregunta.
fuente