Using the Observer Design Pattern in Messaging with .NET Core Microservices
Introduction
The Observer design pattern is one of the behavioral design patterns where an object (the subject) maintains a list of its dependent objects (the observers) and notifies them of any state changes, typically by calling one of their methods. In a microservices architecture, this pattern is incredibly useful for building event-driven systems, particularly in message-based communication.
This article will demonstrate how to implement the Observer pattern in a messaging system using .NET Core and RabbitMQ, though the concepts are applicable to any message broker.
Pre-requisites
Before we begin, ensure that you have the following:
Key Components
Step-by-Step Guide
1. Setup the Microservice Project in .NET Core
Create two microservices:
You can create the microservices using the following commands:
dotnet new webapi -n PublisherMicroservice
dotnet new webapi -n SubscriberMicroservice
2. Add RabbitMQ NuGet Package
For messaging, we will use RabbitMQ. To interact with RabbitMQ in .NET Core, you can use the RabbitMQ.Client NuGet package.
Install it with the following command:
dotnet add package RabbitMQ.Client
领英推荐
3. Implementing the Observer Pattern
Publisher Microservice (Subject)
In the publisher service, we need to implement logic to notify the subscribers whenever a message is published. This can be done by publishing a message to a RabbitMQ exchange that the subscribers are listening to.
PublisherController.cs
using Microsoft.AspNetCore.Mvc;
using RabbitMQ.Client;
using System.Text;
using System.Text.Json;
namespace PublisherMicroservice.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PublisherController : ControllerBase
{
private readonly string _rabbitMqHost = "localhost"; // Replace with your RabbitMQ host
[HttpPost("publish")]
public IActionResult PublishMessage([FromBody] string message)
{
var factory = new ConnectionFactory() { HostName = _rabbitMqHost };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "message_queue", durable: false, exclusive: false, autoDelete: false, arguments: null);
var messageBody = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "message_queue",
basicProperties: null,
body: messageBody);
return Ok("Message sent to the subscribers.");
}
}
}
}
Here, we have a simple API (POST /api/publisher/publish) that takes a message, connects to RabbitMQ, and sends the message to a queue.
Subscriber Microservice (Observers)
Now, the subscriber service listens for messages from the publisher and reacts to those messages.
SubscriberService.cs
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
namespace SubscriberMicroservice.Services
{
public class SubscriberService
{
private readonly string _rabbitMqHost = "localhost"; // Replace with your RabbitMQ host
public void StartListening()
{
var factory = new ConnectionFactory() { HostName = _rabbitMqHost };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "message_queue", durable: false, exclusive: false, autoDelete: false, arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($"Received message: {message}");
// Observer logic can be added here (e.g., update some state, call other services, etc.)
};
channel.BasicConsume(queue: "message_queue", autoAck: true, consumer: consumer);
Console.WriteLine(" [*] Waiting for messages.");
Console.ReadLine(); // Keep the service running
}
}
}
}
In this example, the subscriber connects to RabbitMQ and listens to the message_queue. Whenever a new message is published, the subscriber prints it to the console. This is a simple form of the Observer reacting to changes (new messages).
Startup Configuration (for Subscriber)
In the Program.cs or Startup.cs file of the subscriber service, you need to start the listener.
public class Program
{
public static void Main(string[] args)
{
var subscriberService = new SubscriberService();
subscriberService.StartListening();
}
}
4. Running the Microservices
Conclusion
By implementing the Observer design pattern in a messaging system within .NET Core microservices, we can achieve loose coupling between services, which is a core benefit of microservices architecture. The Publisher microservice acts as the subject, notifying the Subscriber microservices whenever new messages are published. This setup is highly scalable and efficient in an event-driven environment.
This approach can be extended further by integrating additional services like Kafka, Azure Service Bus, or other message brokers, depending on the complexity of your system. The Observer pattern simplifies managing dependencies and keeps each service focused on its own responsibilities, fostering clean and maintainable code.