To implement internal messaging between microservices in .NET, you can utilize various messaging patterns and technologies. One common approach is to use a message broker, such as RabbitMQ or Azure Service Bus, along with a messaging client library like RabbitMQ.Client or Azure.Messaging.ServiceBus. MassTransit is a popular open-source distributed application framework for .NET that provides support for various messaging technologies, including RabbitMQ. It simplifies the development of distributed systems by abstracting away the complexities of message-based communication.
Now, regarding the usage of RabbitMQ with .NET, RabbitMQ provides a robust client library for .NET called “RabbitMQ.Client.” It allows you to interact with RabbitMQ from your .NET applications, whether they are built with C#, VB.NET, or any other .NET language. Before delving deeper into RabbitMQ, it is essential to provide an explanation of certain crucial concepts. Within this docker environment, the examples rely on the default user and default permissions. Therefore, let us now examine the elements and concepts at hand.
Producer: Application that sends the messages.
Consumer: Application that receives the messages.
Queue: Buffer that stores messages.
Message: Information that is sent from the producer to a consumer through RabbitMQ.
Exchange: Receives messages from producers and pushes them to queues depending on rules defined by the exchange type. To receive messages, a queue needs to be bound to at least one exchange.
AMQP: Advanced Message Queuing Protocol is the protocol used by RabbitMQ for messaging.
In this scenario, we demonstrate the utilization of Docker to implement RabbitMQ within a .NET environment. Here we develop two web API projects named “Web” which acts as a consumer and “Mobile” which act as a publisher both are along with a class library project called “Contracts”
Configure Contracts:
Create message contracts (commands and events) that represent the messages to be exchanged between microservices. These can be simple classes with properties that encapsulate the data being transferred. Create a .NET class library project. For example, you could create a project named “Contracts” Here the Contracts.csproj project file –
net6.0
enable
enable
Furthermore, within this project, we will create a DTO (Data Transfer Object) named “MessageDTO” to facilitate the exchange of data.
namespace Contracts.Message;
public record MessageDTO(int Id,string Message);
Configure “Web” and “Mobile” web API :
Now, let’s create two microservice web api projects named “Web” and “Mobile.” Each microservice will have its own configuration file in the form of Web.csproj and Mobile.csproj file. Require some nuget packages to install for both.
MassTransit.AspNetCore
MassTransit.RabbitMQ
Microsoft.VisualStudio.Azure.Containers.Tools.Targets
Swashbuckle.AspNetCore
To facilitate better comprehension of the configuration process, we will provide <ItemGroup> section within the .csproj file. And shared project reference will be “Contracts” project for both Web APIs “Web” and “Mobile”. Here complete .csproj for both.
all
runtime; build; native; contentfiles; analyzers; buildtransitive
Here is the solution snapshot
For access RabbitMQ management console Use this default credentials. Let’s make changes to the appsettings.json file in both the “Web” and “Mobile” web API projects. And add this section.
"RabbitMQConfig": {
"Host": "localhost",
"UserName": "guest",
"Password": "guest"
}
And create a binding model for both.
public class RabbitMQConfig
{
public string? Host { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
}
And Register those credentials with RabbitMQ for both.
var rabbitMQConfig = builder.Configuration
.GetSection("RabbitMQConfig")
.Get();
builder.Services.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.UsingRabbitMq((context, configurator) =>
{
configurator.Host(rabbitMQConfig.Host, h =>
{
h.Username(rabbitMQConfig.UserName);
h.Password(rabbitMQConfig.Password);
});
configurator.ConfigureEndpoints(context);
});
});
Now, let’s establish communication between the “Web” and “Mobile” microservices.
Publish Message from “Mobile” :
In the “Mobile” microservice, use MassTransit to publish messages to RabbitMQ. You can inject an IBus instance into your controller or service, and then use it to publish messages.
Here’s an example of publishing an MessageDTO:
namespace Mobile.Controllers;
[Route("api/[controller]")]
[ApiController]
public class MessageController : ControllerBase
{
private readonly IBus _bus;
public MessageController(IBus bus)
{
_bus = bus;
}
[HttpPost]
public async Task Post([FromBody] string message)
{
int id = Random.Shared.Next(1,99);
await _bus.Publish(new MessageDTO(id,message));
return Ok(new {id,message});
}
}
Consume Message in “Web” :
In the “Web” microservice, use MassTransit to consume messages from RabbitMQ. Create a consumer class that implements IConsumer<T> for the desired message contract and configure MassTransit to consume messages of that type.
Here’s an example of consuming an MessageDTO:
namespace Web.Consumers;
public class MessageConsumer : IConsumer
{
ILogger _logger;
public MessageConsumer(ILogger logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext context)
{
_logger.LogInformation($"Received : {nameof(MessageDTO)}");
_logger.LogInformation($"id:{context.Message.Id} and message:{context.Message.Message}");
await Task.CompletedTask;
}
}
For better control, flexibility, and separation of concerns in the message consumption process. Create a ConsumerDefinition. Here the class is
namespace Web.Consumers;
public class MessageConsumerDefinition : ConsumerDefinition
{
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator consumerConfigurator)
{
endpointConfigurator.UseMessageRetry(r => r.Intervals(500, 1000));
}
}
Let’s add consumer MessageConsumer and MessageConsumerDefinition where MassTransit register in “Web”:
cfg.AddConsumer(typeof(MessageConsumerDefinition));
By utilizing MassTransit with RabbitMQ, you can easily handle the publishing and consuming of messages between microservices in a reliable and scalable manner.
Implement Docker :
Ensure that you have Docker desktop installed on your machine. You can download and install Docker desktop from the official Docker website.
Firstly, We have to Run the Docker Desktop and Then pull rabbitmq image Open a terminal or command prompt and execute the following command to pull the RabbitMQ Docker image from the Docker Hub:
$ docker pull rabbitmq
Then Run RabbitMQ Container Once the image is pulled or not pulled you can run the RabbitMQ container using the following command: this command should be always runnable while until consuming message.
$ docker run -p 15672:15672 -p 5672:5672 masstransit/rabbitmq
Upon successfully executing the command, observe the output displayed here.
To enhance your understanding, you can observe the image being pulled in Docker Desktop. See here pulled image marked with yellow border.
Verify Message Consumption:
Simultaneously launch both web API projects, “Web” and “Mobile.”
To check if the consumer is successfully consuming messages, monitor the logs or add logging statements within the Consume method of the consumer. In this “Web” project here already implemented logger to see receiving messages.
_logger.LogInformation($"Received : {nameof(MessageDTO)}");
_logger.LogInformation($"id:{context.Message.Id} and message:{context.Message.Message}");
To begin, in the “Mobile” web API controller, use Postman or Swagger to perform a POST request with basic data to the URL ‘{base_url}/api/Message’. Let’s check In this postman.
Finally, Let’s have a look at debug console output on your Visual Studio. If you are using VSCode, Open the “Output” panel, click on the dropdown at the top-right corner and select “.NET Core Console”. Then see Marked yellow as output message.
Additionally, you can inspect the RabbitMQ management console or employ tools such as RabbitMQ’s command-line interface (rabbitmqctl) or the RabbitMQ management API to observe the dequeuing of messages from the queue.
Conclusion :
Remember that the specific implementation details may vary depending on the messaging broker and client library you choose. It’s important to refer to the documentation and examples provided by the messaging broker and client library to understand their specific usage patterns and best practices. Overall, using a messaging broker and appropriate client libraries in .NET allows you to establish a reliable and scalable internal messaging system between your microservices, enabling asynchronous communication and decoupling their interactions.