MOM
(Message Oriented Middleware)
Con
il termine Messaging si definisce un meccanismo che
permette la comunicazione asincrona tra client (detti
peer-element) mediante scambio di messaggi caratterizzato
da uno scarso grado di accoppiamento.
Data questa definizione si può considerare Messaging
System un qualsiasi sistema che scambia pacchetti TCP/IP
tra programmi client.
Piuttosto che creare un meccanismo di messaging specifico
per ogni situazione che richieda lo scambio di dati,
diversi vendors hanno creato un sistema che permette
di scambiare messaggi tra applicazioni diverse in modo
generico .
Questi sistemi sono chiamati Message Oriented Middleware
(MOM).
Mediante l'infrastruttura middleware del MOM processi
residenti su macchine diverse possono interagire tra
loro mediante l'invio e ricezione di messaggi tipicamente
asincroni.
E' importante notare che si è in presenza di
un sistema di messaging peer-to-peer : i client interagiscono
tra loro non in modo diretto bensì mediante l'interazione
di un messaging server.
Il messaging server fa le veci del "postino"
: riceve i messaggi dai produttori e li recapita ai
relativi destinatari (consumatori)."
Grazie
alla mediazione del messaging server i client risultano
essere disaccoppiati, cioè non devono sapere
nulla l'uno dell'altra.
Figura
1
Il disaccoppiamento nei MOM è una caratteristica
molto importante ed è maggiore rispetto ai sistemi
middleware basati su architettura ORB(Object Request
Broker).Si pensi ad esempio a RMI : un'applicazione
per poter comunicare con un oggetto remoto deve essere
a conoscenza dei suoi metodi per poterli invocare.
Nei sistemi MOM i client non interagiscono direttamente
tra di loro, nè il produttore nè il ricevitore
devono essere a conoscenza l'uno dell'altro, l'unica
cosa che entrambi devono sapere è il formato
del messaggio e la "casella di posta" (destination)
da usare.
Figura
2
Altra
importante caratteristica offerta dai sistemi MOM è
il concetto di Guaranteed Message Delivery (GMD) che
garantisce la consegna del messaggio.
Al fine di prevenire una perdita di informazioni in
caso di crash del message server, i messaggi devono
essere resi persistenti (ad es: mediante JDBC) prima
di essere recapitati ai consumatori.
Modelli
di messaging
Dopo questa breve introduzione sul MOM vediamo i suoi
due diversi modelli di messaging con i quali è
possibile scambiarsi i messaggi : Point to point(PTP),
Publish and Subcribe(Pub/Sub).
Point-To-Point
Nel
modello PTP un client può mandare uno o più
messaggi ad un altro client. La comunicazione, come
già sopra spiegato, non avviene in modo diretto
bensì condividendo una stessa destinazione dove
si inviano e prelevano i messaggi. La destinazione nel
modello Point-to-Point prende il nome di Queue (coda).
Piu' produttori e piu' consumatori possono condividere
la stessa coda e ogni messaggio ha un solo consumatore;
questo permette di fatto una comunicazione uno a uno.
Non ci sono dipendenze temporali tra sender e receiver
del messaggio e quindi il receiver può ricevere
il messaggio anche se non era in ascolto sulla coda
al momento dell'invio. Il
messaggio viene tolto dalla coda una volta che il ricevitore
l'ha prelevato confermando (acknowledge) la ricezione
al produttore. L'utilizzo del messaging PTP è
da utilizzare nel caso si abbia l'esigenza che ogni
messaggio spedito sia ricevuto (e confermato) da un
solo consumatore.
Figura 3
Publish/Subscribe
Nel
modello Publish/Subscribe (detto anche sistema di messaging
event-driven), i client inviano messaggi ad un topic:
una coda "speciale" in grado di inoltrare
i messaggi a piu' destinatari.
Il Sistema si prende carico di distribuire i messaggi
inviati ("pubblicati") da piu' publishers
i quali sono automaticamente ricevuti da tutti i client
sottoscritti ("subscribed"), cioè che
si sono dichiarati interessati alla ricezione. I client
possono essere dinamicamente o publisher o subscriber
o entrambi (come si vedrà nell'esempio pratico
del prossimo articolo).
Esiste una dipendenza temporale tra publisher e subscriber
visto che il receiver può consumare il messaggio
solo dopo essersi "sottoscritto" (subscription)
al topic d'interesse dopo che il client ha effettivamente
inviato il messaggio. Inoltre il receiver deve rimanere
attivo se vuole continuare a ricevere.
Il modello Pub/Sub permette quindi una comunicazione
uno a molti visto che ogni messaggio può avere
piu' consumatori. Il messaggio una volta consumato da
tutti i subscriber interessati viene tolto dal Topic.
Le API JMS rilassano i vincoli temporali mediante il
concetto di Durable Subscriptions. Le Durable subscriptions
permettono di ricevere i messaggi anche se i subscribers
non erano in ascolto poichè i messaggi vengono
resi persistenti, ad esempio mediante memorizzazione
su database.
Questi
messaggi vengono inoltrati una ed una sola volta e non
possono essere persi nè inoltrati una seconda
volta.
Figura 4
L'utilizzo di messaging Publish/Subscribe è da
utilizzare quando si ha la necessità che ogni
messaggio sia ricevuto da piu' consumatori. Le destinazioni
sono accessibili in modo concorrente e possono definire
anche dei filtri per esprimere le condizioni da rispettare
affinché un messaggio venga recapitato ad una
certa destinazione.
Cos'è
JMS ?
Java Message Service (JMS) è un'insieme di API
Java che permette di creare, spedire, ricevere e leggere
messaggi. JMS è stato sviluppato da SUN insieme
ai maggior produttori di sistemi MOM per definire un'interfaccia
comune indipendente dalla specifica implementazione
del sistema di messaging in modo analogo a quanto avviene
con JDBC e JNDI. L'applicazione Java JMS risulta così
essere indipendente, oltre che dal sistema operativo,
anche dalla specifica implementazione del sistema di
messaging.
Figura 5
Mediante
JMS è possibile interagire con sistemi MOM esistenti
quali MQSeries di IBM, TIB/Rendezvous di TIBCO Software,
Fiorano, ...
Gli attori JMS
In un'applicazione JMS gli attori coinvolti sono ConnectionFactory,
Destination, Connection, Session, MessageProducer, Message
e MessageConsumer come rappresentato in figura 6.
Figura 6
Di seguito viene descritto brevemente il compito di
ciascun attore JMS.
Administered
objects
Le Destination (Queue/Topic) ed i Connection Factory
sono detti Administered Objects perché incapsulano
le specifiche implementazioni dei Provider JMS.
Gli Administered objects sono vendor-dependent, cioè
la loro specifica implementazione varia da provider
a provider e per questo motivo non sono gestiti all'interno
del programma Java.
Le interfacce JMS permettono allo sviluppatore Java
di astrarsi dai dettagli implementativi della specifica
versione di MOM permettendo il riutilizzo del codice
al variare dell'implementazione JMS rispettando appieno
la filosofia Java.
La creazione del connection factory e delle destination
è compito dell'amministratore JMS che deve inserirli
all'interno di un contesto JNDI in un opportuno namespace
in modo da potere essere recuperati da qualsiasi client
mediante le normali API JNDI.
Un'applicazione JMS client, per inviare messaggi (Sender/Publisher)
o per riceverli (Receiver/Subscriber), utilizza i servizi
JNDI per ottenere un oggetto di tipo ConnectionFactory
e uno o piu' oggetti destination (Queue o Topic).
Connection
Factory
E' l'Administered Object utilizzato dal client per ottenere
una connessione con il message server.
Nel caso point-to-point si utilizza l'interfaccia QueueConnectionFactory
:
Context
ctx = new InitialContext();
QueueConnectionFactory QueueConnectionFactory
(QueueConnectionFactory)ctx.lookup("MyQueueConnectionFactory");
mentre
nel caso di Publish/Subscriber si utilizza l'interfaccia
TopicConnectionFactory
TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory)ctx.lookup("MyTopicConnectionFactory");
Destination
E' l'Administered Object che rappresenta l'astrazione
di una particolare destinazione.
Anche in questo caso il client identifica la destinazione
mediante l'utilizzo delle API JNDI.
Nel caso point-to-point la destinazione è rappresentata
dall'interfaccia Queue
Queue
queue = (Queue) jndiContext.lookup("MyQueue");
mentre
nel caso di Publish/Subscriber mediante l'interfaccia
Topic
Topic topic = (Topic) jndiContext.lookup("MyTopic");
Connection
Rappresenta l'astrazione di una connessione attiva con
un particolare JMS provider e si ottiene dall'oggetto
Connection Factory.
Nel caso point-to-point si ottiene un reference d'interfaccia
QueueConnection invocando il metodo createQueueConnection
sull'oggetto Connection Factory :
QueueConnection
queueConnection;
queueConnection
= queueConnectionFactory.createQueueConnection();
analogamente
nel caso di Publish/Subscriber si ottiene un reference
d'interfaccia TopicConnection invocando il metodo createTopicConnection
sull'oggetto Connection Factory
TopicConnection
topicConnection =
topicConnectionFactory.createTopicConnection();
Session
L'interfaccia Session si ottiene dall'oggetto Connection
e rappresenta il canale di comunicazione con una destinazione
e permette la creazione sia di produttori che di consumatori
di messaggi.
La creazione della sessione permette di specificare
il controllo della transazione e la modalità
di acknowledge.
Nel caso point-to-point la Session è identificata
mediante l'interfaccia QueueSession :
QueueSession
queueSession = queueConnection.createQueueSession(
false,Session.AUTO_ACKNOWLEDGE);
nel
caso di Publish/Subscriber mediante l'intefaccia TopicSession
:
TopicSession
topicSession = topicConnection.createTopicSession
(false,Session.AUTO_ACKNOWLEDGE);
Messages
I messaggi JMS sono costituiti da tre parti : un'intestazione
(message header), una serie di campi contenti proprietà
(property fields) ed il corpo (message body).
Figura 7
Il
message header è costituito esattamente da 10
campi che devono essere obbligatoriamente presenti.
Tali campi sono usati sia dai client che dai provider
al fine di identificare ed inoltrare il messaggio.
Tra questi è presente l'identificatore univoco
del pacchetto (JMSMessageID), il nome della Queue o
Topic a cui il pacchetto è destinato(JMSDestination),
la priorità (JMSPriority), il timestamp del tempo
d'inoltrto(JMSTimestamp), l'ID per collegare i messaggi
JMS tra di loro (JMSCorrelationID).
I property fields sono coppie di nome e valore e sono
utili per costruire "filtri" a livello applicativo.
La caratteristica di questi campi è che possono
essere esaminati mediante i message selectors.
Quando un consumatore si connette al server puo' definire
un message selector il quale esamina l'header e i property
fields (non il body message) del pacchetto JMS di una
specifica destinazione sia essa una Queue o un Topic.
Questo permette alle applicazioni JMS di utilizzare
i message selector per specificare quali messaggi è
interessata a ricevere utilizzando una sintassi condizionale
sottoinsieme del linguaggio SQL-92.
Il filtraggio avviene a livello di server e permette
di inoltrare ai client lungo la rete i messaggi strettamente
necessari o utili risparmiando così la banda
del canale.
Un message selector non è altro che un oggetto
di classe String che può contenere boolean literals
(TRUE, true, FALSE and false), operatori logici(NOT,
AND, OR),aritmetici(+,-,*,/),di comparazione(=, >,
>=, <, <=, <> ),di ricerca(IS,LIKE,IN,NOT
IN) ecc .. [JAPI].
Esempi di message selector :
"squadra = 'Pittsburgh Steelers'"
"squadra <> 'Dallas Cowboys'"
"age NOT BETWEEN 20 and 32"
"type = `FIAT' AND color = `blue' AND weight >=
100"
"NewsType = 'Opinion' OR NewsType = 'Sports'"
I
property field sono valorizzabili mediante i metodi
setObjectProperty e getObjectProperty.
Il metodo setObjectProperty accetta in ingresso valori
di classe Boolean, Byte, Short, Integer, Long, Float,
Double e String e se si cerca di utilizzare qualsiasi
altra classe viene generata l'eccezione JMSException.
Le proprietà possono essere lette mediante i
metodi get<Class>Property; nel caso di proprietà
inesistente i metodi getStringProperty e getObjectProperty
ritornano null, per le altre classi viene sollevata
una NullPointerException.
Infine le proprietà possono essere cancellate
mediante il metodo clearProperties, lasciando il messaggio
JMS con il campo PropertyFields vuoto.
Body
Message
I
messaggi JMS possono essere di varie tipi, ognuno dei
quali mette a disposizione gli appositi metodi get e
set.
JMS
non supporta uno specifico body per messaggi XML; in
questi casi è utilizzabile il text message.
I messaggi vengono creati mediante i metodi dell'interfaccia
Session dalla quale estendono sia l'interfaccia QueueSession
che TopicSession.
L'interfaccia mette a disposizione i metodi create relativi
ad ogni tipologia di message : createTextMessage,createObjectMessage,createMapMessage,createBytesMessage
e createStreamMessage prevedendo per ognuno di loro
la versione overloaded che prevede come parametro in
ingresso un oggetto di inizializzazione.
Per creare messaggi nel caso di tipologia Point-to-point
è sufficiente invocare il metodo create opportuno
sulla QueueSession, mentre nel caso di Publish/Subscribe
sull'oggetto TopicConnection come mostrato negli esempi
che seguono :
TextMessage
txtMsg,
txtMsg;
txtMsg
=
queueSession.createTextMessage();
txtMsg.setText("Hello JMS PTP World!");
txtMsg2
= queueSession.createTextMessage("Hello JMS PTP
World!");
ObjectMessage objMsg = queueSession.createObjectMessage();
ObjectMessage objMsg = topicSession.createObjectMessage();
MessageProducer
Il
MessageProducer è l'oggetto che permette l'invio
dei messaggi verso una particolare destinazione.
Utilizzando gli oggetti Session e Destination creati,
si possono inizializzare uno o piu' message producer.
Nel caso point-to-point il Message Producer per inviare
messaggi sulla Queue è referenziato mediante
l'interfaccia QueueSender :
QueueSender
queueSender = queueSession.createSender(queue);
mentre
nel caso di Publish/Subscriber l'invio dei messaggi
verso il Topic può avvenire mediante l'intefaccia
TopicPublisher :
TopicPublisher
topicPublisher= topicSession.createPublisher(topic);
Una
volta creato il produttore, basta invocare il metodo
send sull'oggetto QueueSender nel caso PTP mentre nel
caso Pub/Sub il metodo publish sull'oggetto topicPublisher;
entrambi i metodi send richiedono in ingresso un parametro
d'interfaccia Message e presentano versioni overloaded
[JAPI].
queueSender.send(message);
topicPublisher.publish(message);
MessageConsumer
Il MessageConsumer rappresenta un oggetto in grado di
ricevere messaggi.
Analogamente a quanto avviene per il Message Producer
bisogna indicare l'oggetto Session e la Destination
d'interesse per inizializzare correttamente l'oggetto
Message Consumer.
QueueReceiver
queueReceiver = queueSession.createReceiver(queue);
TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
Nel
caso Publish/Subscribe è inoltre possibile creare
le DurableSubscriber mediante il metodo createDurableSubscriber
sull'oggetto Topic Session.
Nella ricezione in modo asincrono il client definisce
un message listener (una classe che implementa l'interfaccia
javax.jms.MessageListener) e ogni qualvolta un messaggio
arriva alla destinazione d'interesse, il JMS provvede
ad invocare il metodo di callback onMessage.
Si deve creare come primo passo un listener
TopicListener topicListener = new TextListener();
e
passarlo come argomento al metodi setMessageListener
dell'oggetto Receiver
QueueReceiver.setMessageListener(topicListener);
topicSubscriber.setMessageListener(topicListener);
Connesso
il listener ci si pone in ascolto di ricevere e processare
i messaggi invocando il metodo start sull'ogetto Connection
:
queueConnection.start();
topicConnection.start();
La classe Listener è una normale classe Java
che implementa l'interfaccia MessageListener ridefinendo
perciò il metodo onMessage.
In tale metodo si mette la logica applicativa per gestire
il contenuto del messaggio ricevuto; si effettua il
controllo del tipo del messaggio ricevuto e se è
del tipo corretto se ne processa il contenuto.
public
class TextListener implements MessageListener {
public void onMessage(Message
message) {
try {
if (message
instanceof TextMessage) {
TextMessage
txtMsg = (TextMessage) message;
System.out.println("Ricevuto
: " + txtMsg.getText());
...
if (message
instanceof ObjectMessage) {
objMsg
= (ObjectMessage) message;
MyMsgClass
msg = (MyMsgClass)obj.getObject();
//
MyMsgClass è una classe proprietaria serializzabile
}
...
}
}
I
prodotti di Messaging sono intrinsecamente asincroni
in quanto produttore e consumatore del messaggio risultano
disaccoppiati, nonostante questo è bene specificare
che un MessageConsumer può ricevere i messaggi
anche in modo sincrono.
Nella ricezione dei messaggi in modo sincrono il consumatore
richiede esplicitamente alla destinazione di prelevare
il messaggio (fetch) invocando il metodo receive.
Il metodo receive appartiene all'interaccia javax.jms.MessageConsumer
(dalla quale estendono sia QueueReceiver che TopicReceiver)
ed èsopsensivo, cioè rimane bloccato fino
alla ricezione del messaggio, a meno che non si espliciti
un timeout finito il quale il metodo termina :
public
Message receive() throws JMSException
public Message receive(long timeout) throws JMSException
Esempio
di ricezione sincrona :
while(<condition>)
{
Message
m = queueReceiver.receive(1000);
if( (m!=null)&&(m instanceof
TextMessage) {
message
= (TextMessage) m;
System.out.println("Rx
:" + message.getText());
}
}
Finite
le operazione di gestione del messaggio ricevuto, si
effettua il cosiddetto codice di "pulizia"
chiudendo la connessione JMS mediante il metodo close.
queueConnection.close();
topicConnection.close();
Conclusioni
In questo articolo si è introdotto l'argomento
dei MOM e si è presentata una panoramica sulle
API JMS.
Nel prossimo articolo verrà presentato un esempio
completo di applicazione JMS che utilizza il modello
Publish/Subscribe e la modalità di ricezione
asincrona.
Bibliografia
[JMST]
JMS tutorial - http://java.sun.com/products/jms/tutorial/index.html
[JAPI] J2SDKEE API Documentation
[JMSS] JMS Specification (versione 1.0.2b) - http://java.sun.com/products/jms/docs.html
[BWLS] P. Gomez, P. Zadrozny - "Java 2 Enterprise
Editino with BEA Weblogic Server", Wrox "001
[CPJMS] P. Campanelli - "Java Message Service",
Computer Programming N. 105
|