Se eu estivesse usando um RDBMS (por exemplo, SQL Server) para armazenar dados de origem de eventos, como seria o esquema?
Eu vi algumas variações faladas em um sentido abstrato, mas nada de concreto.
Por exemplo, digamos que alguém tenha uma entidade "Produto" e as alterações nesse produto possam vir na forma de: Preço, Custo e Descrição. Estou confuso sobre se eu:
- Tenha uma tabela "ProductEvent", que contém todos os campos de um produto, onde cada alteração significa um novo registro nessa tabela, mais "quem, o quê, onde, por que, quando e como" (WWWWWH) conforme apropriado. Quando o custo, preço ou descrição são alterados, uma nova linha inteira é adicionada para representar o Produto.
- Armazene Custo, Preço e Descrição do produto em tabelas separadas unidas à tabela Produto com um relacionamento de chave estrangeira. Quando ocorrerem alterações nessas propriedades, escreva novas linhas com WWWWWH conforme apropriado.
- Armazene WWWWWH, mais um objeto serializado que representa o evento, em uma tabela "ProductEvent", o que significa que o próprio evento deve ser carregado, desserializado e reproduzido no código do meu aplicativo para reconstruir o estado do aplicativo para um determinado produto .
Particularmente, me preocupo com a opção 2 acima. Levada ao extremo, a tabela de produtos seria quase uma tabela por propriedade, onde carregar o Estado do aplicativo para um determinado produto exigiria o carregamento de todos os eventos desse produto de cada tabela de eventos do produto. Esta explosão de mesa me cheira mal.
Tenho certeza de que "depende" e, embora não haja uma única "resposta correta", estou tentando sentir o que é aceitável e o que é totalmente não aceitável. Também estou ciente de que o NoSQL pode ajudar aqui, onde os eventos podem ser armazenados em uma raiz agregada, o que significa apenas uma única solicitação ao banco de dados para obter os eventos para reconstruir o objeto, mas não estamos usando um banco de dados NoSQL no momento, então estou procurando alternativas.
fonte
Respostas:
O armazenamento de eventos não precisa saber sobre os campos ou propriedades específicos dos eventos. Caso contrário, toda modificação de seu modelo resultaria na migração de seu banco de dados (como na velha persistência baseada em estado). Portanto, eu não recomendaria as opções 1 e 2.
Abaixo está o esquema usado no Ncqrs . Como você pode ver, a tabela "Eventos" armazena os dados relacionados como um CLOB (ou seja, JSON ou XML). Isso corresponde à sua opção 3 (apenas que não existe uma tabela "ProductEvents" porque você só precisa de uma tabela genérica "Eventos". Em Ncqrs o mapeamento para suas raízes agregadas ocorre através da tabela "EventSources", onde cada EventSource corresponde a um Raiz agregada.)
O mecanismo de persistência SQL da implementação do Event Store de Jonathan Oliver consiste basicamente em uma tabela chamada "Commits" com um campo BLOB "Payload". É praticamente o mesmo que no Ncqrs, apenas que serializa as propriedades do evento em formato binário (que, por exemplo, adiciona suporte à criptografia).
Greg Young recomenda uma abordagem semelhante, amplamente documentada no site de Greg .
O esquema de sua tabela prototípica de "Eventos" é:
fonte
O projeto GitHub CQRS.NET tem alguns exemplos concretos de como você pode fazer EventStores em algumas tecnologias diferentes. No momento da escrita, há uma implementação em SQL usando Linq2SQL e um esquema SQL para acompanhá-lo, há um para MongoDB , um para DocumentDB (CosmosDB se você estiver no Azure) e um usando EventStore (como mencionado acima). Há mais no Azure, como armazenamento de tabela e armazenamento de Blob, que é muito semelhante ao armazenamento de arquivo simples.
Acho que o ponto principal aqui é que todos estão em conformidade com o mesmo princípio / contrato. Todos eles armazenam informações em um único local / contêiner / mesa, usam metadados para identificar um evento de outro e 'apenas' armazenam todo o evento como ele era - em alguns casos serializado, em tecnologias de suporte, como era. Portanto, dependendo se você escolher um banco de dados de documentos, um banco de dados relacional ou até mesmo um arquivo simples, há várias maneiras diferentes de todos alcançarem a mesma intenção de um armazenamento de eventos (é útil se você mudar de ideia a qualquer momento e descobrir que precisa migrar ou dar suporte mais de uma tecnologia de armazenamento).
Como desenvolvedor do projeto, posso compartilhar alguns insights sobre algumas das escolhas que fizemos.
Em primeiro lugar, descobrimos (mesmo com UUIDs / GUIDs únicos em vez de inteiros) por muitos motivos que os IDs sequenciais ocorrem por razões estratégicas, portanto, apenas ter um ID não era exclusivo o suficiente para uma chave, então mesclamos nossa coluna de chave de ID principal com os dados / tipo de objeto para criar o que deve ser uma chave verdadeiramente única (no sentido de seu aplicativo). Eu sei que algumas pessoas dizem que você não precisa armazená-lo, mas isso vai depender se você é um novato ou precisa coexistir com os sistemas existentes.
Ficamos com um único contêiner / tabela / coleção por motivos de manutenção, mas brincamos com uma tabela separada por entidade / objeto. Descobrimos na prática que isso significava que o aplicativo precisava de permissões "CRIAR" (o que geralmente não é uma boa ideia ... geralmente, sempre há exceções / exclusões) ou cada vez que uma nova entidade / objeto surgiu ou foi implantado, novo recipientes / tabelas / coleções de armazenamento precisavam ser feitos. Descobrimos que isso era dolorosamente lento para o desenvolvimento local e problemático para implantações de produção. Você pode não, mas essa foi a nossa experiência no mundo real.
Outra coisa a lembrar é que pedir que a ação X aconteça pode resultar em muitos eventos diferentes ocorrendo, portanto, conhecendo todos os eventos gerados por um comando / evento / o que quer que seja útil. Eles também podem estar em diferentes tipos de objetos, por exemplo, empurrar "comprar" em um carrinho de compras pode acionar eventos de conta e armazenamento. Um aplicativo de consumo pode querer saber tudo isso, então adicionamos um CorrelationId. Isso significava que um consumidor poderia solicitar todos os eventos gerados como resultado de sua solicitação. Você verá isso no esquema .
Especificamente com o SQL, descobrimos que o desempenho realmente se tornava um gargalo se os índices e partições não fossem usados adequadamente. Lembre-se de que os eventos precisarão ser transmitidos em ordem reversa se você estiver usando instantâneos. Tentamos alguns índices diferentes e descobrimos que, na prática, alguns índices adicionais eram necessários para depurar aplicativos do mundo real em produção. Novamente, você verá isso no esquema .
Outros metadados em produção foram úteis durante as investigações baseadas na produção, carimbos de data / hora nos deram uma visão sobre a ordem em que os eventos foram persistidos ou gerados. Isso nos deu alguma assistência em um sistema especialmente orientado a eventos que gerou uma grande quantidade de eventos, nos fornecendo informações sobre o desempenho de coisas como redes e a distribuição de sistemas pela rede.
fonte
Bem, você pode querer dar uma olhada no Datomic.
O Datomic é um banco de dados de fatos flexíveis e baseados em tempo , com suporte a consultas e junções, com escalabilidade elástica e transações ACID.
Eu escrevi uma resposta detalhada aqui
Você pode assistir a uma palestra de Stuart Halloway explicando o design do Datomic aqui
Como o Datomic armazena fatos a tempo, você pode usá-lo para casos de uso de sourcing de eventos e muito mais.
fonte
Acho que a solução (1 e 2) pode se tornar um problema muito rapidamente à medida que seu modelo de domínio evolui. Novos campos são criados, alguns mudam de significado e outros podem não ser mais usados. Eventualmente, sua tabela terá dezenas de campos anuláveis, e carregar os eventos será uma bagunça.
Além disso, lembre-se de que o armazenamento de eventos deve ser usado apenas para gravações, você apenas o consulta para carregar os eventos, não as propriedades do agregado. Eles são coisas separadas (essa é a essência do CQRS).
Solução 3 - o que as pessoas geralmente fazem, há muitas maneiras de conseguir isso.
Como exemplo, EventFlow CQRS quando usado com SQL Server cria uma tabela com este esquema:
Onde:
No entanto, se você estiver criando do zero, recomendo seguir o princípio YAGNI e criar com o mínimo de campos necessários para o seu caso de uso.
fonte
A possível dica é o design seguido por "Dimensão que muda lentamente" (tipo = 2) deve ajudá-lo a cobrir:
A função de dobra à esquerda também deve ser implementada, mas você precisa pensar na complexidade da consulta futura.
fonte
Acho que essa seria uma resposta tardia, mas gostaria de salientar que usar RDBMS como armazenamento de origem de eventos é totalmente possível se o seu requisito de taxa de transferência não for alto. Gostaria apenas de mostrar exemplos de um livro razão de sourcing de eventos que construí para ilustrar.
https://github.com/andrewkkchan/client-ledger-service O texto acima é um serviço da web do razão de sourcing de eventos. https://github.com/andrewkkchan/client-ledger-core-db E acima, eu uso RDBMS para computar estados para que você possa aproveitar todas as vantagens de um RDBMS como o suporte a transações. https://github.com/andrewkkchan/client-ledger-core-memory E tenho outro consumidor para processar na memória para lidar com bursts.
Alguém poderia argumentar que o armazenamento de eventos real acima ainda vive em Kafka - como RDBMS é lento para inserir, especialmente quando a inserção está sempre anexando.
Espero que o código ajude a fornecer uma ilustração além das ótimas respostas teóricas já fornecidas para esta pergunta.
fonte