DEV Community

Cover image for Event Sourcing
Carol Rocha Floro
Carol Rocha Floro

Posted on

Event Sourcing

O problema

Recentemente na empresa meu gestor mencionou um pattern que eu não conhecia, o Event Sourcing. A proposta era uma solução para um problema complexo que tínhamos no banco: controlar de forma eficiente e confiável o total transferido por clientes para um fundo de investimentos e quanto falta transferir baseado em um compromisso assinado por esses clientes, compromisso esse que eventualmente pode ser transferido para outros clientes.

Sabendo que poderíamos simplesmente fazer um CRUD e acessar essa informação quando necessário, por que optar por um design pattern diferente, que adiciona uma camada de complexidade?

Nossa intenção era tornar possível reconstruir o estado da aplicação em qualquer momento do tempo, não apenas obter o estado atual. Assim conseguiríamos montar uma "linha do tempo" de cada cliente em relação ao fundo de investimento do qual ele participa, e uma do fundo como um todo.

As vantagens do Event Sourcing

Ao utilizar o Event Sourcing, cria-se um sistema totalmente auditável e com a possibilidade de reconstruir o estado da aplicação em qualquer momento. Ao invés de salvar os dados como estão, são salvos os eventos que levaram ao estado, o que pode ser útil em sistemas que exigem rastreabilidade detalhada por questões legais, como bancos ou aplicações de saúde.

Esse pattern também facilita a testabilidade e o debug, pois ao reprocessar os eventos é possível reproduzir qualquer cenário ocorrido.

Com a publicação em uma fila ou exchange, outras aplicações podem consumir esses eventos também e processa-los conforme a própria necessidade.

Suponha que você tem uma conta corrente e quer saber o saldo dessa conta, mas também quer exibir o extrato dela. Ao utilizar o Event Sourcing você salva cada evento ocorrido - ou seja, cada alteração naquele estado, e pode reconstruir o saldo da conta a qualquer momento.

Premissas e conceitos

Existem algumas premissas para a correta implementação do Event Sourcing:

  • Os eventos são imutáveis: para garantir a confiabilidade dos dados, não é possível alterar eventos já registrados. Para fazer correções, é preciso registrar um evento de compensação;
  • Todo evento é reversível ou contém nele os dados necessários para sua reversão, quando aplicável: aqui é importante ressaltar a diferença entre definir um valor e adicionar um valor. Por exemplo, se a conta A tinha o saldo de R$10,00, podemos definir o novo valor como R$20,00 ou adicionar a ela R$10,00 através de um evento. Para que a reversão aconteça, o melhor é salvar o evento de adição e, caso necessário, registrar um evento oposto;
  • Eventos fazem parte da linguagem ubíqua: todos precisam ser compreensíveis para os stakeholders e devem ter nomes claros;
  • Todo evento deve ter um nome no pretérito perfeito: considerando que um evento sempre é algo que ocorreu no passado, é importante que o nome além de claro reflita o que aconteceu e não uma intenção;
  • A ordem importa: eventos devem ser registrados na ordem em que ocorreram. Esse assunto vai ser abordado com mais profundidade ao longo do texto;
  • Eventos devem ser versionáveis: o schema de um evento pode mudar ao longo do tempo, por isso é importante preparar os handlers para lidar com diferentes versões de eventos. Para isso, pode-se adicionar um campo "version" no documento, a versão como sufixo do nome do evento ou, em último caso, atualizar eventos antigos (mas isso quebra a imutabilidade);
  • Idempotência: handlers de eventos precisam ser idempotentes, principalmente considerando que é uma boa prática aplicar retries para garantir o processamento de todos os eventos. Não deve ser possível processar o mesmo evento mais de uma vez. Além de utilizar Ids únicos para cada evento, é importante manter uma tabela ou cache de eventos já processados e ignorar os eventos já aplicados;
  • Não é uma boa prática armazenar o estado junto ao evento, tanto pelo acoplamento gerado que dificulta mudanças na lógica de negócios quanto pela necessidade extra de processamento dos eventos passados para chegar ao estado e processar o evento em questão.

Mas o que é um evento? Segundo Greg Young, evento é algo que ocorreu no passado e representou uma alteração no estado da aplicação. Um evento torna os efeitos colaterais dessa mudança explícitos - sabemos que se a conta A tem R$20,00 agora é porque esse saldo foi adicionado, podemos saber quem adicionou, quando, e se foi subtraído algum valor. Quando apenas acontece a alteração do estado, por exemplo, foi definido o valor de R$20,00 no saldo da conta A, não se sabe o que aconteceu para que se chegasse àquele valor.

Reprocessar todos os eventos sempre?

Com tudo isso em mente, pode surgir uma dúvida: toda vez que for necessário acessar o estado atual da aplicação será preciso processar todos os eventos? Qual é o custo disso para a memória?

Realmente quando se trata de uma enorme quantidade de eventos poderíamos ter um custo considerável de memória e atrasos na exibição do estado para os usuários. Para resolver esse problema, pode-se utilizar snapshots, que representam o estado da aplicação em um determinado momento. Eles podem ser criados em paralelo ao funcionamento da aplicação de acordo com regras definidas, como "salvar uma vez por dia" ou "salvar o último estado depois do registro de um evento".

Com os snapshots, ao precisar reconstruir o estado basta buscar o snapshot mais próximo ao estado desejado e reprocessar somente os eventos subsequentes.

É importante ressaltar que o snapshot não substitui os eventos, justamente por representar o estado em um determinado momento apenas. Ele pode ficar salvo na mesma base dos eventos, em bancos otimizados para leitura ou até mesmo na memória a depender da necessidade da aplicação.

Event Sourcing e outros patterns

O Event Sourcing combina muito bem com o CQRS (Command-Query Responsability Segregation), favorecendo sistemas distribuídos. Os comandos criam eventos ao invés de alterar diretamente os dados, os eventos são utilizados para gerar read models otimizados (que podem também ser os snapshots). Dessa forma, escrita e leitura podem escalar de forma independente.

Alternativamente ao Event Sourcing mas com a finalidade de permitir a auditabilidade, é possível utilizar o CDC (Change Data Capture) que monitora alterações diretamente no banco de dados e permite auditabilidade técnica, mas não semântica.

Quando não usar e desafios

É verdade que esse pattern não se aplica a qualquer sistema. Suas vantagens são percebidas em aplicações complexas que exigem rastreabilidade, já que a complexidade para lidar com os eventos levando em consideração todas as premissas aumenta a curva de aprendizado. Além disso, há um custo de armazenamento e manutenção dos eventos e a dificuldade de fazer consultas simples direto no banco de dados, já que para chegar ao estado atual todos os eventos precisam ser processados.

Existem outros desafios a serem enfrentados quando se aplica esse pattern. É preciso lidar com a consistência eventual, garantindo que o usuário tenha ciência desse delay entre o evento e seu processamento. Além disso, pela assincronicidade do sistema, é possível que dois eventos concorrentes cheguem na ordem errada. Para garantir que os eventos sejam salvos na ordem correta, deve haver uma validação de timestamp ou id incremental.

Há um risco a ser observado no desenvolvimento: quando um handler gera um evento que ativa o mesmo handler o sistema pode entrar em um loop infinito.

Sobre os handlers, é importante destacar que devem apenas lidar com os eventos e salva-los de forma durável antes de produzir quaisquer outros efeitos.

Conclusão

O Event Sourcing traz uma série de vantagens para aplicações de domínio complexo em sistemas distribuídos, principalmente quando há necessidade de auditabilidade dos dados. Ainda assim, deve ser aplicado considerando os tradeoffs mencionados anteriormente.

Top comments (0)