EJB3 Message Driven Bean con JMS: Enterprise Patterns

La piattaforma Java Enterprise ci mette a disposizione innumerevoli strumenti che, almeno dalla versione 5, semplificano notevolmente la vita dello sviluppatore. Con estrema trasparenza è possibile gestire connessioni a database, transazioni distribuite, proteggere le risorse, pianificare task, eseguire richieste asincrone e altro ancora.

In tutte le guide introduttive su EJB3 che si trovano in rete si parla quasi esclusivamente di Session Beans di tipo Stateless e Stateful e si accenna a malapena all’esistenza dei Message Driven Bean (MDB), perché effettivamente non sembrano molto usati. A cosa serve un MDB? Spesso veniamo liquidati con risposte del tipo “serve a gestire chiamate asincrone al server”. Onestamente non mi sembra poco: è una risposta che apre mille scenari e che non ha fatto altro che incuriosirmi ancora di più! In questo post vedremo di approfondirne alcuni aspetti.

Architettura a messaggi

Abbiamo detto quindi che un MDB, a differenza di un Session Bean, permette ad un client di inviare chiamate al server che verranno gestite in modo asincrono rispetto alla chiamata. Il client cioè notifica una certa operazione al server inviando un messaggio. Il server inserisce i messaggi in un canale sulla quale stanno in ascolto gli MDB che prelevano il messaggio ed eseguono le operazioni richieste. Questo tipo di architettura prevede quindi uno strato applicativo chiamato Message Oriented Middleware (MOM).

Con una architettura di questo tipo però il client non verrà mai a sapere se il messaggio è stato ricevuto o se l’operazione richiesta è andata a buon fine. La cosa più interessante infatti sarebbe riuscire a riportare un messaggio di risposta al client contenente l’esito o il risultato di una certa operazione.

Java Message Service

La specifica su cui è basato lo scambio di messaggi nell’architettura Java EE è Java Message Service (JMS), i cui elementi principali sono:

  • JMS Broker: è il servizio che gestisce lo scambio dei messaggi;
  • JMS Producer: chi crea e invia un messaggio;
  • JMS Consumer: chi riceve un messaggio;
  • JMS Queue: canale di comunicazione point-to-point implementato come una coda FIFO in cui messaggi vengono letti e rimossi nell’ordine in cui sono stati inseriti;
  • JMS Topic: meccanismo per la distribuzione di tipo publish/subscribe di messaggi verso client che sono interessati ad un certo argomento (topic appunto);
  • JMS Message: oggetto (messaggio) che contiene i dati trasferiti.

JMS definisce 6 tipi di interfacce per i messaggi: una base e cinque sottotipi. I sottotipi di differenziano in base al payload, cioè al contenuto del messaggio.

  • Message: classe base usata per notifiche che non ha payload;
  • BytesMessage: il payload è memorizzato come array di bytes. E’ consigliato per scambiare XML in modo da non subire conversioni inutili;
  • TextMessage: il payload è memorizzato come stringa;
  • StreamMessage: uno stream è inteso come una sequenza di tipi primitivi Java;
  • MapMessage: messaggio contenente coppie di chiavi-valori dove le chiavi sono stringhe e i valori possono essere di qualsiasi tipo;
  • ObjectMessage: contiene oggetti Java Serializzabili;

Code e Topic

Utilizzando JMS è quindi possibile inviare messaggi direttamente ad un destinatario tramite un sistema di code o inviarli in broadcast a quei client interessati ad un certo topic.

Point-to-Point model

In questo modello uno a uno (PTP) il mittente (Client1) invia un messaggio verso una particolare coda e il ricevente (Client2) sta in ascolto e riceve i messaggi su quella coda. In questa architettura il mittente e il destinatario sono a conoscenza l’uno dell’altro: l’intento di Client1 è inviare un messaggio ad uno specifico destinatario che sarà l’unico a riceverlo, anche se al momento dell’invio non è disponibile. Sarà infatti il JMS Broker a tenerlo in coda finché non sarà prelevato da Client2.

Point-to-Point Model

Publish/Subscribe model

In questo modello è previsto che più subscribers siano interessati a ricevere messaggi di un certo topic inviati da un publisher. In questa architettura né il mittente, né i destinatari sono a conoscenza gli uni degli altri. Il JMS Broker tiene memoria del messaggio finché non è stato distribuito a tutti i destinatari attivi in un certo momento iscritti ad un certo topic. Nel caso di Durable Subscriptions, il messaggio viene conservato anche per i destinatari non attivi al momento dell’invio.

Publish/Subscribe Model

Concentriamoci adesso sul modello PTP: come si vede in figura, il mittente invia il messaggio su una certa coda e il destinatario la riceve, inviando un segnale di ACK al broker per conferma. Il mittente però non sa niente dell’esito della comunicazione, e spesso può andar bene così.

Immaginiamo però il caso in cui dobbiamo generare un report che coinvolge operazioni onerose che rallenterebbero (o addirittura manderebbero in timout) una normale chiamata web: la soluzione asincrona sembra quella più ovvia. Potremmo quindi inviare un messaggio ad una coda contenente tutte le informazioni necessarie alla generazione del report, ma non sapremmo come e quando aspettarci il risultato. Essendo un problema ricorrente, esiste un pattern enterprise che risolve il problema.

Point-to-Point model: Request/Reply Enterprise Pattern

Il modello PTP presentato precedentemente mostra un canale di comunicazione unidirezionale dal mittente al ricevente. Quando abbiamo bisogno di ricevere una risposta dal ricevente, come nel caso citato della generazione del report, è necessario predisporre un ulteriore canale di comunicazione su cui il mittente sta in ascolto per ricevere messaggi di risposta.

Request-Reply Pattern

Essendo le due code distinte e separate, come fa il Client1 a capire qual è la risposta al messaggio che ha inviato in coda precedentemente? La specifica JMS mette a disposizione due identificatori di messaggio:

  • Correlation ID: identificativo usato dai client per correlare un messaggio di risposta con quello della richiesta.
  • Message ID: identificativo univoco del messaggio assegnato dal broker nel momento in cui il mittente (Client1) invia il messaggio.

Cerchiamo di far luce sull’uso di questi ID. Si distinguono infatti due strategie che vengono identificate come 2 veri e propri sotto-pattern:

JMS Correlation ID Pattern
Se si sceglie questa soluzione, il messaggio di richiesta e risposta devono avere lo stesso corretlation id. Il mittente deve quindi popolare questo valore prima di inviare il messaggio alla coda. Il destinatario a sua volta, per rispettare il pattern, popola il correlation id del messaggio di risposta con lo stesso valore di quello del messaggio della richiesta. Il messaggio viene poi inviato ad una coda preconcordata tra i due.
JMS Message ID Pattern
Il riconoscimento dei messaggi è basato sul message id: il mittente, prima di inviare il messaggio, imposta la coda di risposta. Il destinatario userà questa informazione per sapere su quale coda inviare il messaggio di risposta e correlerà questo messaggio a quello ricevuto impostando il correlation id della risposta con il valore del message id che il mittente gli aveva inviato.

Riassumiamo quindi schematicamente le differenze tra i due approcci.

JMS Pattern Response Queue CorrelationID
Correlation ID Pattern Tutte le risposte vanno sulla stessa coda prefissata Il server copia il Correlation ID della richiesta nel Correlation ID della risposta
Message ID Pattern La risposta viene indirizzata dinamicamente alla coda specificata nel messaggio del mittente Il server copia il Message ID della richiesta nel Correlation ID della risposta

Conclusioni

In questo post abbiamo cercato di introdurre il mondo che sta dietro agli MDB, ovvero JMS e il sistema di code e topic, necessario per comprendere il funzionamento e la gestione delle chiamate asincrone. In definitiva, gli MDB non sono altro che listener in ascolto su una certa coda e quindi, a differenza dei session beans, non verranno mai invocati direttamente da un client. Quello che ci resta da mostrare è come un client invia un messaggio ad una coda! Prossimamente vedremo un po’ di codice che implementa i pattern mostrati.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+