Using the Observer Design Pattern in Messaging with .NET Core Microservices

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:

  • Basic knowledge of C# and .NET Core.
  • A basic understanding of microservices and message-driven architecture.
  • Familiarity with RabbitMQ or any messaging broker (such as Kafka, Azure Service Bus, etc.).

Key Components

  1. Subject: This is the object that holds the state and notifies observers about state changes.
  2. Observer: The objects that listen for changes in the subject. When the subject changes, the observers are updated.
  3. Message Broker (e.g., RabbitMQ): This allows microservices to communicate asynchronously by publishing and consuming messages.


Step-by-Step Guide

1. Setup the Microservice Project in .NET Core

Create two microservices:

  • Publisher Microservice: This will act as the subject that notifies observers (subscribers).
  • Subscriber Microservice: These will be the observers, listening for changes or events from the publisher.

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

  • Start RabbitMQ (either locally or in Docker).
  • Start the Publisher Microservice and use Postman or curl to send a POST request to /api/publisher/publish with a message body.
  • Start the Subscriber Microservice to listen for messages.


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.

要查看或添加评论,请登录

Hamza Zeryouh的更多文章

社区洞察

其他会员也浏览了