Solution de communication entre microservices avec RabbitMq et MassTransit

Dans cet article nous allons explorer la communication MicroService avec RabbitMq et ASP .Net Core.

Nous verrons comment utiliser RabbitMQ et le package MassTransit pour faire communiquer des services. MassTransit nous aidera à publier et consommer des messages de notre serveur RabbitMQ.

Qu’est-ce qu’un Message Broker ?

Avant de commencer sur le sujet, il me parait important d’expliciter certains termes.

La principale responsabilité de Message Broker est de négocier les messages entre le publisher et les abonnés.
Une fois qu’un message est reçu par un Message Broker de la part du publisher, il achemine le message vers un abonné.(subscriber) Le modèle de cMessage Broker est l’un des modèles les plus utiles lorsqu’il s’agit de découpler les microservices.

  • Producer : Application responsable de l’envoi d’un message
  • Consumer : Application responsable de la consommation du message
  • Queue : file ou sont stockés les messages

Ici nous utiliserons RabbitMQ,qui est un des Message Broker disponibles, car il est extrêmement léger , scalable (permet une montée en charge ), permet le partage de données de manière simple.

Ci-dessous un exemple d’architecture distribuée avec RabbitMq :

Elle montre que des messages peuvent envoyés à des applicatifs en « hors ligne » ou

Mise en place de l’environnement

Ici, rien de plus simple, il vous suffit de lancer une commande docker pour avoir un serveur RabbitMq disponible sur votre machine : (veillez à avoir installé Docker Desktop avant)

docker run -d --hostname my-rabbit --name some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management

Vous devriez avoir un container Docker en train de tourner , représentée comme suit dans votre DockerDesktop :

Si vous souhaitez consulter le dashboard, vous pouvez le faire via l’url http://localhost:15672/

L’utilisateur et le mot de passe par défaut sont guest / guest .

L’interface devrait ressembler à cela :


Exemple d’implémentation :

Vous trouverez ici un exemple d’implémentation avec MassTransit : https://github.com/AlexCastroAlex/MicroService.RabbitMq.Sample

Je vais maintenant détailler les couches implémentées :

CrossCuttingLayer

Class library commune à tous les projets , elle contient la classe Todo les constantes du serveur RabbitMQ :

    public class Todo
    {
        public string Id { get; set; }
        public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
        public string TaskDescription { get; set; }
        public bool IsCompleted { get; set; }
    }
 public class RabbitMqConsts
    {
        public const string RabbitMqRootUri = "rabbitmq://localhost";
        public const string RabbitMqUri = "rabbitmq://localhost/todoQueue";
        public const string UserName = "guest";
        public const string Password = "guest";
        public const string NotificationServiceQueue = "notification.service";
    }

Configuration de RabbitMq :

 public static class BusConfigurator
    {
        public static IBusControl ConfigureBus(Action<IRabbitMqBusFactoryConfigurator, IRabbitMqHost> registrationAction = null)
        {
            return Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.Host(new Uri(RabbitMqConsts.RabbitMqRootUri), h =>
                   {
                       h.Username(RabbitMqConsts.UserName);
                       h.Password(RabbitMqConsts.Password);
                   });

            });
        }
    }

Web Api Publisher :

MassTransit sera configuré dans le startup et nous aurons une API qui nous permettra de publier des messages.

 public void ConfigureServices(IServiceCollection services)
        {

            services.AddMassTransit(x =>
            {
                x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(config =>
                {
                    config.Host(new Uri(RabbitMqConsts.RabbitMqRootUri), h =>
                    {
                        h.Username(RabbitMqConsts.UserName);
                        h.Password(RabbitMqConsts.Password);
                    });
                }));
            });
            services.AddMassTransitHostedService();
            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Microservice.Todo.Publisher", Version = "v1" });
            });
        }
 [HttpPost]
        public async Task<IActionResult> CreateTicket(Todo todoModel)
        {
            if (todoModel is not null)
            {
                Uri uri = new Uri(RabbitMqConsts.RabbitMqUri);
                var endPoint = await _bus.GetSendEndpoint(uri);
                await endPoint.Send(todoModel);
                return Ok();
            }
            return BadRequest();
        }

Application console Notification

Pour des besoins de test et de vérification visuelle, une application console a été crée.

Nous créons en premier lieu une classe qui permettra de consommer les messages de la file dans laquelle nous envoyons les messages avec l’API Publisher :

public class TodoConsumerNotification : IConsumer<Todo>
    {
        public async Task Consume(ConsumeContext<Todo> context)
        {
            await Console.Out.WriteLineAsync($"Notification sent: todo id {context.Message.Id}");
        }
    }

Cette classe hérite de IConsumer et implémente du coup une méthode Consume avec en paramètre un message de type Todo crée dans la couche de « Common ».

Ensuite la méthode Main() de l’application Console va pourvoir créer ce « consumer » qui sera en « écoute » sur la file de messages et attendra les messages qu’elle consommera dès leur arrivée.

 var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.Host(new Uri(RabbitMqConsts.RabbitMqRootUri), h =>
                {
                    h.Username(RabbitMqConsts.UserName);
                    h.Password(RabbitMqConsts.Password);
                });
                cfg.ReceiveEndpoint("todoQueue", ep =>
                {
                    ep.PrefetchCount = 16;
                    ep.UseMessageRetry(r => r.Interval(2, 100));
                    ep.Consumer<TodoConsumerNotification>();
                });

            });

            bus.StartAsync();
            Console.WriteLine("Listening for Todo registered events.. Press enter to exit");
            Console.ReadLine();
            bus.StopAsync();

API Consumer :

Cette API a pour but d’illustrer une communication plus standard dans un mode Microservice et ne servira pas dans nos tests.

Elle permet de voir comment les messages seront consommés par un service et non une application console qui ne représente pas un cas courant à l’instar d’une API Rest.

Tester votre application :

Vous allez devoir lancer 3 applications en même temps : (Notification et Publisher auraient suffit)

Ensuite allez sur l’API Publisher et publiez un message via l’API Todo :

Vous devriez voir un message apparaitre dans l’application Console ce qui prouve que le message a été publié et consommé.

Sur votre dashboard, vous devriez aussi avoir des messages :

Si vous ne lancez maintenant que la publisher API et que exécutez des requêtes POST, les messages ne seront pas consommés mais conservés dans la file jusqu’au prochain lancement de l’application Console.

Aujourd’hui, nous avons un peu éclairci le fonctionnement de RabbitMQ, l’avons mis en place pour une future utilisation en mode microservice.

Il existe bien sur des fonctionnalités que nous n’avons pas exploré comme les exchanges , les routing Key , les direct messages mais je vous garde ça pour un prochain tutoriel.

En attendant vous pouvez consulter la documentation de RabbitMQ : https://www.rabbitmq.com/dotnet-api-guide.html

Have fun coding ! 😎