Compartilhar via


Padrão de fornecimento do evento

Azure

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 repositório atua como o sistema de registro e pode ser usado para materializar os objetos de 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 apresenta compensações para obter maior desempenho, escalabilidade e auditabilidade. Depois que o sistema se torna um sistema de fornecimento de eventos, todas as futuras decisões de design são restringidas pelo fato de que este é um sistema de fornecimento de eventos. Há um alto custo para migrar de ou para um sistema de fornecimento de eventos. Esse padrão é mais adequado para sistemas em que o desempenho e a escalabilidade são os principais requisitos. A complexidade que o fornecimento de eventos adiciona a um sistema não é justificada 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 crud (criação, leitura, atualização e exclusão) tradicional, um processo de dados típico é ler dados do repositório, 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, essa abordagem tem alguns desafios:

  • Desempenho: conforme o sistema é dimensionado, o desempenho será prejudicado 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 será perdido.

Solução

O padrão de fornecimento do evento define uma abordagem para lidar com operações em dados que são controladas por uma sequência de eventos, cada um dos quais é registrado em um repositório somente 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 em que 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 persistidos em um repositório de eventos que atua como o sistema de registro (fonte de dados autoritativa) sobre o estado atual dos dados. Manipuladores de eventos adicionais podem escutar eventos nos quais estão interessados e tomar uma ação apropriada. Os consumidores podem, por exemplo, iniciar tarefas que aplicam as operações nos eventos a outros sistemas ou então executar qualquer ação associada necessária para a conclusão da operação. Observe que o código do aplicativo que gera os eventos é separado dos sistemas que assinam 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. Este processo pode ocorrer sob demanda para materializar um objeto de domínio ao tratar 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 otimizados 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 repositório somente leitura, a integração de eventos com aplicativos e sistemas externos e a reprodução de eventos para criar projeções do estado atual de entidades específicas.

Uma visão geral e um exemplo do padrão de fornecimento do evento

Fluxo de Trabalho

O seguinte descreve um fluxo de trabalho típico para este padrão:

  1. 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.
  2. A camada de apresentação chama manipuladores de comando para executar ações como criar um carrinho ou adicionar um item ao carrinho.
  3. O manipulador de comandos chama o repositório de eventos para obter os eventos históricos da entidade. Por exemplo, ele pode recuperar todos os eventos do carrinho. Esses eventos são reproduzidos no objeto para materializar o estado atual da entidade, antes de qualquer ação ocorrer.
  4. A lógica de negócios é executada e os eventos são gerados. Na maioria das implementações, os eventos são enviados por push para uma fila ou tópico para desacoplar os produtores de eventos e os consumidores de eventos.
  5. Os manipuladores de eventos escutam eventos nos quais estão interessados e executam a ação apropriada para esse manipulador. Algumas ações típicas do manipulador de eventos são:
    1. Gravando os eventos no repositório de eventos
    2. Atualizando um repositório somente leitura otimizado para consultas
    3. Integração com sistemas externos

Vantagens de padrão

O padrão de fornecimento do evento fornece as seguintes vantagens:

  • Eventos são imutáveis e podem ser armazenados usando uma operação somente de acréscimo. A interface do usuário, um fluxo de trabalho ou um processo que iniciou um evento pode continuar e tarefas que manipulam 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 consideravelmente o desempenho e a escalabilidade dos aplicativos, especialmente para a camada de apresentação.

  • Os eventos são objetos simples que descrevem uma ação que ocorreu, juntamente com quaisquer dados associados necessários para descrever a ação representada pelo evento. Eventos não atualizam um armazenamento de dados diretamente. Eles simplesmente são registrados para manipulação no momento apropriado. O uso de eventos pode simplificar a implementação e o gerenciamento.

  • Eventos geralmente têm significado para um especialista de domínio, enquanto a incompatibilidade de impedância relacional de objeto pode tornar as tabelas de banco de dados complexas difíceis de entender. As tabelas são construções artificiais que representam o estado atual do sistema, não os eventos que ocorreram.

  • O fornecimento do evento pode ajudar a evitar que atualizações simultâneas causem conflitos porque ele evita a necessidade de atualizar diretamente os objetos no armazenamento de dados. No entanto, o modelo de domínio ainda deve ser criado para se proteger de solicitações que podem resultar em um estado inconsistente.

  • O armazenamento de eventos somente anexado 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 exibições ou projeções materializadas, reproduzindo os eventos a qualquer momento, e pode auxiliar no teste e na depuração do sistema. Além disso, a exigência de utilização de eventos compensatórios para cancelar alterações pode fornecer um histórico de alterações que foram revertidas. Essa capacidade 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 detectar tendências de comportamento do usuário. Ou 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. Desassociar as tarefas dos eventos desse modo fornece flexibilidade e extensibilidade. As tarefas sabem sobre o tipo de evento e os dados de evento, mas não sobre a operação que disparou o evento. Além disso, múltiplas tarefas podem manipular cada evento. Isso permite uma integração fácil com outros serviços e sistemas que escutam somente novos eventos acionados pelo repositório de eventos. No entanto, os eventos de fornecimento do evento tendem a ser de nível muito baixo e pode ser necessário gerar eventos de integração específicos em vez deles.

O fornecimento de eventos geralmente é 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

Considere os seguintes pontos ao decidir como implementar esse padrão:

  • Consistência eventual – o sistema só será eventualmente consistente ao criar exibições materializadas ou gerar projeções de dados reproduzindo eventos. Há algum atraso entre um aplicativo adicionando eventos para o repositório de eventos como resultado da manipulação de uma solicitação, da publicação de eventos e da manipulação dos eventos pelos respectivos consumidores. Durante esse período, novos eventos que descrevem ainda mais as alterações nas entidades podem ter chegado ao repositório 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 consistência eventual nesses cenários.

    Observação

    Veja o Data Consistency Primer para obter informações sobre consistência eventual.

  • Eventos de controle de versão – O repositório 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, poderá ser difícil combinar eventos existentes no repositório com a nova versão. Seu aplicativo precisará dar suporte a alterações nas estruturas de eventos. Isso pode ser feito de várias maneiras.

    • Verifique se os manipuladores de eventos dão 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 eventos para manter os formatos de evento antigos e novos.
    • Implemente um manipulador de eventos para lidar com versões de evento específicas. Isso pode ser um desafio de manutenção, porque 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 eventos para manter os formatos de evento antigos e novos.
    • Atualize eventos históricos para o novo esquema quando um novo esquema for implementado. Isso interrompe a imutabilidade dos eventos.
  • Ordenação de eventos – aplicativos com vários threads e várias instâncias de aplicativos podem estar armazenando eventos no repositório de eventos. A consistência dos eventos no repositório de eventos é essencial, assim como a ordem de eventos que afetam uma entidade específica (a ordem em que ocorrem alterações a uma entidade afeta seu estado atual). Adicionar um carimbo de data/hora a cada evento pode ajudar a evitar problemas. Outra prática comum é anotar cada evento resultante de uma solicitação com um identificador incremental. Se duas ações tentam adicionar eventos à mesma entidade simultaneamente, o repositório de eventos pode rejeitar um evento que corresponde um identificador de entidade e um identificador de evento existentes.

  • Consultando eventos – não há nenhuma 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 um fluxo de eventos usando um identificador de evento como o critério. Normalmente, a ID do evento é mapeada para entidades individuais. O estado atual de uma entidade pode ser determinado somente reproduzindo-se todos os eventos relacionados a ela mediante o estado original da entidade.

  • Custo da recriação do estado para entidades – o comprimento de cada fluxo de eventos afeta o gerenciamento e a atualização do sistema. Se os fluxos forem grandes, considere a criação de instantâneos em intervalos específicos como um número de eventos especificado. O estado atual da entidade pode ser obtido do instantâneo e repetindo-se todos os eventos que ocorreram depois desse ponto no tempo. Para obter mais informações sobre como criar instantâneos de dados, confira Replicação de instantâneo primário-subordinado.

  • Conflitos – embora o fornecimento de eventos minimize a chance de atualizações conflitantes para os dados, o aplicativo ainda deve ser capaz de lidar com inconsistências resultantes da consistência eventual e da falta de transações. Por exemplo, um evento que indica uma redução no estoque pode chegar ao armazenamento de dados enquanto um pedido desse item está sendo feito. Essa situação resulta na necessidade de conciliar as duas operações, seja por meio de aviso ao cliente ou criação de uma ordem pendente.

  • Necessidade de idempotency – a publicação de eventos pode ser pelo menos uma vez e, portanto, os consumidores dos eventos devem ser idempotentes. Eles não devem aplicar a atualização descrita em um evento se o evento é manipulado mais de uma vez. Várias instâncias de um consumidor podem manter e agregar propriedades de uma entidade, como o número total de pedidos feitos. Apenas uma deve conseguir incrementar o agregado, quando ocorre um evento de ordem realizada. Embora esse resultado não seja uma característica-chave do fornecimento do evento, é a decisão de implementação mais comum.

  • Lógica circular – esteja atento a cenários em que o processamento de um evento envolve a criação de um ou mais eventos novos, pois isso pode causar um loop infinito.

Quando usar esse padrão

Use esse padrão nos cenários a seguir:

  • Quando você deseja capturar a intenção, o objetivo ou o motivo nos dados. Por exemplo, as alterações para uma entidade cliente poderão ser capturadas como uma série de tipos de evento específicos como Mudança de residência, Conta fechada ou Falecimento.

  • Quando é minimizar ou evitar completamente a ocorrência de atualizações conflitantes aos dados.

  • Quando você deseja registrar eventos que ocorrem, reproduzi-los para restaurar o estado de um sistema, reverter alterações ou manter um histórico e log de auditoria. Por exemplo, quando uma tarefa envolve várias etapas você precisa executar ações para reverter as atualizações e, em seguida, repetir algumas etapas para trazer os dados de volta para um estado consistente.

  • Quando você usa eventos. É um recurso natural da operação do aplicativo e requer pouco esforço extra de desenvolvimento ou implementação.

  • Quando você precisa desassociar o processo de inserção ou atualização de dados das tarefas necessárias para aplicar essas ações. Essa alteração pode ser para melhorar o desempenho de interface do usuário ou para distribuir eventos em outros ouvintes que entram em ação quando os eventos ocorrem. Por exemplo, você pode integrar um sistema de folha de pagamento a um site de envio de despesas. Os eventos gerados pelo armazenamento de eventos em resposta às atualizações de dados feitas no site seriam consumidos tanto pelo site quanto pelo sistema de folha de pagamento.

  • Quando você quiser flexibilidade para poder alterar o formato dos modelos materializados e dados de entidade se os requisitos sofrerem alterações, ou — quando usado com CQRS — será necessário adaptar um modelo de leitura ou os modos de exibição que expõem os dados.

  • Quando usado com CQRS e consistência eventual é aceitável enquanto um modelo de leitura é atualizado ou então quando o impacto no desempenho causado pela reidratação de entidades e dados de um fluxo de eventos é aceitável.

Esse padrão pode não ser útil nas seguintes situações:

  • Aplicativos que não exigem hiperescalização ou desempenho.

  • Domínios pequenos ou simples, sistemas que têm pouca ou nenhuma lógica de negócios ou sistemas que não são de domínio que funcionam naturalmente bem com mecanismos tradicionais de gerenciamento de dados CRUD.

  • Sistemas em que a consistência e atualizações em tempo real para as exibições dos dados são necessárias.

  • Sistemas em que há somente uma ocorrência baixa de atualizações conflitantes nos dados subjacentes. Por exemplo, os sistemas que predominantemente adicionam dados em vez de atualizá-los.

Design de carga de trabalho

Um arquiteto deve avaliar como o padrão de Fornecimento do Evento pode ser usado no design das suas cargas de trabalho para abordar os objetivos e os princípios discutidos nos pilares do Azure Well-Architected Framework. Por exemplo:

Pilar Como esse padrão apoia os objetivos do pilar
As decisões de design 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, pode facilitar a reconstrução do estado se for necessário recuperar depósitos do estado.

- Particionamento de dados RE:06
- RE:09 Recuperação de desastre
A eficiência de desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações em dimensionamento, dados e código. Esse padrão, geralmente combinado com CQRS, um design de domínio apropriado e captura estratégica de instantâneos, pode melhorar o desempenho da carga de trabalho devido às operações somente de acréscimo atômico e à prevenção do bloqueio do banco de dados para gravações e leituras.

- PE:08 Desempenho de dados

Tal como acontece com qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com este padrão.

Exemplo

Um sistema de gerenciamento de conferências precisa rastrear o número de reservas concluídas para uma conferência. Dessa forma, pode verificar se ainda há lugares disponíveis, quando um potencial participante tenta fazer uma reserva. O sistema poderia armazenar o número total de reservas para uma conferência de pelo menos duas maneiras:

  • O sistema pode armazenar as informações sobre o número total de reservas como uma entidade separada em um banco de dados que contém as informações de reserva. Conforme as reservas são feitas ou canceladas, o sistema pode incrementar ou decrementar esse número conforme apropriado. Essa abordagem é simples em teoria, mas pode causar problemas de escalabilidade se um grande número de participantes está tentando reservar lugares durante um curto período de tempo. Por exemplo, no último dia antes do encerramento do período de reservas.

  • O sistema poderia armazenar informações sobre reservas e cancelamentos como eventos mantidos em um repositório de eventos. Ele poderia então calcular o número de lugares disponíveis reproduzindo esses eventos. Essa abordagem pode ser mais escalonável devido à imutabilidade dos eventos. O sistema só precisa ser capaz de ler dados do repositório de eventos ou acrescentar dados ao repositório de eventos. As informações de evento sobre reservas e cancelamentos nunca são modificadas.

O diagrama a seguir ilustra como o subsistema de reserva de lugares do sistema de gerenciamento da conferência pode ser implementado usando o fornecimento do evento.

Usando o fornecimento do evento para capturar informações sobre reservas de lugares em um sistema de gerenciamento de conferência

A sequência de ações para reservar dois lugares é conforme descrito a seguir:

  1. A interface do usuário emite um comando para reservar lugares para dois participantes. O comando é manipulado por um manipulador de comandos separado. Uma parte da lógica que é separada da interface do usuário e é responsável por gerenciar solicitações lançadas como comandos.

  2. Uma entidade que contém informações sobre todas as reservas da conferência é construída consultando os eventos que descrevem reservas e cancelamentos. Essa entidade é chamada SeatAvailabilitye 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 manter uma cópia armazenada em cache da entidade na memória.

  3. O manipulador de comandos invoca um método exposto pelo modelo de domínio para fazer as reservas.

  4. A SeatAvailability entidade gera um evento que contém o número de assentos reservados. Na próxima vez que a entidade aplicar eventos, todas as reservas serão usadas para calcular quantos assentos permanecem.

  5. O sistema acrescenta o novo evento à lista de eventos no repositório de eventos.

Se um usuário cancela a reserva de um lugar, o sistema segue um processo semelhante, exceto pelo fato de que o manipulador de comandos emite um comando que gera um evento de cancelamento de lugar e o anexa ao repositório de eventos.

Além de fornecer mais escopo para escalabilidade, usar um repositório de eventos também fornece um histórico completo ou log de auditoria das reservas e cancelamentos para uma conferência. Os eventos no repositório de eventos são o registro preciso. Não é necessário 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óximas etapas

Os padrões e diretrizes a seguir também podem ser relevantes ao implementar esse padrão:

  • Padrão de comando e segregação de responsabilidade de consulta (CQRS). O repositório de gravação que fornece a origem permanente das informações de uma implementação de CQRS é normalmente baseado em uma implementação do padrão do fornecimento do evento. Descreve como separar as operações que leem dados em um aplicativo das operações que atualizam dados usando interfaces separadas.

  • Padrão de Exibição Materializada. O armazenamento de dados usado em um sistema baseado em evento de fornecimento normalmente não é adequado para consultar de modo eficiente. Em vez disso, uma abordagem comum é gerar previamente exibições dos dados em intervalos regulares ou quando os dados são alterados.

  • Padrão de Transação de Compensação. Os dados existentes em uma loja 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 desfazer o trabalho realizado por uma operação anterior.