RabbitMQ: Using It with Microservices

dazevedo

Daniel Azevedo

Posted on November 24, 2024

RabbitMQ: Using It with Microservices

Hi devs,

RabbitMQ is one of the most popular message brokers, enabling communication between services in a distributed system. It supports various messaging patterns, such as work queues, publish/subscribe, and RPC. In this post, I’ll demonstrate how to set up RabbitMQ and use it to enable communication between three microservices: Order Service, Inventory Service, and Notification Service.


Why RabbitMQ?

RabbitMQ allows microservices to:

  1. Decouple communication: Services don't need to know about each other.
  2. Increase reliability: Messages are persisted until delivered.
  3. Improve scalability: Multiple consumers can process messages concurrently.
  4. Support multiple protocols: AMQP, STOMP, MQTT, etc.

Architecture Overview

In this example:

  1. Order Service publishes an OrderPlaced message to a RabbitMQ exchange.
  2. Inventory Service subscribes to the exchange to update stock.
  3. Notification Service subscribes to the exchange to send order confirmation notifications.

Prerequisites

  1. Install RabbitMQ:

    • Using Docker:
     docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management
    
  • Access the RabbitMQ management UI at http://localhost:15672 (default username/password: guest/guest).
  1. Install the RabbitMQ.Client package in your .NET projects:
   dotnet add package RabbitMQ.Client
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Implementation

Step 1: Define a Shared Order Model

public class Order
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal TotalPrice { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

This model will represent the order data shared between services.


Step 2: Create the Order Service (Producer)

The Order Service publishes messages to the RabbitMQ exchange.

using RabbitMQ.Client;
using System.Text;
using System.Text.Json;

public class OrderService
{
    private readonly IModel _channel;

    public OrderService()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        var connection = factory.CreateConnection();
        _channel = connection.CreateModel();

        // Declare an exchange and a queue
        _channel.ExchangeDeclare(exchange: "orders_exchange", type: ExchangeType.Fanout);
    }

    public void PlaceOrder(Order order)
    {
        var message = JsonSerializer.Serialize(order);
        var body = Encoding.UTF8.GetBytes(message);

        _channel.BasicPublish(exchange: "orders_exchange", routingKey: "", basicProperties: null, body: body);
        Console.WriteLine($"Order placed: {order.ProductName}");
    }
}

// Usage
var orderService = new OrderService();
var order = new Order { Id = 1, ProductName = "Laptop", Quantity = 1, TotalPrice = 1500.00m };
orderService.PlaceOrder(order);
Enter fullscreen mode Exit fullscreen mode

Here, the Order Service publishes an OrderPlaced event to the orders_exchange.


Step 3: Create the Inventory Service (Consumer)

The Inventory Service listens to the orders_exchange and updates stock.

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;

public class InventoryService
{
    public void Start()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        // Declare the exchange and queue
        channel.ExchangeDeclare(exchange: "orders_exchange", type: ExchangeType.Fanout);
        var queueName = channel.QueueDeclare().QueueName;
        channel.QueueBind(queue: queueName, exchange: "orders_exchange", routingKey: "");

        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            var order = JsonSerializer.Deserialize<Order>(message);

            Console.WriteLine($"Inventory updated for Product: {order.ProductName}, Quantity: {order.Quantity}");
        };

        channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
        Console.WriteLine("Inventory Service is running...");
    }
}

// Usage
var inventoryService = new InventoryService();
inventoryService.Start();
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Notification Service (Consumer)

The Notification Service listens to the orders_exchange and sends notifications.

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;

public class NotificationService
{
    public void Start()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        // Declare the exchange and queue
        channel.ExchangeDeclare(exchange: "orders_exchange", type: ExchangeType.Fanout);
        var queueName = channel.QueueDeclare().QueueName;
        channel.QueueBind(queue: queueName, exchange: "orders_exchange", routingKey: "");

        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            var order = JsonSerializer.Deserialize<Order>(message);

            Console.WriteLine($"Notification sent for Order ID: {order.Id}, Product: {order.ProductName}");
        };

        channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
        Console.WriteLine("Notification Service is running...");
    }
}

// Usage
var notificationService = new NotificationService();
notificationService.Start();
Enter fullscreen mode Exit fullscreen mode

How It All Works

  1. Order Service publishes an OrderPlaced event to the RabbitMQ orders_exchange.
  2. RabbitMQ routes the message to all bound queues.
  3. Inventory Service and Notification Service consume the message and process it independently.

Testing the System

  1. Run the Inventory Service and Notification Service.
  2. Place an order using the Order Service.
  3. Observe the logs for the inventory update and notification.

Benefits of Using RabbitMQ in Microservices

  1. Decoupled Communication: Producers and consumers don’t need direct knowledge of each other.
  2. Scalable: Add more consumers to process messages faster.
  3. Reliable Delivery: Messages are persisted and retried if delivery fails.
  4. Flexible Messaging Patterns: Work queues, publish/subscribe, RPC, etc.

Conclusion

RabbitMQ is a powerful message broker that simplifies communication in microservices. By using the Fanout Exchange pattern, we ensured that multiple services could respond to the same event independently.

Keep coding!

💖 💪 🙅 🚩
dazevedo
Daniel Azevedo

Posted on November 24, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

RabbitMQ: Using It with Microservices
eventdriven RabbitMQ: Using It with Microservices

November 24, 2024