|
Introduzione
Lo scopo di questa nuova serie di articoli è
quella di guidare il lettore nell'apprendimento delle
tecniche di base necessarie per progettare e realizzare
una comune architettura EJB basata su session ed entity
beans. Prendendo spunto da una applicazione di riferimento
verrà mostrata quale sia l'approccio migliore
per strutturare i vari strati applicativi e come implementare
la comunicazione e lo scambio dati fra i vari layer.
L'obiettivo finale non è tanto quello di introdurre
alla teoria di base di EJB, per la quale si può
far riferimento con efficacia ai titoli riportati in
bibliografia ([EJB] e [MB]), ma piuttosto come utilizzare
la tecnologia individuando in modo opportuno i vari
componenti e progettando nel migliore dei modi la architettura
complessiva.
Lo scenario che si andrà a raffigurare è
quello riportato in figura 1: un generico client (applicazione
web, Struts, Swing o altro) tramite l'invocazione remota
dei metodi di un session bean di front end potrà
sfruttare la business logic contenuta nello strato EJB,
ed agire in modo indiretto sui dati rappresentati dai
vari entity beans.

Figura 1 - schema di massima della architettura
EJB in esame
Data
la vastità degli argomenti e dei punti salienti
presenti in una architettura di questo genere è
certamente impossibile affrontare in modo dettagliato
ogni singolo aspetto della tecnologia e delle possibili
soluzioni.
E' sempre bene tenere a mente che il taglio offerto
è espressamente di tipo pratico, e per questo
alcune assunzioni sono frutto dell'esperienza sul campo
e non hanno la pretesa di aderire in modo stretto alle
indicazioni della teoria.
Ogni variazione sul tema finalizzata ad adattamenti
a casi specifici o inclusioni di tecnologie alternative
(esempio Hibernate al posto di CMP) sono incoraggiate
al fine di sperimentare altre strade o possibilità.
Alcune
ipotesi di partenza
Nel momento in cui si procede nella realizzazione di
una applicazione J2EE più o meno complessa è
necessario introdurre alcune semplificazioni o vincoli
strutturali al fine di rendere realistica l'implementazione
finale.
E' ormai statisticamente dimostrato che ogni programmatore
o progettista incontri maggiori difficoltà nella
risoluzione di determinati problemi, in genere legati
a le medesime problematiche: come stratificare l'applicazione,
come mettere in comunicazione i vari componenti e strati,
quali componenti utilizzare, quale framework di persistenza
utilizzare, come gestire le eccezioni ed i log applicativi.
Questi quesiti spesso non trovano una sola risposta
e tantomeno è possibile appellarsi al "super
pattern" o la mirabolante "best practice",
ma è necessario realizzare una serie di indagini
progettuali, sulle specifiche funzionali e requisiti
applicativi per poter trovare la soluzione che si adatta
al meglio al caso in questione.
Di seguito sono elencati alcuni dei punti dove tipicamente
il progettista J2EE incontra i maggiori dubbi: le ipotesi
ed assunzioni fatte sono in parte frutto dell'esperienza
comune, in parte sono dettate dal buon senso, in parte
sono legate a scelte di carattere editoriale (per dovere
di sintesi e di chiarezza non avrebbe avuto senso affrontare
ogni possibile casistica).
Alcune scelte ricalcano la struttura della applicazione
presa in esame.
Il
framework di persistenza
L'applicazione prevede per lo strato di persistenza
l'utilizzo del framework CMP, che in questo momento
storico è causa di accese discussioni sui vari
forum web tanto che si sono formate vere e proprie scuole
di pensiero pro e contro l'utilizzo di entity beans
(contrapposti a framework di mapping alternativi come
JDO, Hibernate o semplici DAO).
La scelta qui fatta (utilizzare CMP) non è espressione
diretta di una preferenza a scapito di altre tecnologie,
ma piuttosto segue quella che è l'impostazione
di questa serie di articoli: fornire gli strumenti per
comprendere ed imparare a realizzare applicazioni EJB.
Mantenimento
dello stato conversazionale fra strati
E' spesso impossibile stabilire un elevato numero di
assunzioni sulla tipologia del client e sulla sua natura.
Per questo motivo è bene cercare di non vincolare
troppo la modalità operativa dello strato EJB.
Questa valutazione generica porta ad una serie di considerazioni
pratiche che aiutano nella scelta delle varie soluzioni.
Ecco di seguito alcune considerazioni riportate in ordine
più o meno sparso.
L'invocazione remota di un session bean è una
operazione inefficiente e quindi la tendenza è
quella di limitare il più possibile il suo utilizzo.
Questa tematica si ripercuote fra le altre cose anche
sulla modalità utilizzata per il mantenimento
della sessione.
L'utilizzo di session beans stateful è una scelta
forte che semplifica molto la parte relativa al mantenimento
dello stato ma che può risultare controproducente
nel momento in cui lo strato client sia di tipo web:
in questo caso infatti si rende necessario tenere traccia
dello stato su due layer differenti con ovvi problemi
di sincronizzazione o nel migliore dei casi con una
replicazione inutile di informazioni (concernenti lo
stato appunto).
Molte possono essere le strade da seguire e le varie
alternative spaziano da quella che si potrebbe definire
EJB blackbox (tutto il lavoro di business così
come il mantenimento della sessione di lavoro è
nascosto nello strato EJB che espone alcuni metodi invocati
dal thin-client) ad una strategia opposta in cui lo
strato EJB non mantiene sessione ed anzi espone metodi
che sono utilizzati dal client come servizi necessari
per svolgere i comuni compiti di busines logic.
Nel primo caso ogni chiamata remota da parte del client
su un metodo remoto corrisponde ad un macro use case
di alto livello: il client ignora le possibili implicazioni
della invocazione di un ipotetico metodoRemoto(), la
sequenza effettiva delle operazioni e conseguenza ad
esso associato.
In questo modo si sposa completamente la filosofia del
thin client con ovvi vantaggi per quanto riguarda le
prestazioni (si riduce al massimo il flusso di invocazioni
e dei dati fra client e parte EJB).
E' ovvio che l'approccio black box pur offrendo innegabili
vantaggi dal punto di vista delle prestazioni, potrebbe
non essere sufficientemente flessibile nel caso in cui
si volesse avere una interazione a grana più
fina.
Con la soluzione alternativa è compito del client
decidere quali metodi invocare sullo strato EJB, la
sequenza esatta delle invocazioni e valutare il da farsi
in funzione dei risultati ottenuti.
Questa soluzione certamente più flessibile ha
come controindicazione una eccessivo traffico di dati
in rete nonché uno spostamento non giustificato
della responsabilità sul client che proprio in
considerazione di quanto detto in precedenza (thin client)
potrebbe essere non adeguato a prendere determinate
decisioni.
E'
spesso impossibile poter dare una risposta definitiva
ed universalmente valida data la moltitudine di aspetti
e requisiti che caratterizzano e differenziano ogni
caso specifico ed ogni applicazione.
La tendenza per questo motivo è quella di dare
sempre una impostazione che sia ragionevolmente valida
per la maggior parte dei casi, consci del fatto che
di volta in volta adattamenti e personalizzazioni sono
sempre necessarie.
Nel caso specifico è bene fornire sempre, almeno
in prima istanza, una implementazione che sia una media
fra il modello black-box e quello service provider,
lasciando ai raffinamenti successivi (o iterazioni come
nel processo RUP, vedi [RUP]) le personalizzazioni in
una o nell'altra direzione.
Personalmente, adottando una scelta forse un po' forte,
prediligo utilizzare session bean di tipo stateless
che offrano al client non macro funzionalità
(macro-usecase) ma operazioni di medio livello relative
alla ricerca, modifica ed assemblaggio dei dati elementari
da esporre poi come DTO più o meno articolati.
Si immagini il caso in cui una Action Struts debba effettuare
una ricerca di alcune informazioni strutturate per poi
permettere la composizione di una pagina JSP; si immagini
che tali informazioni non siano banalmente ottenibili
con una semplice ricerca, ma che implichino la composizione
di un aggregato di dati tramite una o più di
una operazione di ricerca, modifica e trasformazione.
In tale scenario lo strato session espone i metodi necessari
per fornire al client tale aggregato di dati mascherando
come esso sia stato ottenuto. Il client dal canto suo
prende in consegna tali informazioni, mantenendole in
sessione ed eventualmente aggregandole con altre per
permettere la composizione della pagina JSP.
DTO
leggeri o pesanti? Custom o standard?
La stratificazione delle applicazioni J2EE introduce
nelle nostre applicazioni una serie non banale di benefici
volti a semplificare le operazioni di stesura del codice,
di separazione dei contesti e di manutenzione o rifattorizzazione
successiva.
Questi vantaggi hanno in genere un costo che si paga
in termini di complessità delle procedure di
invocazione e soprattutto di migrazione dei dati fra
i vari strati.
Il pattern Data Transfert Object (DTO), dice che per
ogni operazione di invocazione fra uno strato e l'altro
e fra un componente e l'altro, i dati non dovrebbero
fluire in modo sciolto ma dovrebbero sempre essere aggregati
in appositi contenitori di informazioni. Per questo
motivo per spostare le informazioni relative ad un articolo
si dovrebbe sempre utilizzare un oggetto apposito che
contenga tutte le informazioni relative così
come i dati ad esso associati.
Molte sono le scelte da eseguire nel momento in cui
si decide di realizzare il set dei DTO: utilizzare DTO
pesanti (ovvero che contengano tutte le informazioni
di una determinata entità) o leggeri (spostare
solo le informazioni strettamente necessarie relativamente
ad un determinato use case); creare DTO custom (classi
appositamente create) o utilizzare contenitori standard
del JDK (Hashatable, Vector, List etc
).
Definire DTO di primo livello che non inglobino i DTO
dipendenti (es. se devo spostare un DTO utente ci si
deve chiedere se ha senso includere al suo interno anche
tutti i DTO delle varie anagrafiche, profili utente
etc
).
Data la vastità dell'argomento esso verrà
esaustivamente affrontato in un articolo apposito che
pubblicheremo uno dei prossimi mesi.
Invocazione local vs remote
Anche se già da tempo ogni produttore di application
server inseriva all'interno dei propri prodotti tecniche
di ottimizzazione per le invocazioni in-JVM e in-container,
con l'introduzione delle interfacce locali nella specifica
ufficiale EJB, il progettista ha a disposizione uno
strumento in più per poter invocare componenti
remoti deployati all'interno del container.
Le possibili soluzioni in tale ambito sono in genere
piuttosto semplici, tanto che non è lasciato
molto spazio alla fantasia. Nella maggior parte dei
casi, così come nella applicazione in esame si
organizzano gli strati in modo che il client EJB parli
con lo strato server EJB per mezzo di un session di
front-end (pattern session façade) per mezzo
di invocazioni remote.
Il session façade a sua volta comunica con gli
altri session e con gli entity beans per mezzo di invocazioni
locali (più efficienti).
Il client non ha possibilità di comunicare con
altri session bean o con gli entity beans della applicazione
remota: questa soluzione è frutto di una precisa
scelta progettuale frutto di molteplici considerazioni.

Figura
2
- Il session façade funge da centrale di comunicazione
per le invocazione remote con il client e "parla"
con invocazioni locali con gli altri beans. Session
locali così come gli entities non sono accessibili
dal client
Per
quanto riguarda la centralizzazione delle invocazioni
in un unico bean di front end, questa soluzione permette
di sfruttare tutti i benefici tipici del pattern session
façade.
Ad esempio il client non viene informato sulla logica
organizzativa ed applicativa della parte sottostante
e per questo non sono necessarie modifiche estese se
si decide di cambiare qualcosa sotto il façade.
Parallelamente tutte le politiche di accesso, sicurezza,
ottimizzazione del flusso dei dati e quant'altro sono
concentrate in un solo punto.
Infine da un punto di vista dell'analisi funzionale,
il client deve poter utilizzare use-case di alto livello
per svolgere il suo lavoro quotidiano, mentre non dovrebbe
mai accedere a funzionalità a grana più
fine offerte dai session bean sottostanti il façade.
Per un motivo del tutto analogo una applicazione client
EJB non dovrebbe mai accedere ad un rappresentante dei
dati (in questo caso si è scelto il framework
entity beans CMP, ma discorso analogo se si volesse
adottare Hibernate, JDO o DAO). I dati sono un patrimonio
importante tanto che non deve essere esposta al client
la modalità di aggregazione, modifica, variazione
etc.
I
pattern coinvolti
Durante questa trattazione verranno presentati alcuni
dei più famosi pattern J2EE utili per la aggregazione
degli strati applicativi e per la comunicazione dei
vari componenti.
Fra questi troviamo certamente il Session Façade
per l'isolamento dello strato server side e l'accentramento
delle chiamate RMI, pattern che in genere è utilizzato
in stretto contatto con il Business Delegate il quale
offre al client una visione semplificata della invocazione
remota RMI.
Come accennato in precedenza il DTO è probabilmente
il pattern più importante relativamente al trasferimento
dei dati.
Data la natura intrinsecamente distribuita multistrato
e genericamente eterogenea dei soggetti coinvolti nella
applicazione è di grande utilità poter
disporre di un servizio centralizzato ed automatizzato
per ricavare le varie risorse del sistema. Il Service
Locator è il componente che svolge questo compito.
Al suo interno spesso si nascondono le operazioni di
lookup JNDI, e si possono inserire in modo del tutto
trasparente tecniche di cache o pool per migliorare
le prestazioni del sistema.
Ovviamente pattern "spiccioli" come Factory
Singleton e proxy sono un po' ovunque utilizzati e data
la loro popolarità non verranno presentati in
modo esaustivo, rimandando alla bibliografia per maggiori
approfondimenti.
Service
Locator: la localizzazione delle risorse locali e remote
Prima di procedere con la analisi dei vari elementi
che compongono la parte EJB è utile presentare
un oggetto molto importante, sia per il funzionamento
della parte server che del client.
Il componente in questione è basato sul famoso
pattern Service Locator. La classe ServiceLocator viene
utilizzata sia all'interno del client per ricavare le
interfacce remote di un session bean deployato nell'application
server, ma è usato anche da vari session beans
per ricavare le interfacce locali di altri session o
entities.
I due metodi principali che la classe offre sono i getEjbHome()
e getEjbLocalHome() tramite i quali è possibile
ricavare le home interface di bean remoti o locali e
da queste poi procedere alla creazione delle interfacce
local e remote vere e proprie.
Il metodo getEjbHome() consente di ricavare l'interfaccia
home remota di un bean deployato all'interno dell'application
server e il cui nome sia corrispondente a quello passato
come parametro.
Analogamente il metodo getEjbLocalHome() consente di
ricavare con una semplice operazione di lookup la local
home interface come oggetto registrato presso il JNDI
tree dell'application server. Questa differenza implica
che nel primo caso è necessaria una vera e propria
operazione di ricerca remota su un albero secondo la
tipica logica RMI/IIOP, con tanto di narrowing e cast
esplicito: per questo al metodo viene anche passata
la classe sulla quale verrà invocata la conversione
di casti in tipico stile RMI/IIOP. Ecco il metodo relativo
alla versione remota:
public
static EJBHome getEjbHome(String ejbName, Class ejbClass) throws
ServiceLocatorException {
try {
Object object = context.lookup(ejbName);
EJBHome ejbHome = null;
ejbHome = (EJBHome) PortableRemoteObject.narrow(object,
ejbClass);
if (ejbHome == null) {
throw new ServiceLocatorException("Could
not get home for " + ejbName);
}
return ejbHome;
}
catch (NamingException ne) {
throw new ServiceLocatorException(ne.getMessage());
}
}
ed
il corrispondente relativo alla lookup della interfaccia
locale:
public
static EJBLocalHome getEjbLocalHome(String ejbName)
throws ServiceLocatorException {
try {
Object object = context.lookup(ejbName);
EJBLocalHome ejbLocalHome =
null;
ejbLocalHome = (EJBLocalHome)
object;
if (ejbLocalHome == null) {
throw new ServiceLocatorException("Could
not get local home for " + ejbName);
}
return ejbLocalHome;
}
catch (NamingException ne) {
throw new ServiceLocatorException(ne.getMessage());
}
}
Elemento
chiave della classe è la procedura di inizializzazione
del context JNDI presso il quale effettuare la chiamata
remota. Il costruttore è fornito in due versioni
con e senza parametri. Nel secondo caso verrà
effettuata una chiamata al metodo getInitialContext()
senza parametri (quindi eseguendo la connessione verso
il JNDI tree di default: questo in genere corrisponde
a quello messo a disposizione dall'application server
entro la quale viene eseguita l'applicazione chiamante
oppure quello che può essere ricavato in modo
automatico perché attivo sull'host locale).
Il costruttore con parametri invoca il getInitialContext()
passando tutti i parametri di connessione JNDI. Ecco
le due implementazioni dei due costruttori:
protected
ServiceLocator(Properties jndiProperties) throws ServiceLocatorException
{
try
{
context
= getInitialContext(jndiProperties);
}
catch
(Exception e) {
throw
new ServiceLocatorException(e.getMessage());
}
}
protected
ServiceLocator() throws ServiceLocatorException {
try {
context = getInitialContext();
}
catch (NamingException e) {
throw new ServiceLocatorException(e.getMessage());
}
}
entrambi i costruttori sono protetti in quanto le istanze
di ServiceLocator verranno ricavate tramite i metodi
public
static synchronized ServiceLocator getInstance() throws
ServiceLocatorException {
if (serviceLocator == null) {
serviceLocator = new ServiceLocator();
}
return serviceLocator;
}
public
static synchronized ServiceLocator getInstance(Properties
jndiProperties)
throws ServiceLocatorException
{
if (serviceLocator == null) {
serviceLocator = new ServiceLocator(jndiProperties);
}
return serviceLocator;
}
i quali operano secondo la classica modalità
del pattern Singleton.
Ecco infine l'implementazione dei metodi di inizializzazione
vera e propria del context JNDI:
private
Context getInitialContext(Properties jndiProperties)
throws NamingException {
if (jndiProperties == null){
throw new NamingException("Impossibile
inizializzare il context JNDI:
jndiProperties è nullo");
}
Context context = new InitialContext(jndiProperties);
return context;
}
private
Context getInitialContext() throws NamingException {
Context context = new InitialContext();
return context;
}
Da notare che per come funziona la classe ServiceLocator
con poco lavoro aggiuntivo si potrebbe implementare
senza troppa fatica un qualche meccanismo di cache all'interno
in modo da evitare il lookup per quelle interfacce già
ottenute in precedenza e memorizzate in una apposita
tabella in memoria. Per quello che si avrà modo
di vedere in uno dei successivi articoli e soprattutto
per le possibilità di conflitto che una cache
locale può avere con i meccanismi di round robin
cluster (vedi [CLU]), spesso si preferisce rinunciare
a questo piccolo vantaggio in termini di prestazioni
per poter sfruttare al meglio le potenzialità
dell'application server EJB senza complicarsi troppo
la vita.
Conclusione
In questo primo appuntamento sono stati affrontati alcuni
concetti basilari necessari per poter successivamente
affrontare e comprendere il funzionamento e la strutturazione
dei vari componenti della architettura complessiva.
In particolare è stato presentato il ServiceLocator,
elemento fondamentale di tutta l'applicazione in quanto
permette di centralizzare e di isolare la logica di
lookup JNDI.
Per quanto riguarda invece le varie ipotesi ed assunzioni
qui proposte si ricorda, se ancora ce ne fosse bisogno,
che data la vastità dell'argomento si è
volutamente scelto di fornire una possibile soluzione
per raggiungere l'obiettivo finale (realizzare una architettura
J2EE-EJB che sia sensata e facilmente gestibile) .
Probabilmente quanto presentato in questo articolo e
nei successivi non rappresenta certamente la sola alternativa
o necessariamente la migliore. I lettori che volessero
sondare strade alternative potranno sicuramente sperimentare
con successo soluzioni altrettanto valide.
Bibliografia
[EJB] - "Enterprise Java Beans" III Edizione,
di Monson Haefel Ed. O'Reilly
[MB] - "Manuale Pratico di Java" capitolo
su EJB di Giovanni Puliti, www.mokabyte.it/manualejava2,
Ed. Tecniche Nuove.
[RUP] - "Appliyng UML and Patterns" di Graig
Larman Ed. Prentice Hall
[CLU] - "Clustering di applicazioni J2EE - I parte:
architetture Cluster EJB con JBoss" e successivi,
di Giovanni Puliti - pubblicato su MokaByte 89 Ottobre
2005 - http://www.mokabyte.it/2005/10
Risorse
Scarica la classe ServiceLocator
|