Event-sourcing (ES) em uma Arquitetura de Microsserviços

Marcelo M. Gonçalves
12 min readFeb 22, 2020

--

Quando escrevemos nossa aplicação utilizando padrões como CQRS (Command Query Responsibility Segregation), tipicamente necessitamos atualizar seu estado de maneira a encaixar naturalmente aos eventos gerados como consequência da execução dos comandos. Estas atualizações de estado no domínio de nossa aplicação geram eventos, os quais representam, de maneira ordenada, um Snapshot contendo um conjunto de mudanças realizadas durante o ciclo de vida de nossa aplicação.

Event-sourcing trata-se uma maneira de atualizarmos o estado da aplicação com base em um conjunto de eventos ocorridos na linha do tempo. Em uma abordagem tradicional, a persistência de entidades armazena somente o seu estado atual. Event-sourcing modifica este comportamento, apresentando uma alternativa radicalmente diferente, sendo a persistência centrada nos eventos capturados. Desta forma, um objeto de negócio é armazenado como uma sequência de eventos imutáveis constituindo sua mudança de estado temporal.

Captura e Persistência de Estado

Event-sourcing persiste o estado de uma entidade de domínio em uma sequência de state-changing events (eventos de mudança de estado). Assim, sempre que o estado de alguma entidade sofre alteração, um novo evento, relacionado a determinada mudança, é gerado e adicionado a uma lista sequencial (append only). O conjunto de eventos gerados para determinado objeto representa uma entidade persistente dentro do event-store (armazenamento de eventos), sendo identificada através de seu identificador global. O evento então é publicado dentro da aplicação, em seguida ocorrendo um broadcast possibilitando que sejam capturados por diferentes event handlers (controladores de eventos), espalhados pelo ecossistema de nossa aplicação, reagindo aos acontecimentos dentro do domínio, possibilitando com que uma sequência em cadeia se concretize.

Toda a mudança de estado na aplicação gera eventos, desta forma, toda a reconstrução de instâncias de qualquer entidade é realizada aplicando-se um replay de seus eventos gerados a partir do último snapshot (checkpoint).

Event-sourcing, centraliza a estrutura da aplicação em eventos, naturalmente facilitando a implementação do EDA (Event-driven Architecture), favorecendo a utilização de características como imutabilidade e auditoria e melhorando a performance de nossa aplicação. Event-sourcing pode ser aplicado em cenários onde já existem iniciativas como a utilização de uma arquitetura de eventos. Neste caso, eventos já são orgânicos nesta estrutura e nenhum esforço extra seria necessário, facilitando a adoção de event-sourcing.

Armazenamento no Event Store

Aplicações baseadas em event-sourcing utilizam o event-store, uma espécie de banco de dados para armazenamento de eventos. Eventos armazenados e resgatados do Event-store trafegam por meio de uma abstração de acesso aos dados, uma API que possui os conhecimentos necessários para lidar com os formatos dos eventos armazenados.

Naturalmente, sempre que um evento é armazenado no event-store, automaticamente também é publicado em um canal de transmissão, sendo difundido e chegando aos componentes inscritos no evento, sendo então estimulados por ele (event-handlers).

Cada evento persistido no event-store também é entregue aos Subscribers interessados e determinada funcionalidade depende da solução de implementação adotada na camada do message broker. O Event-store pode ser considerado o backbone (espinha dorsal) para aplicações baseadas em arquitetura de microsserviços utilizando EDA (Event-driven Architecture). Toda a comunicação entre os serviços é realizada a partir da publicação e resgate de eventos que passaram pelo Event-store, podendo estas interações serem chamadas HTTP externas, ou mesmo eventos publicados por outros componentes ou serviços.

Abordagens Tradicionais vs Comandos/Eventos

Ao pensarmos em abordagens tradicionais, estruturamos nosso modelo de dados relacional e sequencialmente adaptamos nossas entidades para refletir este modelo. Quando nos referimos a aplicações baseadas em event-sourcing, o mais comum é considerarmos inicialmente as ações que nossa aplicação realizará em conjunto com efeitos colaterais gerados a partir destas ações.

Neste contexto, os padrões CQRS e event-sourcing encaixam-se organicamente possibilitando a prática com comandos e eventos. Aplicações CRUD tradicionais executam atualizações diretamente em um único armazenamento de dados, reduzindo o desempenho e escalabilidade por parte da carga gerada pelas escritas nos mesmos dados ao mesmo tempo em que são resgatados por algum componente de leitura (ou mesmo joins). Assim, conflitos de atualização com diversos usuários executando ações em paralelo podem ser mais prováveis.

Os benefícios ao utilizar um estilo de persistência baseado em uma fonte de eventos incluem a possibilidade de logs de auditoria com maior precisão se comparado a modelos de persistência tradicionais, incluindo a facilidade na composição de queries temporais, por conta do histórico completo mantido.

Uma das características principais sobre event-sourcing é que todas as mudanças de estado na aplicação são executadas obrigatoriamente a partir da criação de eventos, sendo uma espécie de log, mantendo um histórico temporal de eventos sobre nossos objetos de domínio. Desta forma, aplicações baseadas em event-sourcing possibilitam a criação de funcionalidades em cima de nosso event-log gerado.

Replay de Eventos e Reconstrução de Estado

Através do uso de eventos, temos a possibilidade de descartar o estado da aplicação atual e recriá-lo, aplicando um replay dos eventos na ordem em que ocorreram. Portanto, em aplicações baseadas em event-sourcing, event-replay (replay de eventos) temos a possibilidade de recriar cenários passados, apontando para determinado ponto no tempo onde o evento capturado ocorreu.

Conceitualmente, o estado da aplicação pode ser armazenado em memória, em cache ou no disco, desde que possua sua base no Event-log gerado. Porém, no caso de armazenagens não persistentes, funcionalidades como replay não estariam disponíveis pois em caso de falhas, os eventos armazenados em memória seriam destruídos.

Recomenda-se que o estado atual da aplicação seja mantido em um data-store separado, seja desnormalizado ou relacional, mantendo o event-store como base de auditoria para execução de processamentos especiais e isolados nos dados. Consultas ou projeções temporais podem ser construídas de forma desacoplada ao event-store, possibilitando a ele ser a fonte única e imutável sobre os fatos nos dados.

Performance de Carregamento e Snapshots

Algumas entidades podem receber diversos eventos em sua composição, tornando-a extensa. A otimização do carregamento deste tipo de instância pode ser realizada pela aplicação de snapshots periódicos relacionados ao estado das entidades no Event-store.

Snapshots aceleram a reconstrução das instâncias, pois seria preciso apenas encontrar o snapshot mais recente e reaplicar a partir dele a sequência de eventos, resultando em um número menor de eventos a reprocessar na reconstituição da instância da entidade.

Os Snapshots podem ser salvos tanto periodicamente quanto por quantidade de alterações nas entidades. Cada evento possui dados suficientes para a reconstrução do estado de uma entidade. Eventos são imutáveis, incrementados em formato append only (somente inserção) no event-store e em casos de falha, novos eventos representando o estado de falha da instância de uma entidade devem ser gerados e aplicados.

Quando aplicado CQRS+ES pode beneficiar o modelo de leitura de nossa aplicação, estando desacoplada da camada de visualização dos dados permite evoluirmos individualmente na construção de projeções e visões materializadas sem impactar a fonte de dados oficial. Com esta separação, evitamos interferências relacionadas aos processos de reidratação das entidades baseadas em event-sourcing posicionadas no modelo de escrita.

Event Sourcing com CQRS e DDD

Em conjunto com CQRS, o modelo de escrita baseado em event-sourcing realiza o tratamento de regras de negócio de forma isolada ao canal de leitura. Ficando o modelo de domínio responsável por incorporar os comportamentos da aplicação e reconstruir tanto o estado atual quanto os anteriores dos objetos. Assim, sempre que houver atualização de estado de nossa aplicação, eventos serão publicados no canal de escrita da aplicação e posteriormente refletidos no banco de dados de armazenamento do modelo de leitura.

Event-sourcing descreve uma maneira de computar o estado atual de uma instância de um agregado de acordo com a sequência de eventos armazenados no event-store.

Cada evento recebido no Event-store é publicado em uma estratégia de Event-bus apropriada, possibilitando o tracking das mudanças de estado trilhadas sobre o histórico de eventos que constituem o estado atual do objeto.

Eventos usados como ações de compensação de erros também mantém histórico dos dados que foram revertidos, em contraste aos modelos que somente armazenam o estado atual dos objetos. Ao utilizarmos event-sourcing como repositório oficial de dados para o modelo de escrita, os dados dos eventos nunca devem ser atualizados, pois eventos são imutáveis, como forma de atualizar entidades seria criando eventos novos de compensação sequenciais no repositório.

Uma sequência de eventos pode ser usada para detectar tendências de comportamento do usuário ou mesmo resgatar outras informações relevantes ao negócio.

Event-sourcing facilita a implementação de garantias de consistências e atualizações em tempo real nos dados, administrando atualizações conflitantes de dados de forma isolada por consequência do desacoplamento de eventos. Além disso, problemas de escalabilidade e contenção de dados podem ser resolvidos no caso de cenários de alta concorrência por conta da imutabilidade dos dados (append-only), eliminando a necessidade de verificação do evento antes de sua persistência.

Certos cuidados precisam ser tomados ao considerarmos basear nossa aplicação em event-sourcing, como a evolução e publicação de novas versões da estrutura de eventos.

Quando adotado Domain-driven Design, nossos agregados precisam ser pensados considerando seu armazenamento como fonte de eventos persistida no event-store e refletida em todo seu life cycle (ciclo de vida). Os fundamentos do Aggregate Design-pattern aplicado ao ES possibilita a garantia de que suas mudanças de estado sejam capturadas e armazenadas como uma sequência ordenada de mudanças, devendo as próximas sempre serem acrescidas aos demais. Cada alteração como um novo evento (append only).

Em uma arquitetura event-sourcing estamos principalmente interessados nos eventos, desta forma é importante distinguirmos a diferença entre comandos e eventos. Um comando representa uma intenção, modelada com o verbo no infinitivo, enquanto um evento representa um fato, tendo sua nomenclatura modelada no tempo verbal passado.

Event Sourcing com EDA e Compensações

Em cenários de erro, aplicados juntamente com EDA, novos eventos seriam criados e transmitidos obedecendo a sequência temporal do fluxo em execução existente. Nesse caso, desfazendo o estado anterior do objeto através de compensações (compensate), evitando problemas relacionados a sua mutação de estado (state mutation), frequentemente encontrados em aplicações CRUD tradicionais.

Podemos comparar o estado entre os objetos heterogêneos em um determinado período no tempo, bastando para isso retroceder, aplicando projeções sobre os eventos armazenados.

Em uma arquitetura de microsserviços, integrada com Event-driven architecture (EDA) e CQRS (Command Query Responsibility Segregation), estamos em busca de vantagens como comunicação assíncrona, baixo acoplamento, facilidade na escalabilidade horizontal e recuperação de falhas.

Event-sourcing pode ser introduzido em uma arquitetura de microsserviços, e no âmbito arquitetural do ponto de vista da persistência.

Os microsserviços que possuem interesse nos eventos emitidos em um ecossistema EDA, devem inscrever-se nos mesmos com o objetivo de comunicar-se. Event-sourcing pode ajudar toda a cadeia de comunicação baseada em microsserviços, e seus databases individuais, mantendo-os sincronizados e eventualmente consistentes.

Em aplicações event-sourcing, cada evento disparado será armazenado no event-store como um registro (record), incluindo operações de update ou delete. Independente do tipo de operação executada, uma lista confiável e sequencial de eventos será criada e mantida, e a partir desta fonte, eventos podem ser utilizados em outras partes da nossa aplicação.

Em caso de falhas no processamento de uma transação, um evento representando determinado cenário de erro será armazenado no event-store de forma incremental, imutável e atômica. Neste caso, tornar os dados eventualmente consistentes envolvem operações assíncronas integradas ao data-flow da aplicação suportado por infraestrutura de mensagens necessária para funcionamento.

Logs Imutáveis e Troubleshooting

Event-sourcing resolve problemas relacionados à atomicidade, mantendo auditoria dos dados, podendo ser integrado com sistemas de análise e interpretação de dados avançados. Se mantido histórico, as alterações de estado dos objetos são preservadas, facilitando a recuperação e reconstrução de instâncias, facilitando solução de problemas (troubleshooting) posterior.

Repositórios de event-streams podem tornar-se logs de auditoria centralizando todas as mudanças ocorridas. Notificações podem ser enviadas com base nos eventos originais. Análises podem ser executadas em cima de streams de eventos históricos.

Em conjunto com os comandos, eventos descrevem comportamentos que as entidades deveriam assumir em se tratando de DDD, demonstrando explicitamente suas intenções e reações no conjunto de dados. Event-sourcing promove flexibilização do uso do EDA, trazendo uma nova visão em termos de comunicação e armazenamento do ponto de vista da aplicação, permitindo-nos armazenar todos os estados assumidos por determinada entidade em todo o seu ciclo de vida.

Dependendo do tipo de cenário, event-streams podem ser difíceis de manusear, podendo adicionar níveis de complexidade desnecessários ao projeto. Ao aplicar event-sourcing, dependendo da estratégia, será crucial pesar os prós e contras da abordagem.

Quando utilizado com CQRS, event-sourcing seria posicionado no modelo de escrita materializando os objetos de domínio, armazenando entidades em formato de acréscimo sequencial de eventos e registrando ações completas nos dados. Embora se complementem para aplicações que colaboram e comunicam-se por meio de eventos, CQRS e event-sourcing não necessariamente devem ser implementados em conjunto na mesma aplicação.

Razões Históricas, Tipos de Evento e Auditoria

Event-sourcing pode ser utilizado em cenários onde necessitamos conhecer os motivos e intenções executadas nos dados. Os objetivos poderiam ser capturados em formato de eventos, facilitando a identificação das razões as quais originaram determinadas mudanças, refletindo em sua nomenclatura dos eventos a expressividade do fato.

Além de podermos reproduzir cenários passados, realizando replay dos eventos sequenciais, restaurando o estado das entidades que compõem o software, possibilitando o histórico com logs de auditoria, pois o gerenciamento e administração do estado consistente dos dados pode ser realizado mais facilmente utilizando event-sourcing.

Event-sourcing permite que todas as alterações de uma aplicação sejam armazenadas em formato específico respeitando sua ordem de ocorrência, possibilitando a montagem de projeções referentes ao estado atual ou anterior de nossa aplicação. Assim, projeções montadas sobre o passado permitem auditorias nos dados com facilidade.

Event-sourcing pode ajudar a melhorar o desempenho de interfaces de usuário (UI) por desacoplar os processos de atualização de dados dos eventos necessários para determinadas tarefas. A desassociação dos consumidores acelera o processo de resgate de eventos de diversas fontes diferentes simultaneamente.

É importante que aplicações event-sourced possam coexistir na medida em que evoluem com novas versões de dados, eventos e objetos mantendo compatibilidade. Precisamos manter cuidado no consumo de eventos externos de fontes desconhecidas, evitando que eventos em formatos inconsistentes, ou com diferentes versões, sejam injetados em nosso modelo de domínio, ocasionando conflitos no comportamento da aplicação.

Precisamos exigir compatibilidade de leitura destes eventos, sendo conservador e o mais abstrato possível quando lidamos com a definição da criação dos eventos.

Independentemente, como boa prática, precisamos separar eventos internos dos externos, por possuírem necessidades diferentes. Como exemplo, eventos públicos devem ser mais estáveis e possuir contratos mais rígidos, se comparados a eventos internos, onde temos o controle total sobre os mesmos.

Os eventos devem ser processados na ordem correta os quais foram disparados, para isso o mecanismo de implementação deve proporcionar esta garantia. Eventos são reações a intenções publicadas, estando relacionado ao passado, retratando fatos e sua ocorrência.

Utilização de metadados e schemas de definição nos eventos, internos e externos, minimizam riscos de quebrarmos componentes de aplicação quando em conflito de versões.

Estruturalmente, event-sourcing permite reproduzir e consumir os eventos, a qualquer instante, para remontar o estado de determinada instância de uma entidade por parte da aplicação. O fluxo de eventos pode ser usado para criar integração com sistemas externos (fora do domínio da aplicação).

Eventos são objetos simples, imutáveis, que descrevem situações ocorridas, podendo eliminar conflitos de ambiguidade quando descritos com nomenclatura adequada, visando relatar de forma precisa o fato ocorrido.

Cada evento enviado ao repositório representa um conjunto de alterações nos dados, atuando como registro dos acontecimentos relacionados aos elementos da aplicação, materializando objetos de domínio em formato sequencial e registrando ações completas nos dados.

Eventos de atualização que descrevem mudanças nos dados podem ser usados para atualizar interfaces nas camadas de UI da aplicação, mantendo visões materializadas sobre entidades conforme sofrem alterações a cada novo evento que chega ao repositório de dados.

Formato e Versão dos Eventos

O formato (payload) dos eventos armazenados devem ser seguidos de forma rígida, caso contrário podem gerar impactos negativos e conflitos entre os carregamentos de instâncias. Desta forma, podemos considerar utilizar número de versão (considerando data e hora) para nossos schemas de eventos, para efeitos de compatibilidade por conta de concorrência quando residindo em ambientes multi-thread.

Técnicas comuns utilizadas ao adotarmos event-sourcing, visando evitar conflitos de concorrência seria acrescentar um número incremental acompanhando o evento como versão, no caso de duas instâncias tentarem adicionar eventos simultaneamente a mesma entidade, podendo o repositório de eventos gerenciar conflitos isoladamente e garantir que os eventos sejam publicados no repositório somente uma vez.

As abordagens adotadas devem controlar com êxito o incremento e manipulação de agregação de valores nas instâncias das entidades. Na ausência de transações, os conflitos relacionados à consistência através de eventos devem ser tratados.

Para carregamento de instâncias com eventos versionados, event-sourcing é combinado com uma técnica conhecida como snapshot (pontos no tempo), onde encontra-se como crucial o formato, estrutura e semântica dos eventos sendo manipulados.

Consideraçẽs Finais

Microsserviços são componentes modulares que possuem suas próprias camadas, estruturadas de forma independente promovendo o baixo acoplamento, escopo limitado e responsabilidades restritas. Desta forma, cada componente pode evoluir individualmente quando nos referimos a padrões de design arquiteturais, trazendo facilidades na adoção de outros padrões, a exemplo do event-sourcing.

Ao basearmos nossas aplicações em arquitetura de microsserviços buscamos maneiras mais efetivas de trabalharmos com recursos de aplicação. Eventos promovem a construção de aplicações modernas, comprometendo-se a compor a base para a adoção bem sucedida da maior parte dos padrões relacionados aos microsserviços.

Event-sourcing possibilita tirarmos vantagens da combinação diversos padrões de design de arquitetura como CQRS (Command Query Responsibility Segregation), EDA (Event-driven Architecture) e DDD (Domain-driven Design).

Ao combinarmos os designs de arquitetura, tiramos proveito dos pontos positivos de cada padrão arquitetural. Cada padrão possui benefícios e complexidades, sendo necessário resolvermos os trade-offs. Desta forma, independente do padrão adotado, ou conjunto deles, ao escolhermos basear nossa aplicação em eventos, estaremos compondo uma base sólida em direção ao sucesso de nossos microsserviços.

--

--