Design de aplicativos

Event Sourcing | Registro de eventos - um padrão essencial de microsserviço

Como mencionei em minha postagem anterior sobre como falhar com microsserviçosA depuração de um sistema distribuído é uma tarefa desafiadora. Muitas coisas podem dar errado, e algumas delas estão fora do nosso controle, como instabilidade da rede, indisponibilidade temporária ou até mesmo bugs externos. Hoje discutiremos como você pode usar o padrão Event Sourcing / Event Logging para "voltar no tempo" e entender o que deu errado.

Felizmente, há muitas ferramentas disponíveis para monitorar a rede para detectar e registrar eventos inesperados. Malhas de serviço são agora uma opção popular, e você também pode usar ferramentas como OpenTracing para registro distribuído. No entanto, quando falamos em entender o estado de nossas entidades, não há uma estrutura rápida e fácil de usar.

Seus dados potencialmente sobreviverão ao seu código e, ainda assim, ignoramos a evolução de nossos dados ao longo do tempo. Na maioria dos sistemas, até mesmo perguntas simples como "como essa entidade chegou a esse estado?" ou "como estava meu estado há um mês?" são impossíveis de responder, pois nenhum histórico de alterações foi salvo. Manter o controle dessas alterações é fundamental para um sistema saudável, não apenas para fins de segurança ou depuração, mas devido ao seu grande valor comercial (seu Product Owner ficará feliz).

 

A solução

 

Uma excelente maneira de dar visibilidade ao que está acontecendo no seu serviço é por meio do Event Sourcing | Event Logging. O conceito fundamental por trás desse padrão de 10 anos é que cada alteração no estado de um aplicativo deve ser encapsulada em um objeto de evento e armazenada sequencialmente. Se isso lhe soa familiar, talvez seja porque qualquer versão de sistemas de controle ou logs de transações de banco de dados é um grande usuário desse padrão de eventos.

Mas vamos nos aprofundar para entender como ele funciona. Supondo que estejamos criando um Serviço de pedidos para um comércio eletrônico, vamos ver como seriam o estado e os eventos do nosso aplicativo:

Muitos autores definem três regras principais para o sistema de registro/fornecimento de eventos:

  • Os eventos são sempre imutáveis;
  • Os eventos são sempre algo que aconteceu no passado. Alguns desenvolvedores confundem comandos (ex.: PlaceOrder) com eventos (ex.: OrderPlaced)
  • Teoricamente, a qualquer momento, você pode abandonar seu estado atual e reconstruir todo o sistema apenas reprocessando todas as mensagens recebidas.

Outro aspecto interessante desse padrão é que ele o incentiva a pensar sobre os eventos do seu sistema antes de pensar em como será a estrutura real. Isso pode ser contraintuitivo a princípio, pois aprendemos a projetar um sistema desenhando entidades e propriedades, mas está bem alinhado com outra recomendação comum do DDD: pensar primeiro em como seus serviços se comunicarão entre si para identificar facilmente domínios.

 

Fluxo de fornecimento de eventos | registro de eventos

O fluxo mais comum para o sourcing de eventos é semelhante ao seguinte:

  • Receptor de mensagens é responsável por converter a solicitação de entrada em um evento e validá-lo.
  • Loja de eventos é responsável por armazenar os eventos sequencialmente e notificar os ouvintes.
  • Ouvinte de eventos: Como você pode imaginar, esse é o código encarregado de executar a respectiva lógica comercial de acordo com cada tipo de evento.

Há muitas implementações possíveis desse padrão, uma das quais é usar o Serviço de eventosintroduzido no Couchbase 5.5. Em resumo, ele permite que você escreva funções que são acionadas sempre que um documento é inserido/atualizado/excluído. O mecanismo de eventos também permite que você faça enrolar portanto, sempre que um determinado documento for armazenado no banco de dados, você poderá acionar um endpoint em seu aplicativo para processá-lo. Vejamos como seria o uso de eventos:

Se você quiser ler mais sobre o assunto, consulte o Documentação oficial do couchbase eventing.

O Couchbase Eventing é assíncrono, portanto a implementação acima é adequada somente se o seu aplicativo receber apenas chamadas assíncronas. Ela também pode funcionar como uma camada extra de segurança para acionar uma notificação, por exemplo, se alguém tentar atualizar um evento manualmente.

Em alguns sistemas, os campos e a estrutura do evento podem diferir significativamente uns dos outros, e a estrutura fixa dos RDBMs dificulta a modelagem do armazenamento de eventos. Por esse motivo, os desenvolvedores geralmente armazenam seus eventos como um Cadeia de caracteres JSON em um varchar campo. Essa abordagem tem um grande problema: Ela torna seus eventos difíceis de encontrar, pois a maioria de suas consultas será lenta, complicada e cheia de 'gostos. Uma das soluções possíveis para isso é usar bancos de dados de documentos, já que a maioria deles armazena documentos como JSON e tem uma linguagem semelhante à SQL adequada para consultá-los, como N1QL.

 

 

Snapshotting - Controle de versão para seu estado

Adicionar versão/histórico ao seu estado às vezes é chamado de "snapshotting" no mundo do fornecimento de eventos. É essencial evitar o reprocessamento de todos os eventos sempre que você precisar saber qual era o seu estado N dias atrás. Isso também ajuda na depuração, pois você pode reconhecer rapidamente o ponto no tempo em que o estado do aplicativo é diferente do esperado após o processamento de um evento.

O snapshotting é útil, econômico, fácil de implementar e excelente para relatórios temporais. Se você decidiu implementar padrões de Event Sourcing, faça um pouco mais de esforço para implementar também o snapshotting.

 

Correção de inconsistências

Geralmente é aqui que todos os seus esforços valem a pena. Depois de instalar o event sourcing/logging e o Snapshotting, você pode usar uma versão ligeiramente modificada do método Evento retroativo para corrigir inconsistências.

Em resumo, se você corrigiu um bug e agora também precisa ajustar o estado das entidades afetadas, em vez de atualizá-lo manualmente, você pode definir o estado da sua entidade para o que era antes do bug e reproduzir todos os eventos relacionados desde então. Isso corrigirá automaticamente seu estado sem a necessidade de um manual intervenção.

 

 

  • Estado de reversãoReverter o estado de uma entidade para o que era antes do bug. Você pode evitar Etapas 1 e 2 apenas reproduzindo todos os eventos. Nesse caso, no entanto, estamos restaurando um estado anterior porque gostaríamos de evitar o reprocessamento de todo o processo.
  • Ignorar instantâneos: todos os snapshots após o restaurado devem ser marcados como ignorado para evitar a restauração de um snapshot inconsistente no futuro.
  • Eventos RebuildReconstrução de todos os eventos a partir do destino.

Mas e se o evento tiver dados errados ou nunca deveria ter sido acionado? Podemos atualizar ou excluir o evento e reprocessar tudo?

Se você se lembrar, a primeira regra do fornecimento de eventos é que "Os eventos são sempre imutáveis" e isso é por um bom motivo: você precisa confiar no log que está vendo. Mas isso não responde à nossa pergunta, apenas a modifica ligeiramente: como podemos alterar o log de eventos sem alterar o evento?

Bem, uma maneira fácil de resolver esse problema é marcar os eventos como ignorável para que possamos ignorá-los durante o processo de reconstrução:

E se um evento tiver sido acionado pelos dados errados ou na ordem errada? Usando essa abordagem, tudo o que precisamos fazer é marcar o evento como ignorável e adicionar um novo evento com os valores corretos ou na posição correta, como segue:

Legal, não é? Mas há uma tarefa extra complicada aqui: como podemos criar uma sequência de eventos que permita que você adicione eventos no meio?

Uma solução ingênua é adicionar um contador de flutuação para cada entidade. Isso permitirá que você adicione itens no meio infinitamente, de acordo com a teoria de supertarefas (na prática, você está limitado pelo tamanho máximo de float/double), o que normalmente é espaço mais do que suficiente para adicionar todos os eventos necessários para corrigir seu estado:

É claro que a abordagem acima tem muitas falhas, mas é ridiculamente simples de implementar, fácil de consultar e funciona muito bem na maioria dos casos. Se precisar criar uma estrutura mais robusta, considere armazenar seus eventos em uma estrutura de lista vinculada:

 

E quanto aos sistemas externos e outros microsserviços?

Um microsserviço não é uma ilha, portanto, é razoável pensar que um dos efeitos colaterais da reprodução de eventos é que seu serviço pode enviar mensagens para outros serviços externos. Essas mensagens podem acionar inconsistências ou propagar erros em outros sistemas, o que pode tornar a situação pior do que era antes.

Infelizmente, devido à variedade de possibilidades, não existe uma solução mágica para resolver esse problema, e cada caso deve ser tratado individualmente. Algumas das soluções convencionais são:

  • Alterar temporariamente a configuração para não enviar nenhuma mensagem externa ou adicionar um interceptador que permita configurar quais mensagens precisam ser enviadas;
  • Redirecionar solicitações específicas para um serviço falso (um cenário típico se você estiver usando o padrão Service Mesh)
  • Permitir que outros serviços reconheçam que uma determinada operação já foi executada no passado com os mesmos parâmetros e, em vez de lançar um erro, retornar apenas a mesma mensagem de sucesso anterior.

Naturalmente, há um número considerável de casos em que você não poderá corrigir inconsistências externas automaticamente. Nesse cenário, espera-se que outros sistemas imprimam um erro legível por humanos e/ou acionem uma notificação para que alguém intervenha.

 

Vantagens do Event Sourcing 

Embora seja um padrão simples, há muitas vantagens em usá-lo:

  • O registro de eventos tem um alto valor comercial;
  • Ele funciona muito bem com DDD e arquiteturas orientadas a eventos.
  • Audição da origem de todas as alterações no estado de seu aplicativo;
  • Ele permite que você reproduza eventos com falha;
  • Depuração fácil, pois você pode copiar todos os eventos de uma entidade de destino para o seu computador e depurar cada evento para entender como o aplicativo atingiu um estado específico (ignore as implicações de segurança da cópia de dados da produção);
  • Permite que você use o Evento retroativo padrão para reconstruir/consertar seu estado.

Muitos autores também incluem como vantagem a capacidade de fazer consultas temporais, mas considero que consultar vários eventos subsequentes não é uma tarefa trivial. Portanto, geralmente percebo a consultas temporais como uma vantagem do padrão Snapshotting.

 

Desvantagens do Event Sourcing

  • É um pouco menos intuitivo trabalhar com chamadas síncronas, pois você precisará primeiro transformar a solicitação em um evento.
  • Sempre que você implantar uma alteração significativa, será forçado a migrar também o histórico de eventos se quiser manter a compatibilidade com versões anteriores (também conhecida como atualização de eventos).
  • Algumas implementações podem precisar de um trabalho extra para verificar o estado dos eventos mais recentes para garantir que todos eles tenham sido processados.
  • Os eventos podem conter dados privados, portanto, não se esqueça de garantir que o registro de eventos esteja devidamente protegido.

 

Conclusão

Mostrei uma versão ligeiramente modificada do padrão Event Sourcing / Event Logging que tem funcionado bem para mim nos últimos anos. A primeira vez que ouvi falar dele foi há quase 10 anos, na famosa postagem do blog de Martin Fowler (uma leitura obrigatória). Desde então, ele tem me ajudado muito a tornar o estado dos meus microsserviços quase inquebrável, sem falar em todos os recursos de relatório.

No entanto, não é algo que deva ser usado indiscriminadamente em todos os seus serviços. Pessoalmente, acho que apenas os principais realmente valem a pena. Você provavelmente não precisa manter o histórico de todas as vezes que o usuário alterou seu próprio nome no sistema, por exemplo.

 

Se você tiver alguma dúvida, sinta-se à vontade para me enviar um tweet para @deniswsrosa

 

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

Author

Posted by Denis Rosa, defensor dos desenvolvedores, Couchbase

Denis Rosa é um Developer Advocate do Couchbase e mora em Munique, na Alemanha. Ele tem uma sólida experiência como engenheiro de software e fala fluentemente Java, Python, Scala e Javascript. Denis gosta de escrever sobre pesquisa, Big Data, IA, microsserviços e tudo o mais que possa ajudar os desenvolvedores a criar um aplicativo bonito, mais rápido, estável e escalável.

Um comentário

  1. Belo artigo com ótimos conselhos, Denis!

    Usei o Couchbase como um armazenamento de eventos e posso ver como o Eventing Service simplificará a criação de instantâneos e ajudará nas tarefas de limpeza e manutenção do armazenamento de eventos. Mas mesmo sem snapshots, a recuperação e a reprodução de eventos são muito rápidas com o Couchbase.

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.