Origem, repetição e versão de eventos

8

Estou projetando um sistema que usa Event Sourcing, CQRS e microsserviços. Sou levado a entender que esse não é um padrão incomum. Um recurso essencial do serviço precisa ser a capacidade de reidratar / restaurar de um sistema de registro. Os microsserviços produzirão comandos e consultas em um MQ (Kafka). Outros microsserviços responderão (eventos). Comandos e consultas serão mantidos no S3 para fins de auditoria e restauração.

O processo atual de pensamento era que, para fins de restauração do sistema, poderíamos extrair o log de eventos do S3 e simplesmente enviá-lo ao Kafka.

No entanto, isso não reconhece mudanças nos produtores e consumidores ao longo do tempo. O controle de versão no nível de comando / consulta parece ajudar bastante a solucionar o problema, mas não consigo entender os consumidores de controle de versão, de modo que eu possa impor que, quando um comando, durante uma restauração, é recebido e processado, é exatamente o mesmo [versão do] código que está executando o processamento, pois foi a primeira vez que o comando foi recebido.

Existem padrões que posso usar para resolver isso? Alguém conhece outros sistemas que anunciam esse recurso?

EDIT: Adicionando um exemplo.

Um 'comprador' envia uma 'pergunta' a um 'vendedor' no meu site de leilões. O fluxo tem a seguinte aparência: UI -> Web App: POST /question {:text text :to seller-id :from user-id} Web App -> MQ: SEND {:command send-question :args [text seller-id user-id]} MQ -< Audit: <command + args appended to log in S3> MQ -< Questions service: - Record question in DB - Email seller 'You have a question'

Agora, como resultado de um novo requisito comercial, ajusto o consumidor do 'Serviço de perguntas' para persistir uma contagem de todas as perguntas não lidas. O esquema do banco de dados foi alterado. Até o momento, não tínhamos noção se uma pergunta foi lida ou não pelo vendedor. A última linha se torna:

MQ -< Questions service: - Record question in DB - Email seller 'You have a question' - Increment 'unread questions count'

Dois comandos são problemas, um antes da alteração, um após a alteração. A 'contagem de perguntas não lidas' é igual a 1.

O sistema trava. Restauramos repetindo os comandos através do novo código. No final da restauração, nossas 'perguntas não lidas contam' são iguais a 2. Mesmo que, neste exemplo artificial, o resultado não seja uma catástrofe, o estado que foi restaurado não é o que era anteriormente.

Antony Woods
fonte
A questão parece misturar algumas preocupações. A fonte de eventos é uma estratégia de arquitetura para lidar com sistemas em que muitas alterações nos dados subjacentes estão ocorrendo. O controle de versão de DTOs, backups e dados é outra questão. A fonte de eventos não foi projetada especificamente para oferecer a você a capacidade de restauração bare-metal - para isso, é necessário ter uma estratégia específica.
theMayer 15/02
Talvez o upcasting de eventos possa resolver seu problema: blog.trifork.com/2012/04/17/…
Songo
@ rmayer06 Acho que um exemplo é o que estou procurando!
Antony Woods

Respostas:

16

Primeiro, é importante entender e poder aproveitar a diferença entre comandos e eventos.

Como esta pergunta aponta sucintamente, Comandos são coisas que gostaríamos que acontecesse e Eventos são coisas que já aconteceram. Um comando não resulta necessariamente em um evento significativo no sistema, mas geralmente ocorre. Por exemplo, um send messagecomando pode ser rejeitado; nesse caso, nenhum evento acontece (normalmente, um erro não seria considerado um evento nesse sentido, embora ainda possamos optar por registrá-lo em um log de diagnóstico). Agora, se o send messagecomando for aceito, o message sentevento ocorrerá e os detalhes do evento poderão descrever o remetente, o destinatário e o conteúdo.

Quando falamos sobre o estado do sistema, na verdade estamos discutindo não um ponto culminante de comandos, mas de eventos. Somente eventos podem causar uma mudança de estado no sistema. Para tirar um exemplo da vida, suponha que eu vá ao supermercado Publix local e compre um bilhete de loteria da Flórida. O comando foi "Comprar ticket" e o evento foi "Ticket emitido". Meu próximo comando, então, é na loteria desenhar meus números para o PowerBall. A loteria ignorará meu comando (mas não tenho conhecimento), e o evento "Números de PowerBall escolhidos" ocorrerá independentemente dos meus desejos. Se meus números coincidirem, o evento "Jackpot ganho" acontece comigo (e acho que meu comando foi ouvido). Caso contrário, percebo que meu comando foi ignorado.

De uma perspectiva histórica, a loteria está interessada apenas em um subconjunto de eventos. A loteria apenas se preocupa com a (a) emissão de um bilhete, (b) os números foram escolhidos e (c) o jackpot foi ganho. Esses são os itens de interesse. O ato de comprar o ingresso, querer ganhar etc. é irrelevante, como é o que faço com o ingresso depois que perco. Enquanto o mundo real muda para eventos mundanos, precisamos apenas registrar os eventos que são significativos para o nosso sistema.

Em teoria, sob uma técnica de fornecimento de eventos, um fluxo de eventos pode ser repetido desde o início dos tempos para chegar ao estado atual. Isso se baseia na suposição de que as condições subjacentes do sistema são constantes e determinísticas. No entanto, essas suposições não são válidas em muitos sistemas. Os dados associados a um evento, bem como os tipos de eventos nos quais estamos interessados, podem mudar à medida que nosso software de computador evolui. Além disso, pode ser dispendioso computacionalmente recalcular o estado atual em resposta a todas as consultas. Por esse motivo, as capturas instantâneas do estado do sistema geralmente são feitas para representar pontos conhecidos no tempo, aos quais os eventos mais recentes podem ser adicionados.

Embora ainda seja possível reproduzir um fluxo de eventos em várias versões, a quantidade de esforço humano envolvido para fazer isso provavelmente será proibitiva de custos. A menos que haja uma razão justificável para projetar essa capacidade no sistema, é melhor criar seu sistema para utilizar snapshots.

Exemplo em questão

No exemplo dado na pergunta, a arquitetura não é verdadeiramente baseada em eventos; é baseado em comandos. A reprodução de comandos cria o estado do sistema. Este é um anti-padrão e deve ser corrigido. Em vez disso, os eventos principais são:

  • O comprador faz a pergunta
  • O vendedor responde à pergunta

Cada um desses eventos pode ser "repetido" para fornecer o estado atual. Por exemplo, no ato de fazer uma pergunta, o comportamento do sistema pode ser enviar um email ao vendedor e aumentar o unanswered questioncontador. Esse comportamento pode ser alterado; no entanto, o fato de a pergunta ter sido feita não. Da mesma forma, o sistema pode diminuir o unanswered questioncontador quando o vendedor responder. Esse comportamento é mutável, mas o fato de o vendedor responder não é.

A maioria dos sistemas de fornecimento de eventos computa dinamicamente a contagem de perguntas não respondidas, reproduzindo o fluxo de eventos específico em resposta a uma consulta.

theMayer
fonte
Esta é uma grande resposta, graças @ rmayer06
Antony madeiras
Neste exemplo de loteria, você diz que "Somente eventos podem causar uma mudança de estado no sistema", mas se o evento for "emitido por ticket" (e presumivelmente o evento incluirá alguns detalhes como carimbo de data / hora, comprador_id, ticket_id), como você registre o número de referência do ticket se não houver outro sistema de registro que produza os IDs? Existe um sistema CRUD tradicional que precisa primeiro produzir um ticket antes que a fonte do evento possa registrar o fato como pretérito?
Homan
A ação de emitir o ticket é o evento neste caso. Os dados associados ao evento são os descritos na sua pergunta, o que é útil, mas tecnicamente incorreto. Além disso, os eventos normalmente representam uma holarquia de detalhes, onde podem ser compostos e decompostos de forma relativamente ilimitada em cada direção.
theMayer
Uh ... você explodiu minha mente com a coisa holarquia.
Homan
Eu acho que o que eu estava pensando era o seguinte: no mundo CRUD, especialmente no Rails, é comum ter IDs de incremento automático para as chaves primárias das tabelas. Criamos registros sem conhecer os IDs, o banco de dados me devolve o ID do ticket. Agora, migrando para o mundo de Event Sourcing, pelo que li, o evento é 'realizado' antes de persistir no DB e requer um ID agregado. Portanto, ao invés de recuperar o ID após a persistência do DB, parece que o ID exclusivo já deve ser conhecido para que possa ser descrito como um todo. Parece que devemos sempre criar uuid e não auto-ids.
Homan 21/11
3

Comandos e consultas serão mantidos no S3 para fins de auditoria e restauração.

Para auditoria, com certeza. Para restaurar ? Isso é estranho e pode causar dores de cabeça.

Se você deseja obter fontes de eventos, deseja reidratar o estado de eventos (coisas que aconteceram no passado) e não de comandos. Isso evita a maioria dos problemas associados às alterações na implementação de comandos - você só precisa lidar com as alterações de estado persistentes.

O controle de versão ainda é uma preocupação. Em particular, você deseja garantir que seus eventos persistentes sejam o mais flexível possível (representações de DTOs, em vez de serializações diretas dos conceitos em seu domínio). Ao ler eventos da loja, você tem a oportunidade de atualizá-los conforme necessário antes de aplicá-los ao estado de reidratação.

VoiceOfUnreason
fonte
Ok, acho que seu conselho é se preocupar menos com a restauração de comandos e mais com os eventos? Por exemplo, se eu receber um comando ao longo das linhas de "adicionar 10 beans", devo emitir e armazenar posteriormente um evento que diga "10 beans foram adicionados. Novo total: 40"?
Antony Woods
Sim está certo. Cada mudança de estado na entidade de origem do evento é representada por um ou mais eventos; para reidratar, você reproduz todos esses eventos em ordem.
VoiceOfUnreason
2
Não aceitei esta resposta, mas quero lhe agradecer pela contribuição, pois foi vital para alterar meu entendimento. Escolhi a resposta do rmayer06 apenas porque era mais direta, arredondada e mais útil para alguém que estava acessando esta pergunta para obter uma resposta rápida.
Antony Woods