Introduzione
Nella puntata precedente ([EJBAPPS-1]) è stata
fatta una introduzione per una tipica architettura applicativa
basata su EJB dando una prima visione di insieme dello
strato remoto basto su session beans.
Si è visto come, i vari componenti deployati
nel container EJB potessero interagire fra loro in modo
da realizzare lo strato di business logic (tramite i
session beans) ed il data model (entity beans).
In particolare si è potuto vedere come ladozione
del pattern session façade tramite uno o più
session beans di facciata potesse semplificare enormemente
la gestione sia della business logic remota che laccesso
in modo corretto ai bean dati.
Un
client per svolgere le operazioni di connessione ed
invocazione della logica remota in escuzione allinterno
di un container EJB, dovrebbe effettuare una serie di
operazioni ripetitive spesso non proprio semplici o
intuitive: la lookup delloggetto remoto, il marshalling
dei parametri remoti, la gestione delle eccezioni con
relativo wrapping.
Dal punto di vista del client questo genere di problematiche
e quindi le relative tecnologie coinvolte introduce
un livello di complessità e una maggiorazione
dei tempi e dei costi di sviluppo non giustificabili
nellottica di industrializzare il processo di
sviluppo.
Se lo strato client è una applicazione Swing,
Struts o peggio ancora una midlet in funzione in un
palmare o smart-phone, il team di sviluppo non vorrà
certo (o non potrebbe avere i mezzi o le conoscenze
per) gestire tutti gli aspetti relativi a gestione delle
transazioni, passaggio di parametri remoti, lookup,
JNDI etc
Nel corso degli anni sono stati quindi individuate alcune
soluzioni tipo unitamente ad alcuni pattern per isolare
gli strati applicativi luno rispetto allaltro
e semplificare l'assemblamento della architettura nel
suo complesso.
In questo caso viene preso in considerazione il pattern
Business Delegate (parente stretto di pattern analoghi
come il Command o il Proxy) il quale permette di replicare
i concetti di base della parte server side: isolamento,
limitazione dello scope applicativo al layer in esame,
maggior pulizia complessiva.
Limitare
il legame fra gli strati
Già nella puntata precedente si era cercato di
fornire un approccio di questo genere: se ad esempio
si desidera esporre un servizio tramite un session (façade
oppure no), si è potuto vedere come lutilizzo
di componenti stateful (che di fatto sono session beans,
ma concettualmente il discorso non cambia se si volesse
realizzare e utilizzare un qualsiasi componente server
side che mantiene il cosiddetto conversational state
con il client) permettano allo strato server di offrire
un supporto molto più potente; mantenere la sessione
però lega tali oggetti remoti allutilizzo
di un client che deve essere informato di tale sessione.
Più semplicemente si potrebbe dire che un client
che voglia utilizzare un session bean stateful dovrebbe
essere al corrente che tale componente mantiene i dati
di sessione, al fine di semplificare la gestione dei
dati e non incorrere in pericolosi disallineamenti dello
stato.
Viceversa realizzare una piattaforma server side che
offra servizi transazionali ma di tipo stateless, permette
un maggior riutilizzo e riadattabilità a scenari
differenti.
Lo
stesso approccio deve essere quindi portato anche sullo
strato client ed in particolare nel livello di intercomunicazione
client-server: i pattern server side (principalmente
Service Locator e Session Façade) vengono per
questo motivo affiancati da altrettanti pattern sul
layer client e per la gestione della comunicazione dei
dati fra i vari livelli. In figura 1 è riportata
questa schematizzazione
Figura 1 Il Business Delegate permette di
disaccoppiar il client dallo strato remoto di session.
In questo modo sono nascoste tutte quelle operazioni
tipiche di una conversazione remota,
non influenti per il corretto comportamento del client.
Dalla parte del cliente: il business
delegate
Il pattern che viene qui presentato, il Business Delegate,
ha come scopo principale quello di mascherare la complessità
legata alla intercomunicazione client-server RMI/EJB.
Per rendere più semplice la comprensione del
funzionamento di questo pattern verrà al solito
presa in esame una applicazione di esempio, MokaByte
CMS, della quale in questo momento non è necessrio
ai fini dell'articolo comprenderne il funzionamento
nel suo complesso.
Fra le varie funzioni che questa applicazione è
in grado di svolgere troviamo quella relativa alla creazione
e modifica degli articoli che poi verranno pubblicati
via web (o in altri formati come il PDF) secondo una
tipica logica di CMS.
Nello strato EJB di MokaByte CMS si trova quindi un
session façade ArticleManagerSFRemote un
session bean che svolge il compito di session façade
i cui metodi vengono invocati dallo strato client (una
applicazione web basata sul pattern MVC) per effettuare
le opportune operazioni relative alla gestione di articoli
del sito.
Tale session espone quindi alcuni metodi remoti tramite
la interfaccia remota ArticleManagerSFRemote della quale
qui di seguito è riportata una breve porzione
public
interface ArticleManagerSFRemote extends EJBObject {
public void createArticle(ArticleDTO articleDTO) throws
EJBException, RemoteException;
il metodo createArticle() viene invocato dallo strato
web per la creazione di un articolo. Il metodo riceve
in input un oggetto DTO del quale si avrà modo
di parlare in seguito.
Come da specifica linterfaccia remota rappresenta
linsieme delle funzionalità che un session
bean permette di eseguire da remoto.
Il business delegate rappresenta quindi un wrapper che
espone le stesse funzionalità delloggetto
remoto e tramite il quale il client può eseguire
le stesse operazioni ma in modalità locale.
Il business delegate svolge quindi due importanti operazioni:
si connette alloggetto remoto tramite una operazione
di lookup sul nome JNDI con cui il session viene registrato,
secondariamente espone una serie di metodi equivalenti
a quelli esposti dalla interfaccia remota del session.
Vediamo
prima la parte relativa alla connessione.
Il costruttore del BD riceve un oggetto properties contenente
i parametri di connessione JNDI. Il costruttore, nella
implementazione qui in oggetto invoca il metodo interno
al BD initializeArticleSFHome() il quale si preoccupa
di effettuare l'operazione di lookup sul nome remoto
e di memorizzare la home interface del session.
public
class ArticleManagerBD implements Serializable {
private ArticleManagerSFHome articleManagerSFHome;
private ArticleManagerSFRemote articleManagerSFRemote;
public ArticleManagerBD(Properties jndiProperties)
throws Exception {
initializeArticleSFHome(jndiProperties);
articleManagerSFRemote = articleManagerSFHome.create();
}
...
private
void initializeArticleSFHome(Properties jndiProperties)
throws Exception {
String FACADE_NAME = "ArticleManagerSF";
Class FACADE_CLASS = com.mokabyte.cms.ejb.ArticleManagerSFHome.class;
if (articleManagerSFHome ==
null) {
try {
ServiceLocator
locator = ServiceLocator.getInstance(jndiProperties);
articleManagerSFHome
= (ArticleManagerSFHome) locator.getEjbHome(FACADE_NAME,
FACADE_CLASS);
if (articleManagerSFHome
== null) {
throw
new Exception("Did not get home for " + FACADE_NAME);
}
} catch (ServiceLocatorException
e) {
throw
new Exception(e.getMessage());
}
}
}
Come
si potrà notare il BD contiene due variabili
di classe che rappresentano i riferimenti alla home
e remote del session.
In questo particolare momento (ovvero subito dopo l'inizializzazione
della home), per come funziona la comunicazione client
server, il BD non ha ancora instaurato nessun legame
definitivo con il container; la home rappresenta infatti
il factory tramite il quale ricavare loggetto
remoto e non il puntatore alloggetto remoto.
nota:
la scelta di implementare un BD serializzabile è
legata alla possibilità di utilizzare tale oggetto
in una sessione HTTP. Come noto gli oggetti serializzabili
sono gestiti meglio dal container web che ad esempio
è in grado di farli migrare in caso di una sessione
distribuita tipoca di una architettura cluster.
Appare
evidente che il passaggio importante è quello
relativo alla creazione della remote a partire dalla
home.
Per svolgere questa operazione si possono seguire due
strade: la prima ha come obiettivo primario la massimizzazione
delle prestazioni, riducendo fra le altre cose le operazioni
inutili; in questo caso si effettua l'istanziazione
della remote direttamente nel metodo initializeArticleSFHome()
.
E'
in questo particolare momento che il BD viene associato
ad un particolare stub associato alla interfaccia remota.
Anche se questo risulta particolarmente vero solo in
caso di un session stateful, è comunque vero
che il reference alla remote rimarrebbe memorizzato
nella classe una nuova creazione del BD potrà
forzare la riconessione al server remoto.
Questo legame stretto non è da considerarsi un
problema per una possibile caduta della connessione
fra client e server, ma piuttosto potrebbe essere fonte
di malfunzionamenti o cattiva gestione in uno scenario
clustered. Come si è avuto modo di evidenziare
in [MB-CLUSTER] infatti gli application server EJB in
configurazione cluster spesso realizzano meccanismi
di cache distribuita e di pooling geografico degli EjbObject
associati ad un interfaccia remota. In questo caso lutilizzo
di un reference fisso della remote interface di fatto
blocca meccanismi come il round robin della prima reference
disponibile e simili. Nel caso in cui un oggetto non
sia più disponibile lapplication server
non potrà effettuare le debite operazioni di
ricerca del sostituto nel cluster e il client dovrà
ripartire da capo nel suo vorkflow applicativo; ad esempio
ripetere il login e riprendere dalla pagina iniziale
una eventuale operazione di acquisto ondine (ovviamente
il client deve essere opportunamente scritto per gestire
queste casistiche e in particolare essere in grado di
operare con le eccezioni che arrivano dallo strato server).
Non è il caso di approfondire ulteriormente tali
aspetti in questa sede (si veda la bibliografia) ma
per onor di sintesi si potrebbe semplicemente dire che
la prima soluzione (dove il BD memorizza sia la home
che la remote durante la fase di inizializzazione ovvero
nel costruttore delloggetto) è da scegliere
nel caso in cui si lobbietivo principale siano
le performance e non si desideri preoccuparsi troppo
di cosa possa accadere in caso di errore in uno scenario
cluster (ma nella peggiore delle ipotesi leffetto
è quello che il client perde lo stato della sessione
e deve ricominciare dallinizio la procedura).
La seconda strada (che verrà mostrata negli esempi
che seguono) è certamente più conservativa
e probabilmente leggermente meno performante. Personalmente
reputo che il degrado delle prestazioni sia in questo
caso ininfluente, tanto da far preferire per una soluzione
certamente più stabile al variare dello scenario
di deploy.
Dopo la procedura di inizializzazione allinterno
del BD ha a disposizione il reference alla home interface
(ed eventualmente anche quello alla remote).
A questo punto per ogni metodo presente nella remote
interface del session bean, se ne dovrà
scrivere uno nella classe BD.
Ad esempio si potrebbe pensare di creare nel BD un metodo
wrapper del corrispondente sul session:
public
void removeArticle(String id) throws RemoteException
{
articleManagerSFRemote.removeArticle(id);
}
per
cui un client per creare un articolo remoto, potrà
utilizzare il BD ignaro della presenza di un layer remoto
//
si carica il file jndi.properties contenente i parametri
di connessione
// al context EJB
Properties jndiProperties = new Properties();
jndiProperties.load(new FileInputStream ("jndi.properties"));
System.out.println("Caricato
il file jndi.properties "+jndiProperties);
ArticleManagerBD
aticleManagerBD = new ArticleManagerBD(jndiProperties);
try{
articleManagerBD.createArticle(articleVO.getDTO());
}
catch(RemoteException re){
// Gestione della eccezione remota
...
}
Come si può notare, dato che linvocazione
remota obbliga alla gestione di una RemoteException
questa versione del BD soffre di due importanti limitazioni.
Quando un client scrive
articleManagerBD.createArticle(articleVO.getDTO());
deve di fatto premunirsi dall'insorgere di una eventuale
RemoteException, e deve gestirla opportunamente. Se
questo è in prima analisi un aspetto scomodo
ad una analisi più attenta ci si può rendere
conto di quanto sia errato progettualmente: infatti
una midlet per palmare o una applicazione Swing non
hanno interesse a conoscere i dettagli o le motivazioni
che hanno causato il problema. Inoltre obbligano lo
strato client a importare il package javax.rmi e quindi
si devono legare allo strato RMI/EJB.
Questo è un grave errore che va contro la filosofia
di disaccoppiamento del BD. Se a esempio volessimo nascondere
dietro il delegate un tipo di server differente (esempio
un web service) si sarebbe costretti a riscrivere il
codice sul client: è questa la negazione della
programmazione per pattern e la violazione del concetto
di disaccoppiamento.
Con poche modifiche si può pensare di utilizzare
una versione leggermente differente dei metodi di wrap
effettuando anche il wrapping della eccezione.
public
void createArticle(ArticleDTO articleDTO) throws FacadeInvocationException
{
try {
articleManagerSFRemote.createArticle(articleDTO);
} catch (RemoteException re) {
String msg = "Errore di
invocazione remota sul metodo di façade createArticle()
\n" + re;
throw new FacadeInvocationException(msg);
}
}
In questo caso si può notare come la remoteException
sia bloccata e rilanciata sotto forma di FaçadeInvocationException,
una eccezione applicativa appositamente creata dal programmatore
(non esiste nel JDK) al momento della creazione del
BD.
Questa soluzione offre due vantaggi: per prima cosa
il wrapping della eccezione permette di modificare a
proprio piacimento cosa e come
notificare il cliente di un eventuale problema avvenuto
sul server.
Secondariamente offre una completa separazione degli
strati. Il nome della FaçadeInvocationException
suggerisce infatti che si è verificato un problema
relativo alla invocazione di qualcosa che sta là
dietro: non è importante per il cliente
sapere cosa ci sia là dietro. Per concludere
si noti come allinterno del metodo initializeArticleSFHome
sia stata inserita la riga
ServiceLocator
locator = ServiceLocator.getInstance(jndiProperties);
Loggetto ServiceLocator è stato presentato
nella precedente puntata della serie e serve per realizzare
la connessione vera e propria verso lo strato remoto.
Il metodo getInstance(), che riceve i parametri di connessione
incapsulati allinterno di un properties, al suo
interno da qualche parte effettuerà la connessione
al context JNDI del container. Ricordando quanto già
visto in precedenza si potrebbe scrivere
public
static synchronized ServiceLocator getInstance(Properties
jndiProperties)
throws
ServiceLocatorException {
if
(serviceLocator == null) {
serviceLocator
= new ServiceLocator(jndiProperties);
}
return
serviceLocator;
}
private
Context getInitialContext(Properties jndiProperties)
throws NamingException {
if (jndiProperties == null){
Context context = new InitialContext();
}
Context context = new InitialContext(jndiProperties);
return context;
}
Nel
caso in cui il client sia eseguito allinterno
della stessa JVM del container EJB (es. una web application
deployata nello stesso file .ear che contiene anche
la parte EJB), il parametro jndiProperties potrebbe
essere tranquillamente impostato a null in modo
da eseguire la InitialContext() senza parametri (in
questo caso lapplication server ridirige le operazioni
di lookup verso il context di default).
Per onor di completezza invece si consideri il caso
in cui si voglia esplicitamente utilizzare i parametri
di connessioni JNDI: in questo caso sarà sufficiente
provvedere a caricare un file jndi.properties da qualche
parte. La specifica dice che il client può effettuare
la connessione se il file è presente nel classpath
del client, ma forse la prudenza non è mai troppa.
Meglio procedere al caricamento forzato di tale file.
Ecco un esempio di jndi.properties preso dagli esempi
di JBoss.
###
JBossNS client properties for connection from the localhost
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://localhost:1099/
#
The jnp protocol socket factory class
jnp.socketFactory=org.jnp.interfaces.TimedSocketFactory
#
The TimedSocketFactory connection timeout in milliseconds(0
== blocking)
#jnp.timeout=0
#
The TimedSocketFactory read timeout in milliseconds(0
== blocking)
#jnp.sotimeout=0
Conclusione
In questo articolo è stato presentato un altro
piccolo componente indispensabile per la buona progettazione
di una applicazione a strati. Il prossimo articolo sarà
dedicato ad uno degli aspetti tecnologicamente meno
sconvolgenti, il pattern DTO, ma che ricopre spesso
una importanza trategica nella buona riuscita di un
progetto enterprise.
Bibliografia
[EJBAPPS-1]
"Architetture e tecniche di progettazione EJB -
I parte: architettura di una applicazione tipo"
di Giovanni Puliti http://www.mokabyte.it/2005/07/ejbarchitectures-1.htm
[MB-CLUSTER] - "Clustering di applicazioni J2EE
J2EE Clustering con JBoss - I parte: definizioni e configurazione"
di Giovanni Puliti
http://www.mokabyte.it/2004/10/clustering-1.htm e seguenti
|