Implementação de comunicação baseada em eventos entre microsserviços (eventos de integração)
Gorjeta
Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
Conforme descrito anteriormente, quando você usa a comunicação baseada em eventos, um microsserviço publica um evento quando algo notável acontece, como quando atualiza uma entidade comercial. Outros microsserviços assinam esses eventos. Quando um microsserviço recebe um evento, ele pode atualizar suas próprias entidades comerciais, o que pode levar à publicação de mais eventos. Esta é a essência do conceito de eventual coerência. Este sistema de publicação/subscrição é normalmente realizado utilizando uma implementação de um barramento de eventos. O barramento de eventos pode ser projetado como uma interface com a API necessária para assinar e cancelar a assinatura de eventos e publicar eventos. Ele também pode ter uma ou mais implementações baseadas em qualquer comunicação entre processos ou mensagens, como uma fila de mensagens ou um barramento de serviço que suporta comunicação assíncrona e um modelo de publicação/assinatura.
Você pode usar eventos para implementar transações comerciais que abrangem vários serviços, o que lhe dá uma eventual consistência entre esses serviços. Uma transação eventualmente consistente consiste em uma série de ações distribuídas. Em cada ação, o microsserviço atualiza uma entidade comercial e publica um evento que dispara a próxima ação. Lembre-se de que a transação não abrange a persistência subjacente e o barramento de eventos, portanto , a idempotência precisa ser tratada. A Figura 6-18 abaixo mostra um evento PriceUpdated publicado por meio de um barramento de eventos, de modo que a atualização de preço é propagada para a Cesta e outros microsserviços.
Figura 6-18. Comunicação orientada a eventos baseada em um barramento de eventos
Esta seção descreve como você pode implementar esse tipo de comunicação com o .NET usando uma interface genérica de barramento de eventos, como mostra a Figura 6-18. Há várias implementações potenciais, cada uma usando uma tecnologia ou infraestrutura diferente, como RabbitMQ, Azure Service Bus ou qualquer outro barramento de serviço comercial ou de código aberto de terceiros.
Usando agentes de mensagens e barramentos de serviço para sistemas de produção
Conforme observado na seção arquitetura, você pode escolher entre várias tecnologias de mensagens para implementar seu barramento de eventos abstrato. Mas essas tecnologias estão em níveis diferentes. Por exemplo, o RabbitMQ, um agente de transporte de mensagens, está em um nível mais baixo do que produtos comerciais como o Azure Service Bus, NServiceBus, MassTransit ou Brighter. A maioria desses produtos pode funcionar sobre o RabbitMQ ou o Barramento de Serviço do Azure. Sua escolha de produto depende de quantos recursos e quanta escalabilidade pronta para uso você precisa para seu aplicativo.
Para implementar apenas uma prova de conceito de barramento de eventos para seu ambiente de desenvolvimento, como no exemplo eShopOnContainers , uma implementação simples sobre o RabbitMQ em execução como um contêiner pode ser suficiente. Mas para sistemas de missão crítica e de produção que precisam de alta escalabilidade, convém avaliar e usar o Barramento de Serviço do Azure.
Se você precisa de abstrações de alto nível e recursos mais ricos, como Sagas , para processos de longa execução que facilitam o desenvolvimento distribuído, vale a pena avaliar outros barramentos de serviço comerciais e de código aberto como NServiceBus, MassTransit e Brighter . Nesse caso, as abstrações e a API a serem usadas geralmente seriam diretamente as fornecidas por esses barramentos de serviço de alto nível em vez de suas próprias abstrações (como as abstrações simples de barramento de evento fornecidas no eShopOnContainers). Para esse assunto, você pode pesquisar o eShopOnContainers bifurcado usando NServiceBus (amostra derivada adicional implementada pela Particular Software).
É claro que você sempre pode criar seus próprios recursos de barramento de serviço em cima de tecnologias de nível inferior, como RabbitMQ e Docker, mas o trabalho necessário para "reinventar a roda" pode ser muito caro para um aplicativo corporativo personalizado.
Para reiterar: as abstrações de barramento de evento de amostra e a implementação apresentadas no exemplo eShopOnContainers destinam-se a ser usadas apenas como uma prova de conceito. Depois de decidir que deseja ter uma comunicação assíncrona e orientada a eventos, conforme explicado na seção atual, você deve escolher o produto de barramento de serviço que melhor se adapta às suas necessidades de produção.
Eventos de integração
Os eventos de integração são usados para sincronizar o estado do domínio em vários microsserviços ou sistemas externos. Essa funcionalidade é feita publicando eventos de integração fora do microsserviço. Quando um evento é publicado em vários microsserviços de recetor (para quantos microsserviços forem inscritos no evento de integração), o manipulador de eventos apropriado em cada microsserviço de recetor manipula o evento.
Um evento de integração é basicamente uma classe de retenção de dados, como no exemplo a seguir:
public class ProductPriceChangedIntegrationEvent : IntegrationEvent
{
public int ProductId { get; private set; }
public decimal NewPrice { get; private set; }
public decimal OldPrice { get; private set; }
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice,
decimal oldPrice)
{
ProductId = productId;
NewPrice = newPrice;
OldPrice = oldPrice;
}
}
Os eventos de integração podem ser definidos no nível de aplicativo de cada microsserviço, para que sejam dissociados de outros microsserviços, de forma comparável à forma como ViewModels são definidos no servidor e no cliente. O que não é recomendado é compartilhar uma biblioteca de eventos de integração comum em vários microsserviços; fazer isso seria acoplar esses microsserviços a uma única biblioteca de dados de definição de evento. Você não quer fazer isso pelos mesmos motivos que não deseja compartilhar um modelo de domínio comum em vários microsserviços: os microsserviços devem ser completamente autônomos. Para obter mais informações, consulte esta postagem no blog sobre a quantidade de dados a serem colocados em eventos. Tenha cuidado para não levar isso muito longe, pois esta outra postagem de blog descreve o problema que mensagens deficientes de dados podem produzir. O design dos seus eventos deve ter como objetivo ser "perfeito" para as necessidades dos seus consumidores.
Há apenas alguns tipos de bibliotecas que você deve compartilhar entre microsserviços. Uma delas são as bibliotecas que são blocos finais de aplicativos, como a API do cliente do Barramento de Eventos, como no eShopOnContainers. Outra são as bibliotecas que constituem ferramentas que também podem ser compartilhadas como componentes do NuGet, como serializadores JSON.
O autocarro do evento
Um barramento de eventos permite a comunicação no estilo de publicação/assinatura entre microsserviços sem exigir que os componentes estejam explicitamente cientes uns dos outros, como mostra a Figura 6-19.
Figura 6-19. Publique/assine noções básicas com um barramento de eventos
O diagrama acima mostra que o microsserviço A publica no Event Bus, que distribui para os microsserviços B e C assinantes, sem que o editor precise conhecer os assinantes. O barramento de eventos está relacionado ao padrão Observer e ao padrão publish-subscribe.
Padrão do observador
No padrão Observador, seu objeto principal (conhecido como Observável) notifica outros objetos interessados (conhecidos como Observadores) com informações relevantes (eventos).
Padrão Publicar/Subscrever (Pub/Sub)
O objetivo do padrão Publicar/Assinar é o mesmo do padrão Observador: você deseja notificar outros serviços quando determinados eventos ocorrerem. Mas há uma diferença importante entre os padrões Observer e Pub/Sub. No padrão observador, a transmissão é realizada diretamente do observável para os observadores, para que eles se "conheçam". Mas ao usar um padrão Pub/Sub, há um terceiro componente, chamado broker, ou message broker ou event bus, que é conhecido tanto pelo editor quanto pelo assinante. Portanto, ao usar o padrão Pub/Sub, o editor e os assinantes são precisamente dissociados graças ao barramento de evento mencionado ou ao corretor de mensagens.
O intermediário ou o ônibus de eventos
Como conseguir o anonimato entre editor e assinante? Uma maneira fácil é deixar um intermediário cuidar de toda a comunicação. Um ônibus de eventos é um desses intermediários.
Um barramento de eventos é normalmente composto por duas partes:
A abstração ou interface.
Uma ou mais implementações.
Na Figura 6-19, você pode ver como, do ponto de vista do aplicativo, o barramento de eventos nada mais é do que um canal Pub/Sub. A maneira como você implementa essa comunicação assíncrona pode variar. Ele pode ter várias implementações para que você possa trocar entre elas, dependendo dos requisitos do ambiente (por exemplo, ambientes de produção versus desenvolvimento).
Na Figura 6-20, você pode ver uma abstração de um barramento de eventos com várias implementações baseadas em tecnologias de mensagens de infraestrutura, como RabbitMQ, Azure Service Bus ou outro agente de eventos/mensagens.
Figura 6- 20. Várias implementações de um barramento de eventos
É bom ter o barramento de eventos definido através de uma interface para que possa ser implementado com várias tecnologias, como RabbitMQ, Azure Service bus ou outras. No entanto, e como mencionado anteriormente, usar suas próprias abstrações (a interface do barramento de eventos) é bom apenas se você precisar de recursos básicos de barramento de eventos suportados por suas abstrações. Se você precisar de recursos de barramento de serviço mais avançados, provavelmente deve usar a API e as abstrações fornecidas pelo barramento de serviço comercial preferido em vez de suas próprias abstrações.
Definindo uma interface de barramento de eventos
Vamos começar com algum código de implementação para a interface do barramento de eventos e possíveis implementações para fins de exploração. A interface deve ser genérica e direta, como na interface a seguir.
public interface IEventBus
{
void Publish(IntegrationEvent @event);
void Subscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
}
O Publish
método é simples. O barramento do evento transmitirá o evento de integração passado para qualquer microsserviço, ou mesmo um aplicativo externo, inscrito nesse evento. Esse método é usado pelo microsserviço que está publicando o evento.
Os Subscribe
métodos (você pode ter várias implementações dependendo dos argumentos) são usados pelos microsserviços que desejam receber eventos. Este método tem dois argumentos. O primeiro é o evento de integração para se inscrever (IntegrationEvent
). O segundo argumento é o manipulador de eventos de integração (ou método de retorno de chamada), chamado IIntegrationEventHandler<T>
, a ser executado quando o microsserviço recetor recebe essa mensagem de evento de integração.
Recursos adicionais
Algumas soluções de mensagens prontas para produção:
Azure Service Bus
https://learn.microsoft.com/azure/service-bus-messaging/NServiceBus
https://particular.net/nservicebusMassTransit
https://masstransit-project.com/