facebook

Blog

Resta aggiornato

Vediamo come Rebus ci aiuta ad utilizzare un Service Bus astraendo il trasporto sottostante
Risolviamo il “rebus” della comunicazione
mercoledì 16 Ottobre 2019

Negli articoli precedenti, abbiamo visto come fosse possibile disaccoppiare la comunicazione tra applicazioni utilizzando un Message Broker come RabbitMQ.

Quando un sistema software è composto da più servizi che svolgono compiti diversi ma che necessitano di interagire tra loro per eseguire elaborazioni sulle informazioni scambiate, è molto importante fare in modo di eliminare la comunicazione punto-punto ed utilizzare una strategia che le renda indipendenti tra loro, in particolar modo quando il numero di tali applicazioni o servizi tende ad essere molto elevato.

In questo scenario, può essere molto efficace l’adozione di un middleware che si occupi di astrarre la comunicazione tra i servizi coinvolti e che consenta, quindi, al nostro sistema software di essere scalabile in termini di componenti.
Tale soluzione prende il nome di Service Bus.

In letteratura, possiamo trovare moltissime definizioni, ma sinteticamente si può dire che un Service Bus è un sistema software che consente la comunicazione e la coordinazione tra servizi eterogenei mediante pattern noti per lo scambio di messaggi.

L’utilizzo di un’architettura del genere prevede che la comunicazione tra le entità coinvolte sia sotto la responsabilità del bus e che quindi i servizi coinvolti devono preoccuparsi solo di interfacciarsi col bus.
Un’implementazione interessante di un Service Bus per .Net è sicuramente Rebus.

Rebus è una libreria Open Source e viene definita dai proprio creatori in questo modo:

“a thing that in a transparent and distributed way enables communication between bounded contexts”

Tale libreria viene fornita come pacchetto NuGet ed è abbastanza immediato configurare la nostra applicazione per l’utilizzo di Rebus, in quanto viene fornita una Configuration API che ci aiuta ad istanziare il bus in pochi passaggi.
Di seguito possiamo vedere il minimo set necessario alla creazione di un bus funzionante:

var bus = Configure.With(handlerActivator)
    .Transport(chosenTransport)
    .Start();

Il metodo With() riceve come parametro un handler di messaggi. Questo handler può essere il BuiltinHandlerActivator fornito da Rebus oppure un Adapter per l’IoC Container utilizzato eventualmente nella nostra applicazione.
Il metodo Transport() consente di scegliere il meccanismo di trasporto e la modalità del bus che può essere:

  • Normal: modalità in cui il bus è capace di ricevere messaggi;
  • One-Way Client: modalità in cui il bus è solo capace di inviare messaggi.

Prima dello Start() ,che finalizza la configurazione del bus, possono essere aggiunte ulteriori configurazioni opzionali. Ad esempio, quella riguardante il Logging, utile per capire cosa succede nell’interazione mediante il bus, o come quella di Routing che serve a instradare in maniera corretta i messaggi verso la destinazione.

Vediamo adesso come utilizzare Rebus in un semplice esempio consistente in un’applicazione che crea un messaggio e lo invia verso il bus ed un’altra che consuma tale messaggio. Per questo esempio, vedremo tre possibili casi con i seguenti meccanismi di trasporto: MSMQ (Microsoft Message Queuing), RabbitMQ e ASB (Azure Service Bus).

Trasporto con MSMQ

Microsoft Message Queuing è una tecnologia di comunicazione asincrona basata su code messa a disposizione da Microsoft.
Per poter utilizzare MSMQ bisogna installarlo abilitandolo nelle Features di Windows all’interno del Pannello di Controllo.

Creiamo due Console Application, una chiamata Sender e l’altra Receiver ed installiamo Rebus e Rebus.Msmq come pacchetti NuGet:

Il seguente blocco di codice è relativo all’applicazione Sender:

namespace Sender
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var activator = new BuiltinHandlerActivator())
            {
                var message = new DemoMessage("Demo Message");
                var bus = Configure.With(activator)
                    .Transport(t => t.UseMsmq("Sender"))
                    .Routing(r => r.TypeBased().Map<DemoMessage> ("Receiver"))
                    .Start();
                 
                bus.Send(message).Wait();
                Console.ReadLine();
 
            }
        }
    }
}

Come possiamo vedere, in questo esempio, utilizziamo il BuiltinHandlerActivator fornito da Rebus.
Successivamente, creiamo un’istanza dalla classe DemoMessage, la quale contiene semplicemente un campo di tipo string che utilizzeremo come contenuto del messaggio.

Con Transport(t => t.UseMsmq(“Sender”)) dichiariamo l’utilizzo di MSMQ per il trasporto dei messaggi e la stringa “Sender” sarà il nome della coda che verrà creata automaticamente. Possiamo notare che, a differenza della configurazione base, troviamo la configurazione opzionale Routing. Il routing in Rebus serve a specificare dove dev’essere inviato il messaggio.
Il tipo di routing utilizzato nell’esempio è Type-Based, cioè il tipo di messaggio DemoMessage è destinato solo ad un determinato endpoint ed in particolare a quello che possiede la coda identificata dalla stringa “Receiver”. Infine, viene inviato il messaggio verso la destinazione:

Il seguente blocco di codice è relativo all’applicazione Receiver

namespace Receiver
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var activator = new BuiltinHandlerActivator())
            {
                activator.Handle<DemoMessage> (async msg =>
                {
                    Console.WriteLine($"Received: {msg.Text}");
                });
 
                Configure.With(activator)
                    .Transport(t => t.UseMsmq("Receiver"))
                    .Start();
                Console.WriteLine("Press [enter] to exit.");
                Console.ReadLine();
            }
        }
 
    }
}

Anche in questo caso, viene istanziato un handler per il tipo di messaggio DemoMessage. Nel Transport si dichiara l’utilizzo di MSMQ e il nome della coda “Receiver” che è la stessa a cui è destinato il messaggio del Sender.
Come mostrato nell’immagine seguente, verrà stampato correttamente il contenuto del messaggio.

Trasporto con RabbitMQ

Come abbiamo visto nell’articolo Disaccoppiare la comunicazione con RabbitMQ, RabbitMQ è un message broker che implementa il pattern Publish/Subscribe. Per poter utilizzare RabbitMQ con Rebus basta applicare delle piccole modifiche alle due applicazioni Sender e Receiver oltre ad installare il relativo pacchetto NuGet per il trasporto. Per quanto riguarda il Sender, andremo a modificare il tipo di Transport utilizzando il bus nella modalità One-way, in quanto siamo solo interessati a pubblicare il messaggio:

Transport(t => t.UseRabbitMqAsOneWayClient("amqp://localhost"))

La stringa passata come parametro della funzione rappresenta la connection string verso l’istanza di RabbitMQ in esecuzione.
Inoltre, il messaggio viene pubblicato sul bus mediante il metodo Publish() che sfrutta lo scambio di messaggi basato su Topic di RabbitMQ.

Il topic viene automaticamente definito in base al tipo del messaggio, come possiamo vedere in figura:

Anche per quanto riguarda il Receiver, ci sono poche modifiche da fare e sono relative al trasporto in quanto, dovendo ricevere il messaggio, dovremo utilizzare la modalità Normal del bus:

Transport(t => t.UseRabbitMq("amqp://localhost", "Receiver"))

Il primo parametro rappresenta la stringa di connessione ed il secondo invece la coda di input. Terminata la configurazione, sottoscriviamo il Receiver ai messaggi di tipo DemoMessage

bus.Subscribe<DemoMessage>().Wait();

Trasporto con Azure Service Bus

Tra i servizi cloud messi a disposizione da Azure vi è anche il broker di messaggi Azure Service Bus.
Per creare un’istanza di ASB cerchiamo il servizio nella Home di Azure:

Creiamo un nuovo namespace stando bene attenti a selezionare la versione Standard in quanto la versione Basic non supporta lo scambio di messaggi di tipo Publish/Subscribe basato su Topic:

Una volta eseguito il deploy del container, nella sezione Shared access policies possiamo ottenere la connection string da utilizzare nelle applicazioni Sender e Receiver.
Così, come nel caso di RabbitMQ, andremo solo a modificare il tipo di trasporto:

Transport(t => t.UseAzureServiceBus(azureServiceBusConnection,"Sender"))

e a sottoscrivere il Receiver ai messaggi di tipo DemoMessage.

Sender

Infine, sulla dashboard di Azure possiamo visualizzare grafici relativi alle richieste e ai messaggi scambiati, oltre che visualizzare le code ed i topic creati per la comunicazione tra le applicazioni.

Abbiamo visto come, utilizzando lo stesso codice e cambiando solo la tipologia di trasporto, sia essa su un broker di messaggi istanziato in locale o sul cloud, sia possibile fare interagire applicazioni mediante un Service Bus.

Rebus offre anche altre funzionalità come la modellazione dei Process Managers, conosciuti anche come Sagas. Una saga rappresenta qualcosa che evolve nel tempo a seconda di determinati trigger. È possibile, quindi, modellare tale evoluzione come una sorta di macchina a stati, le cui transizioni da uno stato all’altro sono scatenate dall’arrivo di determinati messaggi.

Tratteremo di questi argomenti nel prossimo articolo, ma intanto lascio il link al repository dove è possibile trovare i tre branch con gli esempi mostrati per MSMQ, RabbitMQ e ASB.

https://github.com/intersect88/RebusDemo