MokaByte 49 - Febbraio 2001
Foto dell'autore non disponibile
di
Raffaele Spazzoli
EJB’s Design Techniques 
II Parte
Questo è il secondo ed ultimo articolo di una raccolta delle tecniche più comunemente usate dalla comunità dei programmatori di Enterprise Java Beans per trarre il massimo vantaggio dal framework degli EJB.

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
 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


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
mokainfo@mokabyte.it