Em vez de armazenar apenas o estado atual dos dados num domínio, utilize um arquivo só de acréscimo para registar a série completa das ações realizadas nos dados. O arquivo funciona como o sistema de registos e pode ser utilizado para materializar os objetos do domínio. Tal pode simplificar as tarefas em domínios complexos ao evitar a necessidade de sincronizar o modelo de dados e o domínio empresarial, enquanto melhora o desempenho, a escalabilidade e a capacidade de resposta. Pode também fornecer consistência para os dados transacionais e manter registos de auditoria completos e um histórico que pode permitir ações de compensação.
Contexto e problema
A maioria das aplicações trabalham com dados e a abordagem típica consiste na aplicação em manter o estado atual dos dados ao atualizá-los à medida que os utilizadores trabalham neles. 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 tem algumas limitações:
Os sistemas CRUD executam operações de atualização diretamente em um armazenamento de dados. Essas operações podem diminuir o desempenho e a capacidade de resposta e podem limitar a escalabilidade, devido à sobrecarga de processamento necessária.
Num domínio de colaboração com muitos utilizadores em simultâneo, existe uma maior probabilidade de ocorrência de conflitos de atualização de dados, uma vez que as operações de atualização ocorrem num único item de dados.
A menos que haja outro 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 da aplicação envia uma série de eventos que descrevem imperativamente cada ação ocorrida nos dados para o arquivo de eventos, no qual permanecem. Cada evento representa um conjunto de alterações aos dados (tal como AddedItemToOrder
).
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. Normalmente, o arquivo de eventos publica estes eventos para que os consumidores possam ser notificados e possam processá-los, se necessário. 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.
As utilizações típicas dos eventos publicados pelo arquivo de eventos consistem na manutenção das vistas materializadas das entidades, à medida que as ações na aplicação as alteram, e na integração com sistemas externos. Por exemplo, um sistema pode manter uma vista materializada de todas as encomendas dos clientes utilizadas para preencher partes da IU. O aplicativo adiciona novos pedidos, adiciona ou remove itens no pedido e adiciona informações de envio. Os eventos que descrevem essas alterações podem ser manipulados e usados para atualizar a exibição materializada.
A qualquer momento, é possível que os aplicativos leiam o histórico de eventos. Em seguida, você pode usá-lo 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. Ou, o processo ocorre através de uma tarefa agendada para que o estado da entidade possa ser armazenado como uma exibição materializada, para suportar a camada de apresentação.
A figura mostra uma descrição geral do padrão, incluindo algumas das opções para utilizar a transmissão em fluxo do evento, tal como criar uma vista materializada, integrar eventos com aplicações e sistemas externos e reproduzir eventos para criar projeções do estado atual de entidades específicas.
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 o nível de apresentação ou interface do usuário.
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.
O arquivo de eventos cria eventos e as tarefas realizam 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.
A origem do evento é normalmente combinada com o padrão CQRS ao realizar tarefas de gestão de dados em resposta aos eventos e ao materializar vistas a partir dos eventos armazenados.
Problemas e considerações
Na altura de decidir como implementar este padrão, considere os seguintes pontos:
O sistema só pode ser eventualmente consistente quando criar vistas materializadas ou gerar projeções de dados ao substituir 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. O sistema deve ser concebido de modo a ter em conta a eventual coerência nestes 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.
O arquivo de eventos é a origem de informações permanente e, por isso, os dados do evento nunca devem ser atualizados. A única forma de atualizar uma entidade para anular uma alteração consiste em adicionar um evento de compensação ao arquivo de eventos. Se for preciso alterar o formato (ao invés dos dados) dos eventos persistentes, durante uma migração por exemplo, poderá ser difícil combinar eventos existentes no arquivo com a nova versão. Pode ser necessário iterar todos os eventos a realizarem alterações para que estejam em conformidade com o novo formato ou adicionar novos eventos que utilizam o novo formato. Considere utilizar um carimbo de versão em cada versão do esquema de eventos para manter tanto o antigo como o novo formato de eventos.
As aplicações de vários threads e várias instâncias das aplicações podem estar a armazenar eventos no arquivo 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.
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 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.
O comprimento de cada fluxo de eventos afeta a gestão 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.
Apesar de a origem do evento minimizar a possibilidade de conflitos de atualizações com os dados, a aplicação deve ter a capacidade 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 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.
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.
O armazenamento de eventos selecionado precisa suportar a carga de eventos gerada pelo seu aplicativo.
Esteja atento aos cenários em que o processamento de um evento envolve a criação de um ou mais novos eventos, pois 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:
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 trilhas de auditoria, histórico e recursos para reverter e repetir ações não são necessários.
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.
Um agregado que contém informações sobre todas as reservas para a conferência é construído ao consultar os eventos que descrevem as reservas e os cancelamentos. Este agregado é chamado
SeatAvailability
e está contido num modelo de domínio que expõe métodos para consultar e modificar os dados no agregado.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 agregação) e a manutenção de uma cópia em cache da agregação na memória.
O processador de comandos invoca um método exposto pelo modelo de domínio para fazer as reservas.
O agregado
SeatAvailability
regista um evento que contém o número de lugares que foram reservados. Da próxima vez que o agregado aplicar os eventos, todas as reservas serão utilizadas para calcular o número de lugares restantes.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.
Pode encontrar mais informações sobre este exemplo na Introducing Event Sourcing (Apresentação da Origem do Evento).
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.