MokaByte 65 Luglio Agosto 2002 

MokaShop il negozio online di MokaByte Progettare applicazioni J2EE multicanale
IV parte: l'integrazione fra lo strato web e quello EJB

di

Giovanni
Puliti

Dopo gli articoli dei mesi precedenti dedicati alla realizzazione dello strato web tramite il modello MVC, vediamo queso mese come sia possibile integrare il tutto con uno strato applicativo basato su EJB

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:

  1. Un utene A inizia la procedura di registrazione. Viene creato per lui un nuovo contesto, corrispondente ad una nuova sessione A;
  2. la action JSP invoca il metodo remoto del session bean per controllare fra le altre cose se uid=pippo è disponibile;
  3. il metodo del session bean effettua molti controlli, fra cui uid=pippo;
  4. uid=pippo non è stato assegnato a nessun utente;
  5. 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;
  6. 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;
  7. l'utente della sessione B conclude la registrazione con uid=pippo; viene creato un entity RemoteCommunityUser utilizzando i dati presenti nel bean JSP CommunityUser.
  8. Nel database adesso è presente un record in cui uid=pippo.
  9. L'utente della sessione A riprende la registrazione e la conclude con uid=pippo credendo che sia ancora disponibile.
  10. 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;

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it