Padrão de Origem do Evento
Em vez de armazenar apenas o estado atual dos dados em um banco de dados relacional, armazene a série completa de ações executadas em um objeto em um repositório somente acréscimo. O arquivo funciona como o sistema de registos e pode ser utilizado para materializar os objetos do domínio. Essa abordagem pode melhorar o desempenho, a escalabilidade e a auditabilidade em sistemas complexos.
Importante
O fornecimento de eventos é um padrão complexo que permeia toda a arquitetura e introduz compensações para obter maior desempenho, escalabilidade e auditabilidade. Uma vez que seu sistema se torna um sistema de sourcing de eventos, todas as decisões de design futuras são restringidas pelo fato de que este é um sistema de sourcing de eventos. Há um alto custo para migrar de ou para um sistema de fornecimento de eventos. Este padrão é mais adequado para sistemas onde o desempenho e a escalabilidade são os principais requisitos. A complexidade que o fornecimento de eventos adiciona a um sistema não se justifica para a maioria dos sistemas.
Contexto e problema
A maioria dos aplicativos trabalha com dados, e a abordagem típica é que o aplicativo armazene o estado mais recente dos dados em um banco de dados relacional, inserindo ou atualizando dados conforme necessário. Por exemplo, no modelo tradicional de criação, leitura, atualização e exclusão (CRUD), um processo de dados típico é ler dados do armazenamento, fazer algumas modificações nele e atualizar o estado atual dos dados com os novos valores, geralmente usando transações que bloqueiam os dados.
A abordagem CRUD é simples e rápida para a maioria dos cenários. No entanto, em sistemas de alta carga, esta abordagem tem alguns desafios:
Desempenho: À medida que o sistema é dimensionado, o desempenho se degrada devido à contenção de recursos e problemas de bloqueio.
Escalabilidade: os sistemas CRUD são síncronos e as operações de dados são bloqueadas em atualizações. Isso pode levar a gargalos e maior latência quando o sistema está sob carga.
Auditabilidade: os sistemas CRUD armazenam apenas o estado mais recente dos dados. A menos que haja um mecanismo de auditoria que registre os detalhes de cada operação em um log separado, o histórico é perdido.
Solução
O padrão de Origem do Evento define uma abordagem para o processamento de operações nos dados condicionados por uma sequência de eventos, cada um dos quais é registado num arquivo só de acréscimo. O código do aplicativo gera eventos que descrevem imperativamente a ação executada no objeto. Os eventos geralmente são enviados para uma fila onde um processo separado, um manipulador de eventos, escuta a fila e persiste os eventos em um repositório de eventos. Cada evento representa uma alteração lógica no objeto, como AddedItemToOrder
ou OrderCanceled
.
Os eventos são mantidos num arquivo de eventos que funciona como o sistema de registos (a origem de dados autoritativa) do estado atual dos dados. Manipuladores de eventos adicionais podem ouvir os eventos em que estão interessados e tomar uma ação apropriada. Os consumidores poderão, por exemplo, iniciar tarefas que aplicam as operações dos eventos a outros sistemas ou executar qualquer outra ação associada necessária para concluir a operação. Tenha em atenção que o código da aplicação que gera os eventos é desacoplado dos sistemas que subscrevem os eventos.
A qualquer momento, é possível que os aplicativos leiam o histórico de eventos. Em seguida, você pode usar os eventos para materializar o estado atual de uma entidade reproduzindo e consumindo todos os eventos relacionados a essa entidade. Esse processo pode ocorrer sob demanda para materializar um objeto de domínio ao lidar com uma solicitação.
Como é relativamente caro ler e reproduzir eventos, os aplicativos normalmente implementam exibições materializadas, projeções somente leitura do repositório de eventos que são otimizadas para consulta. Por exemplo, um sistema pode manter uma exibição materializada de todos os pedidos de clientes usados para preencher a interface do usuário. À medida que o aplicativo adiciona novos pedidos, adiciona ou remove itens no pedido, ou adiciona informações de envio, os eventos são gerados e um manipulador atualiza a exibição materializada.
A figura mostra uma visão geral do padrão, incluindo algumas implementações típicas com o padrão, incluindo o uso de uma fila, um armazenamento somente leitura, integração de eventos com aplicativos e sistemas externos e reprodução de eventos para criar projeções do estado atual de entidades específicas.
Fluxo de trabalho
A seguir descrevemos um fluxo de trabalho típico para esse padrão:
- A camada de apresentação chama um objeto responsável pela leitura de um repositório somente leitura. Os dados retornados são usados para preencher a interface do usuário.
- A camada de apresentação chama manipuladores de comando para executar ações como criar um carrinho ou adicionar um item ao carrinho.
- O manipulador de comandos chama o repositório de eventos para obter os eventos históricos para a entidade. Por exemplo, ele pode recuperar todos os eventos de carrinho. Esses eventos são reproduzidos no objeto para materializar o estado atual da entidade, antes de qualquer ação ocorrer.
- A lógica de negócios é executada e os eventos são gerados. Na maioria das implementações, os eventos são enviados para uma fila ou tópico para separar os produtores e consumidores de eventos.
- Os manipuladores de eventos escutam os eventos em que estão interessados e executam a ação apropriada para esse manipulador. Algumas ações típicas do manipulador de eventos são:
- Escrever os eventos na loja de eventos
- Atualizando um repositório somente leitura otimizado para consultas
- Integração com sistemas externos
Vantagens do padrão
O padrão de Origem do Evento fornece as seguintes vantagens:
Os eventos são imutáveis e podem ser armazenados com uma operação só de acréscimo. A interface de utilizador, o fluxo de trabalho ou o processo que iniciou um evento pode continuar e as tarefas que processam os eventos podem ser executadas em segundo plano. Esse processo, combinado com o fato de que não há contenção durante o processamento de transações, pode melhorar muito o desempenho e a escalabilidade para aplicativos, especialmente para a camada de apresentação.
Os eventos são objetos simples que descrevem alguma ação que ocorreu, juntamente com quaisquer dados associados necessários para descrever a ação representada pelo evento. Os eventos não atualizam diretamente um arquivo de dados. Estes são simplesmente registados para processamento no momento adequado. O uso de eventos pode simplificar a implementação e o gerenciamento.
Normalmente, os eventos têm um significado para um especialista de domínio, enquanto os erros de impedância de objeto relacional podem tornar as tabelas de base de dados complexas difíceis de compreender. As tabelas são construções artificiais que representam o estado atual do sistema, não os eventos que ocorreram.
A origem do evento pode ajudar a impedir que atualizações em simultâneo criem conflitos, uma vez que evita a necessidade de atualizar os objetos diretamente no arquivo de dados. No entanto, o modelo do domínio ainda tem de ser criado para se proteger de pedidos que possam resultar num estado inconsistente.
O armazenamento somente acréscimo de eventos fornece uma trilha de auditoria que pode ser usada para monitorar ações executadas em um armazenamento de dados. Ele pode regenerar o estado atual como visualizações ou projeções materializadas, reproduzindo os eventos a qualquer momento, e pode ajudar no teste e depuração do sistema. Além disso, o requisito de usar eventos de compensação para cancelar alterações pode fornecer um histórico de alterações que foram revertidas. Esse recurso não seria o caso se o modelo armazenasse o estado atual. A lista de eventos também pode ser usada para analisar o desempenho do aplicativo e detetar tendências de comportamento do usuário. Ou, ele pode ser usado para obter outras informações comerciais úteis.
Os manipuladores de comando geram eventos e as tarefas executam operações em resposta a esses eventos. Este desacoplamento das tarefas dos eventos fornece flexibilidade e extensibilidade. As tarefas conhecem o tipo de evento e os dados do mesmo, mas não conhecem a operação que acionou o evento. Além disso, várias tarefas podem processar o mesmo evento. Este procedimento permite uma integração fácil com outros serviços e sistemas que apenas estão à escuta de novos eventos gerados pelo arquivo de eventos. No entanto, os eventos da origem do evento tendem a possuir um nível muito baixo, pelo que pode ser necessário gerar eventos de integração específicos.
O fornecimento de eventos é normalmente combinado com o padrão CQRS executando as tarefas de gerenciamento de dados em resposta aos eventos e materializando exibições dos eventos armazenados.
Problemas e considerações
Na altura de decidir como implementar este padrão, considere os seguintes pontos:
Eventual consistência - O sistema só será eventualmente consistente ao criar visualizações materializadas ou gerar projeções de dados através da repetição de eventos. Há algum atraso entre um aplicativo adicionar eventos ao repositório de eventos como resultado do tratamento de uma solicitação, os eventos que estão sendo publicados e os consumidores dos eventos que os manipulam. Durante este período, novos eventos que descrevem mais alterações nas entidades podem ter chegado ao arquivo de eventos. Seus clientes devem estar bem com o fato de que os dados são eventualmente consistentes e o sistema deve ser projetado para levar em conta a eventual consistência nesses cenários.
Nota
Veja o Data Consistency Primer (Manual Básico sobre a Consistência de Dados) para obter informações sobre a consistência eventual.
Eventos de controle de versão - O armazenamento de eventos é a fonte permanente de informações e, portanto, os dados do evento nunca devem ser atualizados. A única maneira de atualizar uma entidade ou desfazer uma alteração é adicionar um evento de compensação ao repositório de eventos. Se o esquema (em vez dos dados) dos eventos persistentes precisar ser alterado, talvez durante uma migração, pode ser difícil combinar eventos existentes no repositório com a nova versão. Seu aplicativo precisará suportar alterações em estruturas de eventos. Isso pode ser feito de várias maneiras.
- Certifique-se de que seus manipuladores de eventos ofereçam suporte a todas as versões de eventos. Isso pode ser um desafio para manter e testar. Isso requer a implementação de um carimbo de versão em cada versão do esquema de evento para manter os formatos de evento antigo e novo.
- Implemente um manipulador de eventos para manipular versões de eventos específicas. Isso pode ser um desafio de manutenção, pois as alterações de correção de bugs podem ter que ser feitas em vários manipuladores. Isso requer a implementação de um carimbo de versão em cada versão do esquema de evento para manter os formatos de evento antigo e novo.
- Atualize os eventos históricos para o novo esquema quando um novo esquema for implementado. Isso quebra a imutabilidade dos acontecimentos.
Ordenação de eventos - Aplicativos multi-threaded e várias instâncias de aplicativos podem estar armazenando eventos no repositório de eventos. A consistência dos eventos no arquivo de eventos é vital, tal como a ordem dos eventos que afetam uma entidade específica (a ordem em que ocorrem as alterações numa entidade afeta o seu estado atual). Adicionar um carimbo de data/hora a cada evento pode ajudar a evitar problemas. Outra prática comum consiste em anotar cada evento resultante de um pedido com um identificador incremental. Se duas ações tentarem adicionar eventos à mesma entidade em simultâneo, o arquivo de eventos poderá rejeitar um evento que corresponda a um identificador da entidade e a um identificador do evento existentes.
Consultando eventos - Não há uma abordagem padrão, ou mecanismos existentes, como consultas SQL, para ler os eventos para obter informações. Os únicos dados que podem ser extraídos são o fluxo de eventos com um identificador de evento como critério. O ID do evento normalmente mapeia para entidades individuais. O estado atual de uma entidade só pode ser determinado com a reprodução de todos os eventos relacionados com o mesmo em relação ao estado original dessa entidade.
Custo de recriação do estado para entidades - A duração de cada fluxo de eventos afeta o gerenciamento e a atualização do sistema. Se os fluxos forem grandes, considere criar instantâneos em intervalos específicos, tal como um número específico de eventos. Pode obter o estado atual da entidade através do instantâneo e ao reproduzir quaisquer eventos ocorridos após esse ponto no tempo. Para obter mais informações sobre como criar instantâneos de dados, consulte Replicação de instantâneo subordinado primário.
Conflitos - Embora o fornecimento de eventos minimize a chance de atualizações conflitantes dos dados, o aplicativo ainda deve ser capaz de lidar com inconsistências resultantes de uma eventual consistência e da falta de transações. Por exemplo, um evento que indica uma redução no estoque de estoque pode chegar ao armazenamento de dados enquanto um pedido para esse item está sendo feito. Esta situação resulta na necessidade de conciliar as duas operações, quer aconselhando o cliente, quer criando uma encomenda em atraso.
Necessidade de idempotência - A publicação de eventos pode ser pelo menos uma vez e, portanto, os consumidores dos eventos devem ser idempotentes. Não poderão voltar a aplicar a atualização descrita num evento se este for processado mais do que uma vez. Várias instâncias de um consumidor podem manter e agregar a propriedade de uma entidade, como o número total de pedidos feitos. Apenas um deve ter sucesso em incrementar o agregado, quando ocorre um evento de ordem colocada. Embora esse resultado não seja uma característica fundamental do fornecimento de eventos, é a decisão de implementação usual.
Lógica circular - Esteja atento aos cenários em que o processamento de um evento envolve a criação de um ou mais novos eventos, uma vez que isso pode causar um loop infinito.
Quando utilizar este padrão
Utilize este padrão nos seguintes cenários:
Quando pretende capturar a intenção, o objetivo ou a razão nos dados. Por exemplo, as alterações em uma entidade de cliente podem ser capturadas como uma série de tipos de eventos específicos, como Casa transferida, Conta fechada ou Falecido.
Quando é fundamental minimizar ou evitar completamente a ocorrência de conflitos de atualizações nos dados.
Quando quiser registrar eventos que ocorrem, reproduzi-los para restaurar o estado de um sistema, reverter alterações ou manter um histórico e um log de auditoria. Por exemplo, quando uma tarefa envolve várias etapas, talvez seja necessário executar ações para reverter atualizações e, em seguida, repetir algumas etapas para trazer os dados de volta a um estado consistente.
Quando você usa eventos. É uma característica natural da operação do aplicativo e requer pouco esforço extra de desenvolvimento ou implementação.
Quando você precisa dissociar o processo de entrada ou atualização de dados das tarefas necessárias para aplicar essas ações. Essa alteração pode ser para melhorar o desempenho da interface do usuário ou para distribuir eventos para outros ouvintes que tomam medidas quando os eventos ocorrem. Por exemplo, você pode integrar um sistema de folha de pagamento com um site de envio de despesas. Os eventos que são levantados pela loja de eventos em resposta a atualizações de dados feitas no site seriam consumidos tanto pelo site quanto pelo sistema de folha de pagamento.
Quando você deseja flexibilidade para poder alterar o formato de modelos materializados e dados de entidade se os requisitos mudarem, ou — quando usado com CQRS — você precisa adaptar um modelo de leitura ou as exibições que expõem os dados.
Quando usado com CQRS, e eventual consistência é aceitável enquanto um modelo de leitura é atualizado, ou o impacto no desempenho de entidades reidratantes e dados de um fluxo de eventos é aceitável.
Este padrão pode não ser prático nas seguintes situações:
Aplicativos que não exigem hiperescala ou desempenho.
Domínios pequenos ou simples, sistemas com pouca ou nenhuma lógica de negócio ou sistemas sem domínios que naturalmente funcionam bem com mecanismos de gestão de dados CRUD tradicionais.
Sistemas em que a consistência e as atualizações em tempo real das vistas dos dados não são necessárias.
Sistemas em que há apenas uma baixa ocorrência de atualizações conflitantes para os dados subjacentes. Por exemplo, os sistemas que predominantemente adicionam dados em vez de os atualizar.
Design da carga de trabalho
Um arquiteto deve avaliar como o padrão de Fornecimento de Eventos pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:
Pilar | Como esse padrão suporta os objetivos do pilar |
---|---|
As decisões de projeto de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | Devido à captura de um histórico de mudanças em processos de negócios complexos, ele pode facilitar a reconstrução do estado se você precisar recuperar lojas estatais. - RE:06 Particionamento de dados - RE:09 Recuperação de desastres |
A Eficiência de Desempenho ajuda sua carga de trabalho a atender às demandas de forma eficiente por meio de otimizações em escala, dados e código. | Esse padrão, geralmente combinado com CQRS, um design de domínio apropriado e instantâneo estratégico, pode melhorar o desempenho da carga de trabalho devido às operações somente acréscimo atômico e evitar o bloqueio do banco de dados para gravações e leituras. - PE:08 Desempenho dos dados |
Como em qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com esse padrão.
Exemplo
Um sistema de gerenciamento de conferência precisa rastrear o número de reservas concluídas para uma conferência. Desta forma, pode verificar se ainda há lugares disponíveis, quando um potencial participante tenta fazer uma reserva. O sistema pode armazenar o número total de reservas numa conferência de, pelo menos, duas formas:
O sistema pode armazenar as informações sobre o número total de reservas como uma entidade separada numa base de dados que contém informações de reservas. À medida que as reservas são feitas ou canceladas, o sistema pode incrementar ou diminuir este número conforme apropriado. Na teoria, esta abordagem é simples, mas poderá causar problemas de escalabilidade se um grande número de participantes tentar reservar lugares durante um curto período de tempo. Por exemplo, no último dia ou antes do fecho de período de reserva.
O sistema poderá armazenar informações sobre as reservas e os cancelamentos como eventos contidos num arquivo de eventos. Este pode, em seguida, calcular o número de lugares disponíveis ao reproduzir esses eventos. Esta abordagem pode ser mais dimensionável devido à imutabilidade dos eventos. O sistema só precisa de conseguir ler os dados do arquivo de eventos ou anexar dados ao arquivo de eventos. As informações do evento referentes às reservas e aos cancelamentos nunca são modificadas.
O diagrama seguinte ilustra a forma como o subsistema de reserva de lugares do sistema de gestão de conferências pode ser implementado com a origem do evento.
A sequência de ações para reservar dois lugares é a seguinte:
A interface de utilizador emite um comando para reservar lugares para dois participantes. O comando é processado por um processador de comandos separado. A lógica que é desacoplada da interface de utilizador e é responsável por processar pedidos publicados como comandos.
Uma entidade contendo informações sobre todas as reservas para a conferência é construída consultando os eventos que descrevem reservas e cancelamentos. Essa entidade é chamada
SeatAvailability
de , e está contida em um modelo de domínio que expõe métodos para consultar e modificar os dados na entidade.Algumas otimizações a serem consideradas são o uso de instantâneos (para que você não precise consultar e reproduzir a lista completa de eventos para obter o estado atual da entidade) e a manutenção de uma cópia em cache da entidade na memória.
O processador de comandos invoca um método exposto pelo modelo de domínio para fazer as reservas.
A
SeatAvailability
entidade levanta um evento contendo o número de lugares que foram reservados. Da próxima vez que a entidade aplicar eventos, todas as reservas serão usadas para calcular quantos lugares restam.O sistema anexa o novo evento à lista de eventos no arquivo de eventos.
Se um utilizador cancelar um lugar, o sistema seguirá um processo semelhante, a menos que o processador de comandos emita um comando que gere um evento de cancelamento de lugares e o anexe ao arquivo de eventos.
Além de fornecer mais espaço para escalabilidade, o uso de uma loja de eventos também fornece um histórico completo, ou trilha de auditoria, das reservas e cancelamentos de uma conferência. Os eventos no arquivo de eventos são um registo preciso. Não há necessidade de persistir agregações de qualquer outra forma, porque o sistema pode facilmente reproduzir os eventos e restaurar o estado para qualquer ponto no tempo.
Próximos passos
Manual Básico de Consistência de Dados. Quando você usa o fornecimento de eventos com um armazenamento de leitura separado ou exibições materializadas, os dados de leitura não serão imediatamente consistentes. Em vez disso, os dados só serão eventualmente consistentes. Este artigo resume os problemas relacionados à manutenção da consistência sobre dados distribuídos.
Orientações sobre a Criação de Partições de Dados. Os dados geralmente são particionados quando você usa o fornecimento de eventos para melhorar a escalabilidade, reduzir a contenção e otimizar o desempenho. Este artigo descreve como dividir dados em partições discretas e os problemas que podem surgir.
Blog de Martin Fowler:
Recursos relacionados
Os padrões e orientações que se seguem também podem ser relevantes ao implementar este padrão:
Padrão de Segregação das Responsabilidades de Comando e de Consulta (CQRS). O arquivo de escrita que fornece a origem de informações permanente para uma implementação do CQRS baseia-se, muitas vezes, numa implementação do padrão de Origem do Evento. Descreve como segregar as operações de leitura de dados numa aplicação a partir de operações que atualizam dados através de interfaces separadas.
Padrão de Vista Materializada. O armazenamento de dados usado em um sistema baseado no fornecimento de eventos normalmente não é adequado para consultas eficientes. Em vez disso, uma abordagem comum consiste em gerar vistas pré-preenchida dos dados em intervalos regulares ou quando os dados mudam.
Padrão Transação de Compensação. Os dados existentes em um repositório de fornecimento de eventos não são atualizados. Em vez disso, são adicionadas novas entradas que fazem a transição do estado das entidades para os novos valores. Para reverter uma alteração, as entradas de compensação são usadas porque não é possível reverter a alteração anterior. Descreve como anular o trabalho realizado por uma operação anterior.