Implémentation de la communication basée sur les événements entre les microservices (événements d’intégration)

Conseil

Ce contenu est un extrait du livre électronique « .NET Microservices Architecture for Containerized .NET Applications », disponible sur .NET Docs ou sous forme de PDF téléchargeable gratuitement et pouvant être lu hors ligne.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Comme décrit précédemment, quand vous utilisez la communication basée sur les événements, un microservice publie un événement chaque fois qu’une chose notable se produit (par exemple, lorsqu’une entité commerciale est mise à jour). Les autres microservices s’abonnent à ces événements. Lorsqu’un microservice reçoit un événement, il peut mettre à jour ses propres entités commerciales, ce qui peut générer la publication d’autres événements. Il s’agit de l’essence même du concept de cohérence éventuelle. Ce système de publication/abonnement est généralement obtenu à l’aide de l’implémentation d’un bus d’événements. Le bus d’événements peut être conçu comme l’interface de l’API nécessaire pour s’abonner aux événements et s’en désabonner, ainsi que pour les publier. Il peut également avoir une ou plusieurs implémentations basées sur une communication entre processus ou une communication de messagerie, telle qu’une file d’attente de messagerie ou un bus de service prenant en charge la communication asynchrone, ainsi qu’un modèle de publication/abonnement.

Vous pouvez utiliser des événements pour implémenter des transactions commerciales concernant plusieurs services, et ainsi obtenir une cohérence à terme entre ces services. Une transaction cohérente à terme se compose d’une série d’actions distribuées. À chaque action, le microservice met à jour une entité commerciale et publie un événement qui déclenche l’action suivante. N’oubliez pas que la transaction n’étend pas la persistance sous-jacente et le bus d’événements. Par conséquent, l’idempotence doit être gérée. La figure 6-18 ci-dessous montre un événement PriceUpdated publié via un bus d’événements, de sorte que la mise à jour de prix est propagée au panier et à d’autres microservices.

Diagram of asynchronous event-driven communication with an event bus.

Figure 6-18. Communication pilotée par les événements et basée sur un bus d’événements

Cette section explique comment implémenter ce type de communication avec .NET à l’aide d’une interface de bus d’événements générique, comme indiqué dans la figure 6-18. Il existe plusieurs implémentations possibles, qui utilisent chacune leur propre technologie ou infrastructure, telles que RabbitMQ, Azure Service Bus ou tout autre Service Bus tiers open source ou commercial.

Utilisation de répartiteurs de messages et de bus de services pour les systèmes de production

Comme indiqué dans la section relative à l’architecture, vous pouvez choisir parmi plusieurs technologies de messagerie pour l’implémentation de votre bus d’événements abstraits. Toutefois, ces technologies sont situées à des niveaux différents. Par exemple, le répartiteur de messages RabbitMQ se trouve à un niveau inférieur à celui des produits commerciaux tels qu’Azure Service Bus, NServiceBus, MassTransit ou Brighter. La plupart de ces produits peuvent fonctionner par-dessus RabbitMQ ou Azure Service Bus. Vous devez choisir le produit en fonction du nombre de fonctionnalités et du niveau de scalabilité prête à l’emploi dont vous avez besoin pour votre application.

Si vous souhaitez uniquement implémenter une preuve de concept de bus d’événements pour votre environnement de développement, comme dans l’exemple eShopOnContainers, une simple implémentation par-dessus RabbitMQ exécutée dans un conteneur peut être suffisante. Toutefois, pour les systèmes stratégiques et les systèmes de production qui nécessitent un haut niveau de scalabilité, il peut être utile d’évaluer et d’utiliser Azure Service Bus.

Si vous avez besoin d’un haut niveau d’abstraction et de fonctionnalités plus riches telles que Sagas pour les processus longs qui facilitent le développement distribué, d’autres bus de services commerciaux et open source comme NServiceBus, MassTransit et Brighter méritent d’être évalués. Dans ce cas, les abstractions et l’API à utiliser sont généralement celles fournies par ces bus de services de haut niveau, et non vos propres abstractions (comme les abstractions de bus d’événements simples fournies dans eShopOnContainers). Pour cela, vous pouvez examiner l’exemple eShopOnContainers dupliqué (fork) à l’aide de NServiceBus (autre exemple dérivé, implémenté par Particular Software).

Bien entendu, vous pouvez toujours créer vos propres fonctionnalités de bus de services par-dessus les technologies de bas niveau comme RabbitMQ et Docker. Toutefois, le travail que cela nécessite peut être trop coûteux pour une application d’entreprise personnalisée.

Pour réitérer : les exemples d’abstractions de bus d’événements et d’implémentation présentés dans l’exemple eShopOnContainers sont destinés à être utilisés uniquement comme preuve de concept. Une fois que vous avez décidé que vous souhaitez établir une communication asynchrone et pilotée par les événements, comme expliqué dans la section actuelle, vous devez choisir le produit Service Bus qui convient le mieux à vos besoins pour la production.

Événements d’intégration

Les événements d’intégration sont utilisés pour synchroniser l’état du domaine sur plusieurs microservices ou systèmes externes. Pour cette fonctionnalité, vous devez publier les événements d’intégration en dehors du microservice. Lorsqu’un événement est publié sur plusieurs microservices récepteurs (sur tous les microservices abonnés à l’événement d’intégration), le gestionnaire d’événements de chaque microservice récepteur gère l’événement.

Un événement d’intégration est essentiellement une classe conteneur de données, comme le montre l’exemple suivant :

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;
    }
}

Les événements d’intégration peuvent être définis au niveau de l’application de chaque microservice. De cette façon, ils sont dissociés des autres microservices, d’une manière comparable à la façon dont les ViewModels sont définis sur le serveur et sur le client. Cependant, il n’est pas recommandé de partager une bibliothèque d’événements d’intégration entre plusieurs microservices, car cela aurait pour effet de coupler ces microservices avec une seule bibliothèque de données de définition d’événements. Un tel partage est déconseillé, tout comme le partage d’un modèle de domaine entre plusieurs microservices, car les microservices doivent être complètement autonomes. Pour plus d’informations, consultez ce billet de blog sur la quantité de données à placer dans les événements. Veillez à ne pas aller trop loin, car cet autre billet de blog décrit le problème que les messages de données déficients peuvent produire. Votre conception des événements est censée être « parfaitement adaptée » aux besoins de leurs consommateurs.

Seules certaines bibliothèques peuvent être partagées entre plusieurs microservices. Parmi elles figurent les bibliothèques qui sont des blocs d’application finale, comme l’API cliente Event Bus de l’exemple eShopOnContainers. Vous avez également les bibliothèques qui constituent des outils pouvant être partagés en tant que composants NuGet, comme les sérialiseurs JSON.

Le bus d’événements

Un bus d’événements permet une communication de type publication/abonnement entre les microservices, sans nécessiter que les composants soient explicitement informés de la présence des uns des autres, comme le montre la figure 6-19.

A diagram showing the basic publish/subscribe pattern.

Figure 6-19. Principes de base de la communication de type publication/abonnement avec un bus d’événements

Le diagramme ci-dessus montre que le microservice A publie sur le bus d’événements, lequel distribue aux microservices d’abonnement B et C, sans que le serveur de publication ait besoin de connaître les abonnés. Le bus d’événements est lié au modèle Observateur et au modèle Publication/Abonnement.

Modèle Observateur

Dans le modèle Observateur, votre objet principal (l’observable) envoie aux autres objets intéressés (les observateurs) des informations pertinentes (des événements).

Modèle Publication/Abonnement

L’objectif du modèle Publication/Abonnement est le même que celui du modèle Observateur, c’est-à-dire que vous souhaitez informer les autres services de certains événements. Toutefois, il existe une différence importante entre ces deux modèles. Dans le modèle Observateur, la diffusion va directement de l’observable aux observateurs, pour qu’ils prennent connaissance des uns des autres. En revanche, lorsque vous utilisez un modèle Publication/Abonnement, un troisième élément est impliqué. Il s’agit du répartiteur (de messages) ou du bus d’événements, qui est connu à la fois de celui qui publie et de celui qui s’abonne. Par conséquent, lorsque vous utilisez le modèle Publication/Abonnement, celui qui publie et ses abonnés sont dissociés grâce au bus d’événements ou au répartiteur de messages.

L’intermédiaire ou le bus d’événements

Comment permettre l’anonymat entre celui qui publie les événements et ses abonnés ? Un moyen simple consiste à laisser un intermédiaire s’occuper de toutes les communications. Un bus d’événements joue le rôle de cet intermédiaire.

Un bus d’événements est généralement constitué de deux parties :

  • L’abstraction ou interface

  • Une ou plusieurs implémentations

Dans la figure 6-19, vous pouvez voir que, du point de vue d’une application, le bus d’événements n’est autre qu’un canal de publication/abonnement. La façon dont vous implémentez cette communication asynchrone peut varier. Plusieurs implémentations sont possibles, de sorte que vous pouvez passer de l’une à l’autre, selon les exigences de votre environnement (par exemple, s’il s’agit d’un environnement de production ou d’un environnement de développement).

Dans la figure 6-20, vous pouvez voir une abstraction d’un bus d’événements avec plusieurs implémentations basées sur des technologies de messagerie d’infrastructure, telles que RabbitMQ, Azure Service Bus ou un autre répartiteur de messages/événements.

Diagram showing the addition of an event bus abstraction layer.

Figure 6-20. Les différentes implémentations d’un bus d’événements

Il est judicieux de définir le bus d’événements par le biais d’une interface, pour pouvoir l’implémenter avec plusieurs technologies comme RabbitMQ Azure Service Bus ou autre. Toutefois, comme mentionné précédemment, l’utilisation de vos propres abstractions (l’interface du bus d’événements) convient uniquement si vous avez besoin de fonctionnalités de bus d’événements de base, prises en charge par vos abstractions. Si vous avez besoin de fonctionnalités de bus de services plus riches, utilisez l’API et les abstractions fournies par votre bus de services commercial préféré, plutôt que vos propres abstractions.

Définition de l’interface d’un bus d’événements

Commençons par du code d’implémentation pour l’interface du bus d’événements, et par les implémentations possibles, à des fins d’exploration. L’interface doit être générique et simple, comme celle qui suit.

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;
}

La méthode Publish est simple. Le bus d’événements va diffuser l’événement d’intégration qui lui a été passé à tous les microservices, et même aux applications externes, abonnées à cet événement. Cette méthode est utilisée par le microservice qui publie l’événement.

Les méthodes Subscribe (vous pouvez avoir plusieurs implémentations selon les arguments) sont utilisées par les microservices qui souhaitent recevoir des événements. Cette méthode a deux arguments. Le premier est l’événement d’intégration auquel s’abonner (IntegrationEvent). Le deuxième argument est le gestionnaire d’événements d’intégration (ou méthode de rappel) nommé IIntegrationEventHandler<T>, qui doit être exécuté lorsque le microservice récepteur obtient le message d’événement d’intégration.

Ressources supplémentaires

Certaines solutions de messagerie prêtes pour la production :