in Architettura Software, C#, Informatica

Rebus: Semplificare la Comunicazione tra Servizi con un Message Bus per C#

La comunicazione tra i vari componenti di questi sistemi rappresenta spesso una delle sfide più complesse da affrontare. È qui che entrano in gioco i Message Bus, e in particolare Rebus, una libreria .NET pensata per semplificare drasticamente la gestione di code di messaggi in C#.

Cos’è Rebus?

Rebus è un framework leggero e flessibile per la messaggistica che agisce come un bus di messaggi. A differenza di una semplice libreria client per un sistema di coda, Rebus ti offre un’astrazione di alto livello che ti permette di inviare e ricevere messaggi senza preoccuparti dei dettagli di implementazione della coda sottostante. Se nel progetto si sta usando RabbitMQ, Azure Service Bus, SQL Server o un altro provider, l’interfaccia di programmazione rimane la stessa. Questo lo rende incredibilmente versatile e facile da integrare in progetti esistenti.

Caratteristiche Principali di Rebus

  • Astrazione del Trasporto: Rebus supporta una vasta gamma di sistemi di trasporto di messaggi (MSMQ, RabbitMQ, Azure Service Bus, Amazon SQS, ecc.), permettendoti di cambiare facilmente la tecnologia di coda senza modificare il codice dell’applicazione.
  • Serializzazione Flessibile: Puoi scegliere il formato di serializzazione che preferisci per i messaggi, come JSON (il default), XML o altri.
  • Gestione Automatica dei Retry: In caso di errori durante l’elaborazione di un messaggio, Rebus gestisce automaticamente i tentativi di elaborazione (retry) con una logica configurabile. Questo è fondamentale per la robustezza del sistema.
  • Dead-Letter Queue: I messaggi che falliscono in modo persistente dopo tutti i tentativi vengono spostati in una “dead-letter queue”, permettendoti di analizzarli e gestirli manualmente senza bloccare il sistema.
  • Integrazione con DI: Rebus si integra perfettamente con i più diffusi sistemi di Dependency Injection (DI) di .NET, facilitando la gestione delle dipendenze e la configurazione.
  • Semplice Modello Pub/Sub: Oltre all’invio di messaggi “one-to-one” (comando), Rebus implementa un modello Publish/Subscribe che ti consente di inviare messaggi a più consumer contemporaneamente, rendendolo ideale per la gestione di eventi.

Esempi di Codice

Vediamo come iniziare a usare Rebus con un semplice esempio che sfrutta RabbitMQ come trasporto.

Per prima cosa, installa i pacchetti NuGet necessari:

Bash

dotnet add package Rebus
dotnet add package Rebus.RabbitMq

1. Configurazione di Rebus e Invio di un Messaggio

La configurazione è il punto di partenza. Creiamo una classe MessageSender che si occuperà di inviare i messaggi.

C#

using Rebus.Config;
using Rebus.Routing.TypeBased;

public class MessageSender
{
    public static async Task SendOrderMessage()
    {
        // Configurazione di Rebus
        var bus = Configure.With(new BuiltinHandlerActivator())
            .Transport(t => t.UseRabbitMq("amqp://localhost", "my-app-queue"))
            .Routing(r => r.TypeBased().Map<OrderMessage>("my-app-queue"))
            .Start();

        // Messaggio da inviare
        var order = new OrderMessage
        {
            OrderId = Guid.NewGuid(),
            CustomerName = "Mario Rossi",
            Amount = 99.99m
        };

        Console.WriteLine($"Invio dell'ordine {order.OrderId}...");
        await bus.Send(order);

        Console.WriteLine("Messaggio inviato!");
        await bus.DisposeAsync();
    }
}

// Classe che rappresenta il nostro messaggio
public class OrderMessage
{
    public Guid OrderId { get; set; }
    public string CustomerName { get; set; }
    public decimal Amount { get; set; }
}

In questo esempio:

  • UseRabbitMq configura Rebus per usare RabbitMQ. Il primo parametro è la stringa di connessione e il secondo è il nome della coda principale.
  • Map<OrderMessage> mappa il tipo OrderMessage a una specifica coda.
  • await bus.Send(order) invia il nostro oggetto order alla coda. Rebus si occupa della serializzazione e di tutto il resto.

2. Gestione del Messaggio con un Handler

Ora, creiamo un’applicazione che riceve e gestisce il messaggio inviato. A questo scopo, abbiamo bisogno di un handler che implementa l’interfaccia IHandleMessages<T>.

C#

using Rebus.Bus;
using Rebus.Config;
using Rebus.Handlers;

public class OrderMessageHandler : IHandleMessages<OrderMessage>
{
    public Task Handle(OrderMessage message)
    {
        // Logica di business per gestire l'ordine
        Console.WriteLine($"Ricevuto l'ordine {message.OrderId} per {message.CustomerName} di {message.Amount} euro.");

        // Simuliamo un'operazione che richiede del tempo
        // E.g., salvare l'ordine nel database, inviare una notifica, etc.
        return Task.CompletedTask;
    }
}

public class MessageReceiver
{
    public static async Task StartReceiver()
    {
        var activator = new BuiltinHandlerActivator();

        // Registrazione dell'handler
        activator.Register(() => new OrderMessageHandler());

        // Configurazione e avvio del bus
        var bus = Configure.With(activator)
            .Transport(t => t.UseRabbitMq("amqp://localhost", "my-app-queue"))
            .Start();

        // Iscrizione alla coda
        await bus.Subscribe<OrderMessage>();

        Console.WriteLine("In ascolto di messaggi... Premi Invio per uscire.");
        Console.ReadLine();

        await bus.DisposeAsync();
    }
}

In questo secondo esempio:

  • BuiltinHandlerActivator è un semplice contenitore per la gestione degli handler.
  • activator.Register registra la nostra classe OrderMessageHandler come gestore per i messaggi di tipo OrderMessage.
  • Quando un messaggio OrderMessage arriva nella coda “my-app-queue”, Rebus lo deserializza e chiama il metodo Handle del nostro handler.

Perché Usare Rebus?

La vera forza di Rebus risiede nella sua semplicità e affidabilità. Permette di implementare modelli di comunicazione asincrona robusti e scalabili senza reinventare la ruota. La sua astrazione libera dai vincoli di una specifica tecnologia di coda, rendendo il codice più portabile e resiliente.

Se si sta costruendo un sistema distribuito e hai bisogno di un modo affidabile per far comunicare i tuoi servizi, Rebus è una scelta eccellente che merita di essere esplorata. Nel post successivo vedremo come interagire con la configurazione.