Introduzione
Nello
scorso articolo abbiamo visto alcuni pattern che si applicano a entrambi
i tipi di Bean (Session Bean e Entità Bean). Inoltre abbiamo
visto un pattern che ci aiuta a dare la giusta dimensione agli Entity Bean
(Aggregate Entity).
Continuiamo
subito con altri pattern che si applicano solo agli Entità Bean.
Value
Object
Nome:
Value Object.
Problema:
l’invocazione da parte di un client dei metodi get/set per leggere o impostare
le proprietà di un Entity Bean genera un grande volume di chiamate
potenzialmente remote. Quando il client si trova effettivamente fuori del
container questo porta ad una notevole inefficienza.
Relazioni
con altre tecniche: assieme ad Aggregate Entities consente di scrivere
applicazioni particolarmente efficienti.
Descrizione:
l’idea alla base di questa tecnica è di avere degli oggetti (Value
Object) che rappresentano lo stato dell’Entity Bean. Secondo questa tecnica
quando un client necessita di avere accesso allo stato di un Entity Bean
chiede all’Entity Bean il suo stato mediante un metodo che gli restituisce
appunto il Value Object. La classe che definisce il Value Object deve essere
serializzabile perché il Value Object deve viaggiare come parametro
in chiamate remote. Il vantaggio di questa tecnica è che con una
sola chiamata remota il client si porta in locale tutti i dati di cui ha
bisogno. Questa tecnica è adatta a quelle applicazioni che visualizzano
dati in maniera record oriented e quindi usano gli Entity Bean prevalentemente
come contenitori di dati. Inoltre questa tecnica entra in sinergia con
la Aggregate Entity ottimizzando il trasferimento sul lato client dei dati
di Entità Bean a granularità grossa.
Un
esempio di Value Object può essere il seguente:
public
class BusinessBean1 implements javax.ejb.EntityBean {
public String getAttribute1() {}
public void setAttribute1(String attribute1) {}
public BusinessValue1 getValue() {}
…
container oriented + business methods
}
public
class BusinessValue1 implements java.io.Serializable {
private String attribute1;
public BusinessValue1() {}
public String getAttribute1() {
return attribute1;
}
}
Come
si può notare dal codice la proprietà Attribute1 del Business
Value è read only mentre nel Bean tale proprietà è
read/write. Il metodo getValue del Bean restituisce il Value Object. Questa
scelta è coerente col fatto che ogni metodo che modifica lo stato
dell’Entity Bean deve essere eseguito sull’Entity Bean.
In
questo modo però abbiamo ancora grosse inefficienze quando il client
deve modificare molti attributi di un Entity Bean. Ancora una volta per
ogni attributo da modificare abbiamo una chiamata remota. Si può
modificare il codice precedente rendendo possibile fare un aggiornamento
delle property sul Value Object e poi dando la possibilità al client
di passare all’Entity Bean un Value Object che rappresenta il suo nuovo
stato.
Il
codice diviene come segue:
public
class BusinessBean2 extends implements javax.ejb.EntityBean {
public String getAttribute1() {}
public void setAttribute1(String attribute1) {}
public BusinessValue2 getValue() {}
public void setValue(BusinessValue2 value) {}
…
container oriented e business methods
}
public
class BusinessValue2 implements java.io.Serializable {
private String attribute1;
public BusinessValue2() {}
public String getAttribute1() {
return attribute1;
}
public void setAttribute1(String attribute1) {
this.attribute1=attribute1;
}
}
Tramite
il metodo setValue dell’Entity Bean viene passato un Value Object all’Entity
Bean. L’implementazione del metodo dovrà poi provvedere a fare in
modo che lo stato dell’Entity Bean venga a riflettere quello del Value
Object passato. Affinché il container, poi, sincronizzi lo stato
del Bean con il database, il metodo setValue deve essere eseguito con attributo
transazionale TX_REQUIRED.
Questa
tecnica per quanto spesso necessaria per ottenere i livelli di servizio
richiesti dalla maggior parte delle applicazioni solleva tre ordini di
problemi che si possono così raggruppare:
-
Creazione
e inizializzazione dei Value Object
-
Validazione
degli attributi
-
Problemi
di sincronizzazione legati all’update dei Value Object
Creazione ed inizializzazione
dei Value Object
Se
si utilizza la tecnica dei Value Object è necessario decidere chi
crea i Value Object e come questi vengano inizializzati.
Nome:
Value Object Factory Method.
Problema:
avere un unico metodo per la creazione dei value object presuppone che
l’oggetto così creato sia adatto a tutti i tipi di client dell’Entity
Bean. Questo in generale non è vero anzi spesso un client è
interessato solo ad una porzione degli attributi di un bean.
Relazioni
con altre tecniche: deve essere usata con Value Object, può
essere usata assieme a session facade per isolare totalmente l’Entity Bean
dai client.
Descrizione:
Se gli Entity Bean del sistema sono a granularità grossa è
probabile che non tutti i client del sistema siano interessati all’interezza
dello stato dei Bean. Può essere dunque utile avere dei Factory
Method che costruiscono i Value Object su misura per il client che li ha
richiesti. Il Factory Method oltre a definire quali attributi devono essere
visualizzati contiene la logica di inizializzazione di tali campi (vedi
Lazy Initializaztion). Inoltre ogni Factory Method costruisce dei Value
Object che possono avere comportamenti diversi quali ad esempio diverse
politiche di sincronizzazione col Bean. I factory method possono essere
localizzati nella Home Interface o, nel caso si usi la tecnica Session
Facade, nel Session Bean che realizza la Facade.
Nome:
Lazy Initialization
Problema:
un Entity Bean può contenere riferimenti ad altri Entity Bean. Durante
la costruzione del Value Object, inizializzare questi riferimenti con i
Value Object dei Bean corrispondenti può portare ad avere strutture
di Value Object molto ingombranti in termini di memoria.
Relazioni
con altre tecniche: deve essere usata con Value Object.
Descrizione:
Lazy Inizialization è una tecnica usatissima in molti contesti che
consiste nell’inizializzare strutture dati solo quando i dati in esse contenuti
vengano effettivamente richiesti. Nel caso degli Entity Bean la struttura
dati è la rete di relazioni fra gli Entity Bean che viene riflessa
durante la costruzione dei Value Object in una rete di relazioni fra Value
Object.
Non
è necessario costruire subito tutti i Value Object, ma è
possibile rimandarne la costruzione a quando il client lo richieda effettivamente.
Al momento dell’inizializzazione del Value Object possiamo decidere quali
riferimenti inizializzare e di quali rimandare l’inizializzazione.
Come
abbiamo visto avere molti messaggi che trasferiscono pochi dati può
essere svantaggioso, ma anche avere pochi messaggi che trasferiscono quantità
enormi di dati è altrettanto controproducente. Con le tecniche dei
Value Object e della Lazy Inizialization siamo in grado di tarare la dimensione
più appropriata dei messaggi da scambiare con i client.
Un
esempio di Lazy Inizialization può essere il seguente:
public
class EJB1 implements javax.ejb.EntityBean {
public EJB1() {}
public ejbprove.EJB1value getValue() {}
public void setValue(ejbprove.EJB1value value) {}
}
public
class EJB2 implements javax.ejb.EntityBean {
public EJB2() {}
public ejbprove.EJB1value getEJB1() {}
public void setEJB1(ejbprove.EJB1value EJB1) {}
public ejbprove.EJB2value getValue() {}
public void setValue(ejbprove.EJB2value value) {}
}
public
class EJB2Value {
private ejbprove.EJB1Value EJB1;
private ejbprove.EJB1Remote EJBRemote;
public EJB2Value() {}
public ejbprove.EJB1Value getEJB1() {
if (EJB1==null){
EJB1=EJBRemote.getEJB1();
}
return EJB1;
}
public void setEJB1(ejbprove.EJB1Value EJB1) {
this.EJB1 = EJB1;
}
}
In
questo esempio EJB2 contiene un riferimento ad EJB1. Il Value Object di
EJB2 (EJB2Value) usa la Lazy Initialization per restituire il riferimento
al Value Object di EJB1 (EJB1Value il cui codice non è riportato).
Si
noti che la tecnica Lazy Initialization è utile anche per un altro
motivo: consente di usare la medesima tecnica anche su lato server. Riferendosi
all’esempio precedente EJB2 non è costretto a inizializzare il reference
a EJB1, obbligando il container ad allocare un altro EJB, fino a che non
vi sia effettivamente bisogno di farlo.
Validazione degli
attributi
In
generale quando si sviluppa una applicazione distribuita bisogna decidere
di chi è la responsabilità di validare l’input, ovvero se
essa sia del client che riceve l’input o dell’application server. Se il
codice è sul lato client esso deve essere duplicato per ogni tipo
di client, se il codice è sul lato server esso è centralizzato,
ma gli errori vengono rilevati solo dopo che l’input è stato inviato
al server e il server risponde con una eccezione caricando dunque la rete
di un (forse) inutile traffico. L’approccio migliore in questi casi è
probabilmente avere entrambe le validazioni a costo di una efficienza leggermente
più bassa. In questo modo se un errore non viene intercettato sul
lato client verrà scoperto dal server.
Nel
caso si usino i Value Object una tecnica frequentemente usata è
quella di effettuare la validazione nei Value Objects e sul Bean. Questo
però comporta una fastidiosa duplicazione di codice in corrispondenza
dei metodi set del Value Object e del Bean. Ci possono essere due modi
per condividere tale codice: Value Object e Bean ereditano da una classe
comune il cui unico scopo è quello di effettuare le validazioni.
Oppure Value Object e Bean delegano la validazione ad un oggetto esterno.

So noti
che in entrambi i casi abbiamo separato la business logic “tradizionale”
da quella di validazione dei dati. Non sempre questa è un operazione
così facile, anzi capita spesso che per validare la coerenza di
dati di input si chiamino metodi di business logic.
Problemi legati
all’update
Il
fatto che con i Value Object il client usi una cache locale dello stato
dell’Entity Bean introduce alcuni problemi quando andiamo a scrivere le
modifiche effettuate sulla copia.
Nome:
Long Lived Pessimisitic Pseudo-Transaction (Version Numbering).
Problema:
due client possono richiedere contemporaneamente il Value Object dello
stesso Entity Bean. Quando salvano le modifiche, solo una delle due copie
viene salvata.
Relazioni
con altre tecniche: deve essere usata con Value Object.
Descrizione:
sarebbe desiderabile che la lettura dei dati di un Value Object e la successiva
scrittura avvenissero in un'unica transazione. In questo modo le righe
del database che contengono i dati vengono bloccate al momento della lettura
e rilasciate durante la successiva scrittura. Questo approccio non è
attuabile in applicazioni distribuite perché le righe restano bloccate
per un tempo non noto a priori e che dipende da fattori esterni al sistema
(il cosiddetto think time dell’utente che talvolta prevede anche la pausa
caffè). Normalmente allora i dati vengono letti in una transazione
e aggiornati in un'altra. In questo caso è possibile che due o più
client ottengano una copia del bean e poi tentino di salvare le proprie
modifiche. Se non si effettuasse nessun controllo sul database alla fine
rimarrebbero solo i dati dell’ultimo che ha eseguito l’aggiornamento. Questo
problema viene risolto consentendo il salvataggio solo ad un client e intercettando
successivi tentativi di salvataggio con la segnalazione di un errore. Questo
è un comportamento logico perché i client che eseguono i
successivi salvataggi, lo fanno supponendo che il bean sia in un certo
stato mentre questo è cambiato; l’elaborazione dei client deve ripartire
con l’ultimo stato del Bean.
In
pratica per ottenere questo comportamento è sufficiente che l’Entity
Bean abbia un campo intero che identifica la versione dello stato. Tale
campo verrà incrementato tutte le volte che si esegue una modifica
allo stato del Bean. Il campo deve essere presente anche sul database.
Inoltre tale campo deve essere memorizzato anche nel ValueObject così
quando il Value Object verrà usato per effettuare le modifiche il
Bean potrà controllare la versione del proprio stato e quella del
ValueObject ed eventualmente sollevare una eccezione.
Un
esempio di Version Number può essere il seguente:
public
class EJB extends javax.ejb.EntityBean {
public EJB() {}
public EJBvalue getValue() {}
public void setValue(value value) {
if (!getVersionID.equals(value.getVersionID())){
// bean state is changed
throw new EJBException(“isolation not respected”);
}
setVersionID(new Integer(getVersionID().intValue()+1));
}
public void setVersionID(Integer versionID){}
public Integer getVersionID(){}
}
Session Facade
Nome:
Session Facade.
Problema:
si vuole porre un interfaccia netta fra un progetto e i client nascondendo
anche gli Entity Bean oppure ci sono use case che utilizzano più
Entity Bean
Relazioni
con altre tecniche: può essere usato con Value Object per isolare
totalmente i dettagli del progetto.
Descrizione:
questa tecnica, che sta diventando piuttosto popolare, consiste nel predisporre
un certo numero di Session Bean che si comportino come una Facade[4] del
sistema. Una Facade è un rivestimento o una facciata del sistema
che ne nasconde la complessità interna ed espone verso i client
esterni una interfaccia semplificata ed adatta al tipo d’uso che il client
deve operare sul sistema. I Session Bean, assieme ad eventuali Value Object
che servono a scambiare dati, diventano l’interfaccia del sistema. Questa
tecnica può essere applicata in modi diversi: si può avere
un Session Bean per ogni Entity Bean. In tal caso in genere il Session
Bean serve ad eseguire i metodi dell’Entity Bean, ma anche metodi su gruppi
di Entity Bean. In altri casi si ha un Session Bean per ogni use case del
sistema. In ogni caso i Session Bean diventano la sede dei Factory Method
dei Value Object. La Facade di Session Bean serve bene ad isolare il sistema
dai client esterni (persone o sistemi) garantendo una maggiore manutenibilità
del progetto.
Dynamic Value
Object
Nome:
Dynamic Value Object.
Problema:
i Value Object spesso hanno la stessa logica interna: caching dello stato
dell’Entity Bean che rappresentano con eventuale gestione della concorrenza,
Lazy Initialization dei reference esterni al Bean ecc…L’unica cosa che
cambia fra i Value Object è l’interfaccia, cioè l’insieme
dei metodi set/get che espongono al client. E’ auspicabile trovare un modo
per condividere la logica comune a tutti i Value Object, senza però
modificare la semplicità con la quale i client interagiscono con
essi.
Relazioni
con altre tecniche: questa tecnica si basa sulle seguenti tecniche:
Business Interface, Busieness Object, Value Object,Lazy Initialization,
Version Numbering.
Descrizione:
l’ipotesi alla base di questa tecnica e che in un progetto in cui si scelga
di utilizzare la tecnica dei Value Object, questi contengono tutti la stessa
logica. Infatti tutti i Value Object si comportano come una cache locale
al client dello stato dell’Entity Bean. Inoltre i Value Object devono avere
una politica di isolamento delle transazioni da utilizzare durante gli
aggiornamenti e devono avere una politica di inizializzazione dei riferimenti
esterni al Bean che essi rappresentano. Nei paragrafi precedenti abbiamo
visto alcune politiche per affrontare questi problemi, ce ne possono essere
altre, quello però che è importante tenere presente è
che all’interno di un singolo progetto verosimilmente le politiche usate
dai vari Value Object saranno uniformi. Da ciò segue che buona parte
o magari tutto il codice che sta attorno a Value Object può essere
condiviso. L’ideale sarebbe avere un unico Value Object che si traveste
di volta in volta con l’interfaccia Remote dell’Entity Bean che deve rappresentare.
Questo è possibile grazie a due feature di java tanto potenti quanto
poco conosciute: la reflection e i dynamic proxy (quest’ultima apparsa
solo nel jdk1.3).
Tramite
la reflection si possono chiamare metodi di una classe senza conoscerla
a compile time; mediante i dynamic proxy è invece possibile creare
a run time una classe di un tipo specificato.
I
Value Object che vengono creati con questa tecnica saranno in grado di
chiamare i metodi dell’Entity Bean che rappresentano grazie alla reflection
e avranno come tipo la RemoteInterface dell’Entity Bean grazie ai dynamic
proxy.
Il
funzionamento dei Dynamic Value Object è il seguente: i metodi get/set
utilizzano una cache locale realizzata con una HashTable: la cache è
inizializzata con un set di valori che non comprendono i riferimenti ad
altri Entity Bean i quali vengono inizializzati solo on-demand (Lazy Inizializzation),
inoltre i metodi di set eseguono l’update solo della cache. Per sincronizzare
il Value Object con il l’Entity Bean che rappresenta si dispone di un metodo
apposito (update()). Gli altri metodi della Remote Interface (cioè
i metodi di business veri e propri) vengono invocati direttamente sull’Entity
Bean.
Le
relazioni fra le classi e le interfacce di questa tecnica è mostrata
nella seguente figura:

Tutto
comincia dalla Business Interface e dal Business Object che rappresentano
rispettivamente l’interfaccia e l’implementazione di un business object
del mostro sistema e possono appartenere ad un package che contiene solo
la logica di business. La BusinessRemote estende l’interfaccia BusinessInterface,
l’interfaccia EJBObject (come da specifiche EJB) e l’interfaccia Proxiable.
Questa interfaccia contiene i metodi necessari per leggere e scrivere diverse
proprietà del bean con una sola operazione.
La
classe BusinessBean rappresenta un Entity Bean del sistema, essa estende
da BusinessObject (come prevede la tecnica Business Object). Inoltre implementa
le interfacce Versionable e Proxiable. L’interfaccia Versionable espone
due metodi che servono a leggere e a scrivere la versione dello stato del
bean e serve ad implementare la tecnica Version Numbering. L’implementazione
dei metodi dell’interfaccia Proxiable invece viene delegata alla classe
Proxy Delegate in quanto grazie alla reflection può essere uguale
per tutti gli Entity Bean.
La
classe DynamicProxy è il Value Object. Essa viene creata sul lato
client grazie a una factory. La factory assieme al ProxyDelegate determina
la politica di creazione dei ValueObject.
La
classe DynamicProxy implementa dinamicamente l’interfaccia BusinessRemote
e Updatable.
Grazie
al fatto che DynamicProxy espone l’interfaccia BusinessRemote, il client
può utilizzarla senza modifiche al codice in tutti i punti dove
veniva usata l’interfaccia BusinessRemote. Il client però deve essere
consapevole di usare un Value Object e deve sapere che quando desidera
sincronizzarsi con l’Entity Bean deve chiamare il metodo update dell’interfaccia
Updatable.
Per
maggiori dettagli è possibile consultare il codice allegato all’articolo.
Conclusioni
La
tecnologia EJB è in piena evoluzione: sono da poco uscite le specifiche
EJB 2.0 e leggendole si percepisce che non sono un punto di arrivo bensì
un punto di partenza. Gli application server a breve cominceranno ad adeguarsi
alle nuove specifiche. Tutto questo movimento lascia certamente molto spazio
alla nascita di nuove tecniche e probabilmente ne renderà obsolete
altre.
Quello
che è importante è tenere presente quali sono i punti di
forza e di debolezza del framework degli EJB e per i punti di debolezza
quali sono le tecniche che ci possono tornare utili.
Bibliografia
[1]
Sun Microsystem, “Enterprise Java Bean 2.0 Specification”, 2000
[2]
Sun Microsystem, “Designing Enterprise Application with Java 2 Platform
Enterprise Edition”, 2000
[3]
Nova Laboratories, “The Developers’s Guide to Understanding Enterprise
JavaBeans Applications”, 2000
[4]
Addison Wesley, “Design Patterns: Elements of Reusable Object-Oriented
Software” Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, and
Grady Booch
|