|
Introduzione
Nei mesi scorsi abbiamo effettuato una introduzione
piuttosto approfondita sulla realizzazione di applicazioni
web basate sul modello MVC. Il filo conduttore degli
articoli fino qui pubblicati è sempre stato quello
di mostrare l'importanza di una buona progettazione
della architettura complessiva al fine di rendere il
più semplice possibile la fase di implementazione
e sopratutto di manutenzione.
Come forse sarà apparso fin da subito questo
modo di lavorare, benché sia estremamente potente
e molto flessibile, inzialmente richiede uno sforzo
di comprensione forse non sempre banale.
I benefici però sono molto importanti e facilmente
verficabili quando si deve evolvere verso qualcosa di
più complesso: il pattern MVC isola in determinati
moduli (il Model, la View ed il Controller) le funzionalità
specifiche della applicazione permettendo in tal modo
di modificare o sostituire uno solo di tali moduli senza
che il resto della applicazione ne sia interessata.
Questo mese vedremo come sia semplice e praticamente
indolore, migrare la business logic da una struttura
basata esclusivamente su servlet/JSP MVC (riprendendo
in esame l'esempio dei mesi scorsi la logica era tutta
inglobata nelle classi action), alla integrazione di
una architettura EJB in cui la business logic sia inserita
in enterprise beans in esecuzione all'interno di un
container apposito.
L'utilizzo della tecnologia EJB introduce un livello
di complessità sicuramente molto alto, per cui
non sempre tale passaggio è giustificato: per
una completa analisi di questi aspetti elencazione sarebbe
necessario molto più spazio di quello qui a disposizione.
Per brevità ricordiamo semplicemente alcuni concetti
fondamentali che possono giustificare l'utilizzo di
EJB:
- Transazionalità
sicurezza e solidità: le principali caratteristiche
del modello EJB possono essere indispensabili in uno
scenario complesso in cui i molti client accedono
contemporaneamente alle risorse condivise
- Riutilizzo:
se si spinge ulteriormente il processo di isolamento
della Business Logic in componenti remoti, questi
potranno essere progettati in modo da essere utilizzati
in modo ancora più autonomo dal resto della
applicazione, e quindi ad essere utilizzati più
volte.
- Scalabilità:
è indubbio che l'utilizzo di un sistema EJB
permette di scalare verso l'alto in modo molto più
potente di quanto non sia possibile con un applicazione
monoblocco.
Per
maggiori chiarimenti sull'uso di EJB è sufficiente
leggere un qualsiasi manuale che spieghi cosa esso sia
e perché sia così importante in ogni applicazione
enterprise.
Questo mese cercheremo di comprendere da un lato come
sia possibile integrare una applicazione web con lo
strato EJB ed al contempo di vedere alcune possibili
scelte che si possono effettuare per progettare al meglio
l'applicazione.
Come per gli articoli precedenti vorrei ricordare che
quanto verrà qui mostrato è frutto di
considerazioni limitate al caso in esame, e che sia
la parte di progettazione come quella delle tecniche
di programmazione non devono essere considerate come
il meglio possibile, ma solo come spunti per iniziare
ad impratichirsi con queste tecnologie.
La
Business Logic si sposta
Come ormai è stato detto più volte detto,
l'utilizzo del modello MVC consente di isolare, in classi
apposite, tutta la business logic della applicazione.
Riconsidendo per un momento il grafico riportato in
figura 1 e già pubblicato in precedenza, si potrà
notare come le varie Action siano interpellate tutte
le volte che deve essere eseguita una operazione non
banale (la business logic appunto), eliminando dalle
pagine JSP tutto quello che riguarda le operazioni di
controllo dei dati, di gestione del database e cose
di questo tipo.

Figura 1 - L'organizzaizone di una web application
secondo il modello MVC prevede l'isolamento della logica
di esecuzione in apposite classi
Il
solo serlvet/JSP container non offre nessun supporto
per quanto riguarda la sicurezza, la transazionalità,
la persistenza e la resistenza agli incidenti, aspetti
per i quali sicuramente EJB è più utile
visto che fin dall'inizio è stato pensato appositamente
per questi scopi.
Il grosso beneficio di aver isolato la business logic
in classi opportune permette di spostare la business
logic dal servlet container ad un EJB container in cui
siano stati installati session ed entity beans.
Questo passaggio, se da un lato permette di non stravolgere
completamente la struttura della applicazione, richiede
alcune importanti considerazioni in modo da scegliere
i componenti giusti e la migliore sequenza di operazioni
da effettuare.
Al fine di semplificare la trattazione si concentrerà
l'attenzione sullo stesso esempio già visto in
precedenza, ovvero la gestione di una community tramite
pagine web: tale sistema deve prevedere la possibilità
di effettuare una nuova registrazione, la modifica di
un profilo già immesso, la rimozione di un utente
ed infine il recupero della password tramite email.
In questo caso si limiterà l'attenzione alla
fase di registrazione di un nuovo utente: dopo che l'utente
ha immesso i propri dati, il sistema effettuerà
i dovuti controlli su tali dati e procederà alla
registrazione solo se tutti i vincoli imposti su tali
dati saranno rispettati.
Il flusso della applicazione è riportato sommariamente
in figura 2, mentre nella figura successiva è
presentato in modo più dettagliato il passaggio
delle varie pagine ed azioni secondo lo schema tipico
presentato negli articoli precedenti.

Figura
2 - grafico sommario del flusso della web application

Figura 3 - grafico dettagliato del flusso della
web application: in questo caso nei rettangoli verdi
sono riportate le associazioni fra i codici di risposta
delle action e la pagina da visualizzare, così
come il nome del codice di una azione da eseguire ed
il nome della classe corrispondente. Riconsiderando
quanto visto nei mesi precedenti, tali associazioni
sono quelle inserite ad esempio nei file actions.xml
o routing.xml
Si
immagini di utilizzare nella applicazione JSP un oggetto
UserCommunity, oggetto che conterrà tutti i campi
necessari a rappresentare un utente registrato nella
comunità virtuale, come nome, cognome, email,
indirizzo fino a id e password.
Su alcuni di tali campi si potranno imporre dei vincoli:
che l'anno di nascita sia effettivamente un numero,
o che la posta elettronica rispetti determinate regole
di sintassi; sicuramente si dovrà imporre che
lo uid sia unico all'interno della community, ed eventualmente
anche la password. Similmente anche la posta elettronica
dovrà essere unica, perché altrimenti
non si potrà implementare il meccanismo di password-recovery
tramite email, tipico di ogni sistema di community.
Il rispetto di tali vincoli verrà garantito,
al momento della registrazione, da una serie di controlli
messi in atto una classe apposita: in questo esempio
quindi il metodo perform() della classe AddNewCheckAction.class
rappresenta la business logic della applicazione JSP.
Anche altre funzioni (e quindi altra business logic)
saranno inglobate all'interno della applicazione, ma
per il momento è sufficiente concentrarsi su
questa classe.
Registrazioni concorrenti
Nel modello MVC senza EJB il metodo perform() potrebbe
non fornire sufficienti garanzie sulla correttezza finale
dei dati: ad esempio potrebbe accadere che due utenti
effettuino la registrazione in contemporanea, immettendo
gli stessi valori per alcuni campi importanti. Semplificando
al massimo si potrebbe avere che
Thread
A corrispondente all'utente A
1. controlla se uid è libero
2. uid è libero
3. controlla altri campi
4. controlla password libera
5. password è libera
6. controlla altri campi
7. procedi alla registrazione nel database dei dati
A
patto di non implementare tecniche manuali per la gestione
della transazione, non si può avere la certezza
che le operazioni dal punto 1 al 7 avvengano senza interruzioni
e che un altro thread bruci sul tempo tale thread e
proceda alla registrazione di un uid dopo che se ne
era controllata la disponibilità. Questi e molte
altri aspetti sono solo alcuni dei problemi che tipicamente
entrano in gioco in scenari concorrenti: per chi fosse
interessato ad approfondire tali componenti può
eventualmente leggere [jconc] [ejb3].
Risulta chiaro quindi che per il solo supporto transazionale,
spostare le operazioni del metodo perform dentro un
container EJB potrebbe essere una buona scelta.
MVC
con EJB: dalle action ai metodi remoti
Per prima cosa si deve cercare di capire quali siano
i soggetti in causa nella applicazione web MVC e come
trasformarli in modo da integrare EJB: questa scelta
non è del tutto semplice ed automatica, dato
che coinvolge anche un secondo aspetto fondamentale,
ovvero come collegare i due sistemi.
A tale scopo si possono considerare due elementi della
web application MVC: la classe action che contiene la
business logic in esame, ed il bean gestito dalle pagine
JSP. Quest'ultimo, nell'esempio in esame il bean CommunityUserBean,
non è altro che una raccolta di campi, le proprietà
dell'utente, e non ingloba nessuna logica di lavoro.
Il suo scopo è infatti raccogliere i vari dati
immessi dall'utente tramite form, o viceversa visualizzarli
a video.
Non è necessario preoccuparsi anche dei servlet
che implementano la funzione di controller, dato che
non verranno toccati da questo processo di integrazione
della logica in EJB.
Le action invece verranno sostituite con successo da
un unico enterprise session bean, i cui metodi andranno
a sostituire i vari perform. Dato che in questo caso
la transazionalità è data automaticamente
dal container, si può risolvere brillantemente
il problema di cui sopra.
Ovviamente dato che il sistema JSP dovrà interagire
in qualche modo con la parte EJB ed in particolare con
i metodi remoti del session, le action continueranno
ad esistere anche se svolgeranno la funzione di gateway
verso il session bean (figura 4).

Figura 4 - Le action che prima racchiudevano
la business logic, adesso funzionano come strumento
di integrazione fra lo strato EJB e quello JSP. Ogni
metodo perform, invocato dall'ActionServlet, provvederà
ad inoltrare la chiamata al corrispondente metodo remoto
del session bean. Altra importante funzione delle action
è quella di prelvare dal contesto i bean da passare
come parametri ai metodi remoti, e di reimetterli dopo
averli ricevuti indietro modificati.
Rimane
da chiedersi se utilizzare uno stateless o uno stateful
session bean. Questa domanda implica la scelta di chi
e come mantiene la sessione con il client remoto, se
il container JSP (e quindi utilizzare un session stateless)
oppure il container EJB (utilizzando incece un session
stateful).
Per poter rispondere a questa domanda conviene prendere
in esame l'altro componente da trasformare, o meglio
da rivedere, il bean CommunityUser.
Per quella che la teoria di EJB insegna, un oggetto
senza business logic, ma che rappresenta esclusivamente
una collezione di proprietà è un ottimo
candidato per essere rappresentato da un entity bean.
Anche in questo caso non è possibile, ne sarebbe
corretto, interagire da una pagina JSP direttamente
con un entity; il bean CommunityUser quindi continuerà
ad essere presente nella applicazione e continuerà
ad essere utilizzato con lo stesso spirito di prima,
ovvero come raccoglitori di dati: da una parte le pagine
JSP ne popoleranno i campi con istruzioni del tipo
<jsp:setProperty
name="CommunityUserBean" property="*"
/>
dall'altro
per la visualizzazione si potrà scrivere in una
pagina .jsp qualcosa del tipo
<b>Nome:</b>
<jsp:getProperty name="CommunityUserBean"
property="userFirstName"/>
Il
session bean, dovendo interagire con i dati immessi
dall'utente, dovrà accedere ai campi del bean:
una soluzione potrebbe essere quella di mantenere la
sessione tramite il container JSP, utilizzando l'oggetto
CommunityUserBean con scope session.
In questo caso il bean verrebbe passato ai metodi del
session tutte le volte che sia necessario effettuare
determinate operazioni di business logic sui dati di
CommunityUserBean. In questo caso il session sarebbe
di tipo stateless ed i suoi metdo si potrebbero considerare
semplici metodi remoti di servizio.
Per capire meglio, il metodo perform() della action
AddNewCheckData.class potrebbe diventare
//
ricava il bean CommunityUserBean, come attributo,
// dalla sessione JSP
user =(CommunityUserBean)httpServletRequest.getSession()
.getAttribute("CommunityUserBean");
//
inzializza il contesto EJB
Context ctx = new InitialContext();
Object ref = ctx.lookup("CommunityRegistrator");
CommunityRegistratorHome communityRegistratorHome;
communityRegistratorHome = (CommunityRegistratorHome)
PortableRemoteObject.narrow(ref,
CommunityRegistratorHome.class);
// si crea un session CommunityRegistratorRemote
// per servire lo strato client, JSP
CommunityRegistratorRemote registrator = communityRegistratorHome.create();
//
invoca il metodo remoto EJB per il controllo dei dati
sul CommunityUserBean
user=registrator.checkDataUser(user);
//
ripone nella sessione il bean CommunityUserBean
httpServletRequest.getSession()
.setAttribute("CommunityUserBean",user);
Si
noti come la action prima di invocare il metodo remoto,
ricavi dalla sessione il bean CommunityUserBean. In
questo caso il session bean, in caso di dati non corretti,
modifica un suo campo particolare contenente tutti i
messaggi di errore relativi ai dati immessi. Dato che
tale messaggio se presente dovrà essere visualizzato
in una pagina JSP, è necessario riporre il bean
modificato nella sessione.
Se si fosse voluto mantenere la sessione tramite il
container EJB, in modo da sfruttarne le maggiori potenzialità,
si sarebbe dovuto utilizzare uno stateful session, tenendo
a scope di pagina il bean CommunityUserBean; risulta
ovvio in questo caso per ogni modifica ad uno dei campi
del bean in una qualsiasi pagina JSP si dovrebbe passare
tale oggetto al session, con notevole traffico in rete,
considerando che in ogni caso un session è da
considerarsi remoto, cosi come le invocazioni dei metodi
ed il passaggio dei parametri.
<<
figura 5 - Se la sessione è mantenuta dal container
Ejb tramite uno stateful, allora ogni volta che lo strato
JSP effettua una modifica sui dati utente, il bean a
scope di pagine deve essere passato al session bean
remoto.>>
Per
quanto riguarda l'utilizzo di un entity bean, si deve
procedere in modo più accorto. Infatti se si
considera ad esempio l'operazione di registrazione di
un utente, essa non deve avvenire in un sol colpo: l'utente,
dopo aver immesso i propri dati per la registrazione
e non ha ancora terminato la procedura di registrazione,
potrebbe volere ricontrollare i dati prima del salvataggio
finale. Se poter disporre di un bean a scope session
nel contesto JSP è una operazione comoda perché
permette di portare i dati da una pagina all'altra,
creare un entity corrisponde invece parte dei casi alla
creazione di una o più righe direttamente nel
database e questo potrebbe essere un comportamento non
desiderato.
Per risolvere questo problema le possibilità
possono essere molte, e non è detto che una strada
sia completamente sbagliata a confronto di un'altra.
Si prenda ad esempio il caso del controllo dei dati
utente: se prima il tutto avveniva all'interno del metodo
perform della classe AddNewCheck.class, adesso tali
controlli si sono spostati all'interno del metodo checkDataUser().
Anche se tale metodo è transazionale, l'invocazione
dei metodi da parte del container JSP avviene in modo
prettamente asincrono e la lentezza della rete internet
non fa che amplificare il concetto di asincronia delle
operazioni effettuate da un utente. Il problema è
presente in ogni applicazione basata su interfaccia
utente, dove si ipotizza che l'operazione di controllo
dei dati sia separata da quella di salvataggio finale:
dopo l'immissione dei dati l'utente vuole sempre ricontrollare
i dati immessi prima del salvataggio finale.
Per questo checkDataUser() e saveDataUser()saranno due
metodi separati del session bean e sebbene sia possibile,
è fortemente sconsigliato dar vita a transazioni
che ricadano su più metodi di un session.
Riassumendo il problema la sequenza delle operazioni
potrebbero essere:
-
Un utene A inizia la procedura di registrazione. Viene
creato per lui un nuovo contesto, corrispondente ad
una nuova sessione A;
-
la action JSP invoca il metodo remoto del session
bean per controllare fra le altre cose se uid=pippo
è disponibile;
- il
metodo del session bean effettua molti controlli,
fra cui uid=pippo;
- uid=pippo
non è stato assegnato a nessun utente;
- l'utente
A, per un qualsiasi motivo ritarda la conclusione
della registrazione: il collegamento ad internet potrebbe
essere improvvisamente rallentato oppure l'utente
sospende per rispondere al telefono;
- un
altro utente B, associato alla sessione B, esegue
tutte le operazioni precedenti, e sceglie la uid=pippo.
Tale valore è ancora disponibile; si procede
con la registrazione;
- l'utente
della sessione B conclude la registrazione con uid=pippo;
viene creato un entity RemoteCommunityUser utilizzando
i dati presenti nel bean JSP CommunityUser.
- Nel
database adesso è presente un record in cui
uid=pippo.
-
L'utente della sessione A riprende la registrazione
e la conclude con uid=pippo credendo che sia ancora
disponibile.
- Viene
creato un entity RemoteCommunityUser utilizzando i
dati presenti nel bean JSP CommunityUser, ma i dati
sono sovrapposti.
Si
noti come al punto 9 per l'utente A non viene infatti
rieffettuato nessun controllo sulla disponibilità
di uid, dato che in ogni caso non si potrebbe avere
la certezza matematica che dopo il controllo non si
verifichino altre pause (sempre dando per ipotesi che
check e save siano due procedure separate temporalmente).
In questo caso il metodo checkDataUser() potrebbe essere
...
if (UserBean.getUserFirstName()!=null &&
UserBean.getUserFirstName().length()==0){
UserBean.addErrorMessage("Nome utente
non valido: "+UserBean.getUserFirstName()+";");
}
...
Context
ctx = new InitialContext();
Object ref = ctx.lookup("CommunityUser");
CommunityUserHome remoteuser;
remoteuser
= (CommunityUserHome)
PortableRemoteObject.narrow(ref,CommunityUserHome.class);
try {
CommunityUserRemote UserfindById = remoteuser.findByPrimaryKey(xxx,
"pippo");
// Se si arriva qui c'è già
un utente
UserBean.addErrorMessage("UID "+UserBean.getUserId()+
"assegnato ad un altro utente");
}
catch (Exception ex) {
...
}
Il
controllo sulla presenza di un utente con uid=pippo
è stato fatto effettuando una ricerca con il
metodo findByPrimaryKey() al quale è stato passato
l'id da controllare, oltre ad altri parametri necessari
per la ricerca.
Questo scenario, tanto frequente (facilmente risolvibile
anche senza particolari supporti EJB tramite l'utilizzo
di flag nel database), potrebbe essere risolto tramite
la creazione al punto 2 di un entity il cui valore del
campo uid sia pippo.
Questa operazione impedirebbe ad una seconda ricerca
di avere risposta negativa, anche se ci si trovasse
nella condizione di cui prima.
Il codice appena visto quindi, dopo aver effettuato
una findByPrimaryKey(), potrebbe procedere alla create()
dell'entity.
In questo caso, anche se l'utente interrompese la procedura
di registrazione, nel database si troverebbe un record
di un utente fantasma, ed il valore pippo non sarebbe
più disponibile anche se non associato a nessun
utente.
L'utilizzo di un flag status come campo dell'entity
RemoteCommunityUser, da impostare a "saved"
a procedura completata potrebbe evitare l'insorgere
di utenti fantasma nel database, avendo ovviamente l'accortezza
di rimuovere periodicamente o all'abbandono dell'utente,
tutti record che abbiano status=temporary. L'utilizzo
dei bean temporizzati (ovvero in grado di eseguire certe
operazioni a determinati intervalli di tempo) disponibili
dalla prossima versione della specifica EJB potrebbero
risolvere questo problema in modo semplice.
Volendo implementare controlli di unicità anche
sugli altri campi, sarà sufficiente procedere
in modo analogo, dopo aver fornito l'entity bean di
metodi di ricerca opportuni, come findByEmail() o findByPassword().
A fine procedura il bean della sessione JSP invocherà
il metodo saveUser() del session bean in modo da rendere
definitiva la registrazione. Nel caso in cui si sia
utilizzato un entity fin dall'inizio, tale metodo non
dovrà far altro che impostare status ad un valore
divero da temporary, convenzionalmente potrebbe essere
saved. Nel caso in cui invece non si sia creato l'entity,
lo si dovrà fare in questa sede. In entrambi
i casi la creazione del bean entity produce la scrittura
nel database dei dati utente, permettendo di disinteressarsi
delle problematiche di accesso ai dati che invece saranno
relegate nei metodi ejbStore() o ejbLoad() dell'entity.
Molti sconsigliano un uso pesante o sconsigliano del
tutto gli entity beans, ed in uno scenario come questo
l'utilizzo di un flag nel database porterebbe ad un
risultato analogo, rimandando la creazione dell'entity.
Come
integrare i vari prodotti
Per quanto riguarda l'integrazione fra i due container,
il JSP e EJB, si dovrebbe scendere molto nel dettaglio
dell'utilizzo dei prodotti scelti per questo tipo di
applicazione. Anche se non è possibile fare una
trattazione esauriente in uno spazio umanamente gestibile,
si tenga presente che il punto di contatto fra il session
bean e la action che ne invoca i metodi remoti, è
dato da una lookup tramite un sistema di naming JNDI.
A tal proposito, una cosa che non è forse stata
rimarcata è che la action dovrebbe sempre interagire
con un enterprise bean di tipo session e mai con un
entity direttamente. Il session infatti è il
servitore fedele di ogni applicazione client, ed esporta
verso il mondo esterno tutte le funzionalità
di logica o di gestione dati che sono inglobate nello
strato EJB.
E' interessante notare come, proprio grazie all'utilizzo
di un pattern potente ed universalmente accettato come
l'MVC, la parte di interfaccia grafica, qui costituita
dallo strato web servlet/JSP, potrebbe essere sostituita
da una applicazione stand alone con gui Swing. La business
logic non cambia ed anzi essendo inglobata in un robusto
sistema basato su EJB, potrà essere utilizzata
in contesti differenti senza troppe modifiche.
E' vero però che, per permettere una così
alta flessibilità e potenza, lo strato EJB dovrebbe
essere modellato a sua volta in strati e sottostrati.
Ad esempio a volte si indica una buona architettura
come costituita da 4 livelli EJB, in cui i livelli più
bassi sono legati alle funzionalità universalmente
utilizzate, mentre quelli dei livelli più alti
saranno modellati per il caso in esame. Nella peggiore
delle ipotesi, volendo riutilizzare la parte EJB in
un contesto completamente differente da quello per cui
era stato pensato originariaente, sarà sufficiente
modificare al più il primo strato EJB, quello
relativo allo use case in esame.
Conclusioni
Quello che si è appena visto in questo articolo
è solo un piccolo esempio di come EJB possa essere
utilizzato concretamente all'interno di una applicazione
più strutturata e che preveda l'interazione con
l'utente finale tramite interfaccia grafica.
Ovviamente per un lettore esperto, quanto visto potrebbe
apparire scontato, e per certi versi banale: purtroppo
per poter scendere ad un livello di approfondimento
maggiore sarebbe necessario molto più spazio
e tempo di quello a disposizione qui questo mese. Questi
aspetti sono molto importanti, in genere non si trovano
nella bibliografia ufficiale che si limita ad esporre
la teoria di EJB in modo asettico, e sono invece il
pane quotidiano di un buon progettista.
E' praticamente impossibile poter trattare in modo universale
tali concetti, ma nei prossimi mesi si cercherà
di dare un po' di indicazioni anche in tal senso.
Bibliografia
[jconc] "Concurrent Programming in Java(TM): Design
Principles and Pattern (2nd Edition)" di Doug Lea, Co;
ISBN: 0201310090
[ejb3] "Enterprise Java Beans" di Richard Monson-Haefel,
Ed. O'Reilly ISBN: 0596002262;
|