Back-End

24 set, 2018

Event Sourcing em Python

Publicidade

Em 2002, Chris Harn, um Desenvolvedor Sênior da empresa Autotote, cuidava do software que administrava 65% das apostas de corridas de cavalos da América do Norte na época. Chris decidiu invadir o sistema e manipular os dados dos tickets de apostas.

Tudo estava planejado para acontecer na segunda maior data de corridas dos Estados Unidos. Chris fez uma aposta comum, assistiu as primeiras quatro corridas e depois editou os dados do ticket da aposta diretamente no disco para ser uma das possíveis vencedoras antes do envio para o sistema principal. Normalmente haveria entre 20 e 30 vencedores, o suficiente para não levantar suspeitas. Porém, um dos cavalos que ele apostou não teve nenhuma outra aposta, fazendo-o o único vencedor.

Toda essa situação poderia ter sido evitada utilizando mecanismos de persistência de dados imutáveis. A capacidade de reescrever um evento é uma vulnerabilidade de segurança. Em sistemas críticos que exigem rastreabilidade de informações, é aconselhável a utilização de Event Sourcing.

O que é Event Sourcing?

Event Sourcing garante que todo o estado de um objeto possa ser representado por um Fluxo de Eventos. Essa abordagem permite saber como os dados chegaram naquele estado final. Os eventos deixam de ser apenas uma informação sobre um ocorrido e se tornam a fonte da verdade.

Abordagem prática em Python

Consideremos um sistema financeiro que precisa registrar todas as operações que aconteceram na conta dos usuários. Dentro desse cenário, nós precisamos realizar quatro operações básicas:

  • Abertura de conta
  • Depósito de fundos na conta
  • Retirada de fundos na conta
  • Visualização de saldo da conta

Analisando essas operações, precisamos criar um evento para cada mudança de estado do objeto. Os Eventos devem formar um Fluxo de Eventos que será ordenadamente persistido.

Figura 1 — Flxo de Eventos de conta

Definindo os Eventos

Os eventos podem ser implementados como qualquer estrutura de dados, porém, é recomendável que seja serializável. Para este exemplo implementaremos utilizando namedtuples, conforme a figura 2:

Figura 2 — Eventos como namedtuples

Construindo um objeto orientado a eventos

Precisamos reproduzir o Fluxo de Eventos no objeto, então precisaremos de uma classe que tenha um método para receber qualquer evento e endereçar para o método correto de tratamento do evento e salvar os novos eventos em uma lista.

Figura 3 — Classe para direcionar os eventos

Na figura 3, o construtor pode receber uma lista de eventos e passar cada um deles para o método mutate, que é responsável por chamar um método de tratamento para aquele evento específico, conforme a figura 4.

Figura 4 — Fluxo de chamada de método de tratamento do evento

Agora precisamos definir um objeto para representar a conta, porém, iremos separar a sua definição em duas classes: uma classe de alteração de estado e outra classe para comportamentos.

Figura 5 — Classe para tratamento dos eventos

Na figura 5 nós temos uma classe para alteração do estado do objeto conforme os eventos que ele receber. O reconhecimento do método ocorre pelo nome do evento graças a sua herança de MutationMixin, conforme a figura 4.

Agora precisamos de uma classe que aplique os eventos no objeto e os armazene em uma lista interna do objeto.

Figura 6 — Classe para gerenciar a aplicação de novos eventos

Conforme a figura 6, nós temos um método apply que será utilizado no objeto para adicionar novos eventos. O método apply, além de chamar o método especializado, também adiciona o evento na lista de novos eventos.

Figura 7 — Classe para comportamentos

Na figura 7 temos uma classe que contém apenas os comportamentos e suas regras de negócio. Sempre que precisar alterar o estado do objeto, será chamado o método apply passando um evento com os seus devidos atributos.

Essas divisões foram feitas para melhorar a organização do código. O fluxo completo da alteração de estado de BankAccount através dos comportamentos pode ser visto na figura 8.

Figura 8 — Fluxo de alteração do estado do objeto

Implementação de Armazenamento de Eventos

Agora precisamos implementar um mecanismo de armazenamento de eventos. O Armazenamento de Eventos é dividido entre duas classes: EventStore e AppendOnlyStore. O EventStore é uma interface entre a aplicação e o mecanismo de persistência, enquanto o AppendOnlyStore é uma interface para o mecanismo de persistência. No nosso exemplo iremos utilizar o SQLite 3 para persistência dos Fluxos de Eventos.

Primeiro vamos precisar criar uma tabela para representar o nosso Event Store.

Figura 9 — SQL para criação da tabela

Agora precisamos do AppendOnlyStore para ser a nossa interface com o SQLite 3. Ele pode se adequar a qualquer base de dados relacional ou NoSQL com algumas modificações. Há também a possibilidade de fazer com que esse mecanismo trabalhe apenas com o sistema de arquivos.

Figura 10 — AppendOnlyStore para SQLite 3

O EventStore precisa de algum formato de serialização para persistir o Fluxo de Eventos (JSON, XML, Protocol Buffers). Para simplicar utilizaremos o Pickle, que está disponível na biblioteca padrão do Python.

Figura 11 — EventStore utilizando pickle para serialização

Fazendo a aplicação funcionar

Agora que temos tudo pronto, só precisamos montar os nossos casos de uso.

Figura 12 — Aplicação utilizando Event Sourcing

Vantagens

  • A razão para cada alteração não é perdida, trazendo confiabilidade
  • Excelência em inteligência de negócio a médio e longo prazo
  • Capacidade de reproduzir o estado de um objeto em qualquer ponto do tempo para fins de rastreabilidade, auditoria e depuração
  • O Fluxo de Eventos apenas adicionar novos Eventos tem um ótimo desempenho

Desvantagens

  • Exige um entendimento profundo do negócio, então é recomendável apenas para problemas complexos
  • Poucas ferramentas e materiais para auxiliar na implementação, podendo aumentar os riscos de introduzir essa abordagem em projetos com times inexperientes
  • Poucos desenvolvedores com experiência nessa abordagem no mercado
  • A implementação é quase inviável sem alguma forma de Segregação de Responsabilidades por Consultas e Comandos (CQRS), pois os fluxos de eventos são difíceis de pesquisar

Considerações finais

Por mais incrível que seja ter um sistema utilizando Event Sourcing, o custo para implementar e manter uma abordagem tão complexa pode não compensar na maioria dos projetos. A regra mais importante é tentar ser pragmático e verificar se realmente compensará, dependendo do cenário.

Referências