Login
Cerca all'interno di JavaPortal
Help
Home Page Documentazione Forum Progetti Partner Pubblica!
Documentazione > Data Access Object : utilizzo ed interazione con alcuni Design Patterns
Hide
JavaWebServices.it
Repository Classi
Tutorial
NewsLetter
News
Blogs
Risorse Free onLine
Libri Java Free

Hai una tesi in Java?
Tesine preparate
per esami?
Pubblica tutto su
JavaPortal!

Scrivi al nostro staff


VMware ha comprato SpringSource


Publio Cornelio Tacito
Tutte le cose che ora si credono antichissime furono nuove


Deployment Descriptor Reference


Rss Feed
Home Page
Articoli
News
Forum
Classi

  Visualizza Commenti (0) Aggiungi Commento    
Add to Shortcuts
 
Vota l'articolo
Data Access Object : utilizzo ed interazione con alcuni Design Patterns
By Andrea Gazzarini
7 giugno 2005

  Data Access Object : utilizzo ed interazione con alcuni Design Patterns
Program Brevi cenni sulle classi di esempio utilizzate
Program Data Access Object : panoramica
Program Recupero intelligente delle strutture : le faces
Program Introduzione dei Layer Supertypes
Program Struttura di una ipotetica implementazione per le dao e le faces
Program Conclusione
Program Bibliografia

Tutte le applicazioni di tipo enterprise hanno a che fare con problematiche inerenti la persistenza dei dati (meglio la gestione di tale persistenza), le specificità del sistema di storage utilizzato, la flessibilità, e la capacità di adattamento in caso di cambiamenti di tale sistema.

Se da un lato le API JDBC si occupano della gestione degli aspetti di comunicazione a basso livello con la specifica fonte dati (rendendo il tutto trasparente), dall'altro la progettazione e lo sviluppo devono necessariamente tenere conto di possibili cambiamenti di tale fonte dati; cambiamenti che possono essere legati alla marca dello specifico prodotto (DB2, ORACLE, etc.), oppure al meccanismo di storage utilizzato (database relazionale, database ad oggetti, xml, etc.).

Nel progettare una applicazione che ha a che fare con problematiche di questo tipo ci si pongono diversi problemi, fra i quali:
  • Flessibilità e capacità di adattamento del software ad eventuali cambiamenti del meccanismo di persistenza utilizzato;
  • Scelta dei componenti di realizzazione del Domain Model[Martin Flower];
  • Necessità di fruizione dei dati a livelli di granularità ed organizzazione diversi da parte dei diversi tier.

I primi due punti fanno riferimento a delle scelte di carattere strutturale, relative cioè alla architettura del sistema, ai prodotti da utilizzare; il terzo riguarda invece l'utilizzo dei prodotti, le loro potenzialità, le loro peculiarità raffrontate alle esigenze della realtà che stiamo analizzando. Quando si parla di analisi da un punto di vista teorico si pensa ad un insieme di attività che gettano delle solide fondamenta di certezza su cui poi costruire tutta l'impalcatura sovrastante; si pensa ad un qualcosa che richiede un grosso sforzo all'inizio ma che dopo rende il tutto più facile.

Questo da un punto di vista pratico non si avviene quasi mai, verificandosi puntualmente situazioni in cui l'analisi, la progettazione e lo sviluppo si avvolgono in una sorta di circolo vizioso in cui l'uno influenza e modifica l'altro causando inevitabilmente incertezza. In questi casi lavorare su progetti in cui sussiste questa condizione risulta essere una ottima palestra per sperimentare l'utilizzo di patterns (è l'unico lato diciamo.positivo!) e rendere quanto più aperto ai cambiamenti il nostro codice.fatte salve le situazioni in cui la confusione è così elevata che per produrre software flessibile bisognerebbe scrivere delle classi vuote!!!

Circoscrivendo il problema all'ambito del recupero dati, mentre è opportuno chiarire subito che dare le risposte a questioni di questo tipo non è sempre facile e dipende poi dallo specifico contesto, l'utilizzo del pattern Data Access Object (DAO) è di notevole ausilio.

Il pattern in questione viene, nel contesto di una classificazione basata sui tier, collocato nel cosidetto Integration tier, il livello cioè che si occupa di astrarre le specificità del Resource tier (ove risiedono i meccanismi di storage utilizzati) nei confronti del Business tier, il quale utilizza i dati per le sue finalità a prescindere e senza preoccuparsi della loro provenienza.

Scopo di questo articolo è di compiere un percorso di approfondimento nel mondo dei DAO a partire da una definizione per così dire ufficiale (quella riportata su "Core J2EE patterns"), arricchendo di volta in volta il discorso o con nozioni prelevate da altri autori circa l'argomento oppure frutto esclusivamente della mia esperienza. Inizialmente si prenderà quindi in considerazione una visione "essenziale" dei DAO, arrichendola di volta in volta con concetti derivanti dall'interazione con altri Design Patterns. Tale interazione è decisamente necessaria, poiché  permette di estendere il raggio d'azione del discorso arrivando ad una soluzione modulare e flessibile; risulta peraltro difficile cercare di effettuare un approfondimento su un pattern trattandolo in maniera isolata; questo è possibile quando viene presentata una visione astratta e generica dello stesso, ma quando si scende in un contesto più reale sorgono diversi problemi all'interno dello stesso scenario difficilmente risolvibili con un unico pattern: da qui l'importanza dell'interazione;

L'ultima nota riguarda una precisazione sull'ambito del problema affrontato: si fa fondamentalmente riferimento ai soli meccanismi di recupero dati, quindi per questo articolo non vengono presi in considerazione operazioni di inserimento, cancellazione ed aggiornamento in genere; queste discussioni saranno oggetto di un articolo separato. Per lo stesso motivo non verranno prese in considerazione le tecniche di utilizzo dei DAO da parte degli EJB.

Va da sé che lasciate a parte le fonti esterne (ufficiali o meno), tutto quello che è frutto di considerazioni personali del sottoscritto in merito all'argomento è soggettivo e quindi discutibille...anzi, vuole essere una sorta di esempio di come a partire da una definizione base si arrivi poi a sviluppare una propria idea personale sull'argomento.



Brevi cenni sulle classi di esempio utilizzate top

Questa sezione descrive brevemente i concetti alla base dell'esempio (molto rudimentale) utilizzato nell'articolo. Anche se ogni classe, al momento opportuno, verrà ripresa ed approfondita, riporto qui di seguito delle nozioni di carattere generale ai fini di una migliore comprensione del tutto.

L'esempio a cui si riferimento è un (ipotetico) sistema di ricerca di gruppi musicali(...!); per rappresentare tali concetti ho utilizzato una classe BandVO che implementa una interfaccia VO utilizzata per questioni di polimorfismo.

Fig. 1 Struttura dei Value Object utilizzati nell'esempio

I criteri presi in considerazione secondo cui è possibile effettuare la ricerca sono due:

  • Ricerca per nome (della band);
  • Ricerca per data di fondazione(sempre della band) compresa tra due intervalli;
Gli esempi presentati sono molto scarni e spesso e volentieri descrivono una situazione forzata (in senso semplicistico), lontana da come si presenterebbe dalla realtà.ovviamente per ragione esplicative.


Data Access Object : panoramica top

Per una corretta comprensione di che cosa sia un Data Access Object il miglior punto di partenza è senza dubbio la definizione riportata in "Core J2EE patterns".

"Use a Data Access Object (DAO) to abstract and encapsulate all access to the data source.The DAO manages the connection with the data source to obtain and store data." (Core J2EE Patterns)

La definizione è molto semplice, ed in effetti è il concetto di DAO ad essere semplice: un oggetto che astrae ed incapsula la logica di accesso ad una sorgente dati. La definizione in questione offre però una visione (giustamente) generica ed astratta che deve essere poi concretizzata ed applicata alla specifica realtà nel momento in cui si decide di utilizzare questo pattern; a prima vista infatti sembrerebbe che dalla definizione si stesse parlando di un unico oggetto avente responsabilità non meglio precisate sulla natura dei dati che tratta... il fatto che il "DAO gestisce la connessione con la sorgente dati per ottenere e inserire/aggiornare/cancellare i dati" sembra quasi far pensare ad una entità monolitica che si occupa di tutti gli aspetti legati a tutti i dati rilevanti per l'applicazione.

In realtà in una applicazione, fermo restando la natura ed il tipo di sorgente dati, esistono diversi DAO, ognuno dei quali si occupa di gestire gli aspetti legati all'accesso ai dati di una o più entità del Domain Model [Martin Flower].

Quindi, prendendo in considerazione un esempio per così dire ufficiale, potremmo citare il CloudScapeCatalogDAO di PetStore, che rappresenta il DAO che gestisce i meccanismi di accesso ai dati (su uno specifico sistema di storage di tipo RDBMS in particolare CloudScape) per il recupero dello stato di una o più entità di tipo Catalogo. Dando un breve sguardo a questa classe notiamo come in realtà sia molto semplice dal punto di vista concettuale; ogni metodo provvede, a partire dai dei parametri di ingresso, ad effettuare delle ricerche e restituire i relativi risultati.

Quello che si vuole ottenere è per l'appunto l'incapsulamento all'interno di questa classe di tutte le operazioni necessarie (relative all'entità trattata) ad effettuare queste ricerche sulla sorgente dati (specifica) in questione.

Leggendo attentamente la definizione notiamo come l'utilizzo dei DAO abbia un altro scopo oltre quello di incapsulare: quello di astrarre...

Per spiegare questo concetto introduciamo innanzitutto il nostro DAO : RDBMSBandDAO (genericamente non indirizzato su uno specifico database relazionale a differenza di quello di PetStore).

public class RDBMSBandDAO {

            public BandVO findByName(String name) throws DAOSysFindException,NoSuchResultException {

                        // Lista contenente i risultati della ricerca

                        List result = new LinkedList();

Connection connection = null;

PreparedStatement statement = null;

ResultSet rs = null;

try {

 connection = ...ottenimento della connessione allo specifico db.

                                    statement = connection.prepareStatement("SELECT ....");

                                    rs = statement.executeQuery();

                                    if(rs.next()) {     

                                              // Valorizzazione dei Value Objects

                                                BandVO band = new BandVO();

                                                band.setName(rs.getString("NAME"));

                                                // ...setting delle altre properties  

                                                result.add(band); // ...aggiunta VO alla lista dei risultati

                                                return result;

} else {

            throw new NoSuchResultException();

}                                  

} catch(SQLException exception) {

            throw new DAOSysFindException(exception);

} catch(Exception exception) {

            throw new DAOSysFindException(exception);

} finally {

            // Chiusura resultset

            try{

           if (rs != null) rs.close();

} catch (Exception ex){

}

            // Chiusura statement

            try{

          if (statement != null) statement.close();

} catch (Exception ex){

}

            // Chiusura connessione

            try{

           if (connection != null) connection.close();

} catch (Exception ex){

}

}
}

            public List findWhereFoundationDateIsBetween(Date start, Date end) throws DAOSysFindException {

                        //...codice necessario ad effettuare la ricerca

                        //...e valorizzare la lista dei value object di tipo BandVO secondo questo criterio di ricerca.
}
}

Quello che si evince è che lo scopo del DAO è quello di incapsulare la logica di accesso alla sorgente dati per recuperare ed utilizzare lo stato (su un database di tipo relazionale) di una lista di specifiche entità (le Band ) attraverso i due metodi di ricerca:

  1. per nome (che torna un singolo oggetto BandVO);
  2. per data compresa fra due estremi (che torna una lista di oggetti BandVO come risultato).

L'utilizzo più immediato è quello di istanziare la classe, e successivamente invocare il metodo di ricerca desiderato, ottenendo una lista di band che corrispondono al risultato voluto.

Supponiamo che, nel bel mezzo dello sviluppo, si decida (per una ragione ignota) di cambiare database, passando ad un OODBMS (database ad oggetti); tale cambiamento però, potrebbe riverlarsi effimero, nel senso che una volta viste e valutate le differenze prestazionali rispetto al RDBMS, si potrebbe decidere di rispristinare il vecchio sistema (quello relazionale). Attualmente il codice è pieno di chiamate a RDBMSBandDAO, utilizzato in ogni dove della nostra applicazione, quindi il passaggio ad un ipotetico OODBMSBandDAO non è assolutamente indolore...

Innanzitutto scriviamo il nostro OODBMSBandDAO, che possiede una interfaccia identica a RDBMSBandDAO, precisando che il recupero dei risultati per database di questo tipo avviene in maniera specifica a seconda del prodotto. Lo pseudo-codice riportato utilizza Hybernate:

public class OODBMSBandDAO {

            public List findWhereFoundationDateIsBetween(Date a,Date b) throws DAOSysFindException {

                        Session session = sessionFactory.openSession();

                        Transaction transaction = null;

                        List result = new LinkedList();

try {

            transaction = session.beginTransaction();

                        List result = s.createQuery("select new BandVO...").list();

                        return result;

} catch(Exception exception) {

            if (transaction != null) transaction.rollback();

            throw new DAOSysFindException(exception);

} finally {

            if (session != null) session.close();

}

}

            public List findByName(String name) throws DAOSysFindException,NoSuchResultException {

                        // Ottenimento risultati secondo questo criterio di ricerca
}
}

Il requisito di astrazione dei Data Access Object serve proprio per fronteggiare situazioni di questo tipo. Una volta che si hanno a disposizione le due classi RDBMSBandDAO e OODBMSBandDAO, notiamo come (per definizione) esse debbano avere la stessa interfaccia. In effetti entrambi devono permettere le medesime operazioni con gli stessi parametri e tipi di ritorno. Formalizziamo questo concetto introducendo l'interfaccia BandDAO che i due DAO dovranno implementare.

public interface BandDAO {

            public BandVO findByName(String name) throws DAOSysFindException,NoSuchResultException;

            public List findWhereFoundationDateIsBetween(Date start, Date end) throws DAOSysFindException;

}

public class RDBMSBandDAO implements BandDAO

public class OODBMSBandDAO implements BandDAO

A questo punto il codice è già potenzialmente flessibile, in quanto rende possibile l'utilizzo del polimorfismo. Il problema che ci si pone è che in numerosi punti della nostra applicazione è presente questa riga di codice:

RDBMSBandDAO dao = new RDBMSBandDAO(); // Ottenimento dell'istanza del DAO

List bands = dao.findWhereFoundationDateIsBetween(startDate,endDate); // Ricerca ed ottenimento dei risultati

Ora, se vogliamo utilizzare l'OODBMSBandDAO potremmo sostiture la prima riga con

OODBMSBandDAO dao = new OODBMSBandDAO();

ottendendo il risultato voluto, ma in verità in maniera poco elegante...pensate al fatto che la porzione di codice sopra descritta è presente in trenta punti dell'applicazione!! Inoltre che un domani si potrebbe decidere di passare di nuovo al RDBMSBandDAO!!! Come evitare questo inutile spreco di tempo??

Fermo restando che almeno una volta  quelle benedette trenta righe vanno cambiate, quello che si può fare e cercare di fare in modo che sia la prima e l'ultima!

A tale scopo presento di seguito due soluzioni, entrambi le quali si avvalgono di due Design Pattern di tipo creazionale.

3.1         Factory Method : il CatalogDAO di Java PetStore

La soluzione (CatalogDAOFactory) consiste nel creare una classe con un Factory Method [GOF], dove incapsulare la logica di creazione del DAO specifico.

In realtà l'implementazione suggerita dalla Gang of Four differisce leggermente dal Factory Method utilizzato da PetStore. Se dovessimo prendere alla lettera quanto riportato in "Design Patterns" la struttura del CatalogDAOFactory dovrebbe presentarsi così:

Fig. 2 Il pattern Factory Method applicato al CatalogDAO di PetStore

Come si nota dal diagramma, ad ogni prodotto concreto (MySQLCatalogDAO piuttosto che CloudScapeCatalogDAO) è associata una specifica factory (rispettivamente MySQLCatalogDAOFactory e CloudScapeCatalogDAOFactory). Sia i prodotti che le factory possiedono delle interfacce comuni (CatalogDAO per i prodotti e CatalogDAOFactory per le factory) attraverso le quali il codice client lavora distaccandosi dalle  reali implementazioni.

L'implementazione di PetStore differisce e semplifica il diagramma riportato in Fig. 2, utilizzando il contesto java:comp/env associato agli EJB per recuperare sotto forma di stringa il nome della classe DAO da caricare e successivamente ottenendo una istanza di tale classe attraverso Class.forName(String clazz).newInstance().

In questo modo si evita di scrivere una classe factory per ogni DAO da creare, centralizzando il compito creazionale all'interno della sola CatalogDAOFactory che a questo punto diviene una classe concreta e non più un'interfaccia come nel caso precedente.

Fig. 3 L'implementazione del Factory Method utilizzato in PetStore per il CatalogDAO

Due annotazioni: innanzitutto notare come l'utilizzo di una interfaccia comune ai DAO del catalogo (CatalogDAO per l'appunto) renda flessibile (e possibile) l'utilizzo di un pattern di questo tipo. Inoltre attraverso l'utilizzo del metodo Class.forName e la property nell'environment EJB che contiene il nome della classe da caricare tale metodo è completamente immune da qualsiasi cambiamento nei confronti del sistema di persistenza (e di conseguenza della implementazione dell' XXXCatalogDAO utilizzato).

Da tenere d'occhio la frequenza con cui il metodo creazionale viene invocato...questo perché tutta la flessibilità offerta da un meccanismo di questo tipo ha un costo principalmente risiedente nella istanziazione dell'oggetto attraverso Class.forName().newInstance(). Provate a vedere con un piccolo main di test la differenza in termini prestazionali tra l'utilizzo di :

Pippo obj = (Pippo)Class.forName("com.gazzax.sample.Pippo").newInstance()

e la riga di codice:

Pippo obj = new Pippo().

3.2         Abstract Factory : ovvero una factory di factory...

La seconda soluzione prevede l'introduzione del concetto di famiglie o gruppi di oggetti aventi una destinazione, uno scopo comune ed appartenti alla stessa tipologia.

Nel nostro esempio, la tipologia di oggetti è genericamente Data Access Object, le famiglie sono quelle degli RDBMSDAO (DAO che accedono a database relazionali) e OODBMSDAO (DAO che accedono a database ad oggetti). Entrambi le famiglie possiedono oggetti che implementano interfacce comuni (RDBMSBandDAO e OODBMSBandDAO implementano BandDAO, RDBMSMusicianDAO e OODBMSMusicianDAO implementano MusicianDAO, etc...);

Per una definizione immediata dell'Abstract Factory possiamo dire che si tratta di una factory di factory...! Vediamo di capire meglio...abbiamo detto che abbiamo (attualmente) due famiglie di oggetti

DAO che accedono a database relazionali:

RDBMSBandDAO à implementa BandDAO

RDBMSMusicianDAO à implementa MusicianDAO

DAO che accedono a database ad oggetti:

OODBMSBandDAO à implementa BandDAO

OODBMSMusicianDAO à implementa MusicianDAO

Per ognuno di queste famiglie andiamo a creare una classe Factory

// Factory per i DAO che accedono a database relazionali

public class RDBMSDAOFactory {

      public BandDAO getBandDAO(){

                  return new RDBMSBandDAO();
}

public MusicianDAO getMusicianDAO() {

            return new RDBMSMusicianDAO();
}

// altri metodi creazionali...

}

// Factory per i DAO che accedono a database ad oggetti

public class OODBMSDAOFactory {

      public BandDAO getBandDAO(){

                  return new OODBMSBandDAO();
}
public MusicianDAO getMusicianDAO() {

            return new OODBMSMusicianDAO();
}

// altri metodi creazionali...

}

Quello che a questo punto manca è una classe che funga da coordinatore, cioè che a partire da un codice mnemonico fornisca (crei) la factory dei DAO opportuna. Una volta ottenuta questa factory si chiama il metodo creazionale del DAO che interessa (invocandolo sulla specifica factory). Ecco perché factory di factory: prima è necessario creare la DAOFactory concreta e poi utilizzare quest'ultima per ottenere il DAO desiderato.

I codici mnemonici che identificano i sistemi di storage utilizzati sono racchiuse nell'interfaccia StorageType.

public interface StorageType {

      // Database relazionale

int RDBMS = 1;

// Database ad oggetti

            int OODBMS = 2;

}

Quindi andiamo a scrivere una DAOFactory...

// Factory delle DAO factory

public abstract class DAOFactory {

      // Metodo creazionale

      public static DAOFactory getDAOFactory(int whichFactory) {

                  switch(whichFactory) {

                              case StorageType.RDBMS:

                                          return new RDBMSDAOFactory();

                              case StorageType.OODBMS:

                                          return new OODBMSDAOFactory();                     

}

}
}

 
...e assicuriamoci del fatto che le due factory precedenti estendano quest'ultima; questo vincolo ci assicura che ogni futura XXXDAOFActory possieda i metodi creazionali di TUTTI i DAO necessari alla applicazione.

Il codice da utilizzare per ottenere un DAO su RDBMS è il seguente:

BandDAO bandDao = DAOFActory.getDAOFactory(StorageType.RDBMS).getBandDAO();

Notiamo come l'unico punto dove è presente un richiamo alla famiglia di dao da utilizzare è nell'utilizzo della variabile StorageType.RDBMS. Per il resto l'applicazione utilizza il dao attraverso la sua interfaccia (BandDAO);

Il passaggio da RDBMS a OODBMS comporta la sostituzione della variabile StorageType.RDBMS con StorageType.OODBMS tutte le volte che è stato utilizzata la linea di codice appena presentata; questo può risultare scomodo da un punto di vista di manutenibilità...possiamo risolvere questo problema introducendo il concetto di DAO factory utilizzata correntemente (di default) ed aggiungere un altro metodo nella classe DAOFactory:

// Metodo creazionale della XXXDAOFactory utilizzata correntemente

public static DAOFactory getDefaultDAOFactory(){

      return DAOFactory.getDAOFactory(StorageType.RDBMS);

}

L'ottenimento del dao necessario (per esempio BandDAO) si effettua attraverso:

BandDAO bandDao = DAOFActory.getDefaultDAOFactory();

List result = bandDao.findByFoundationDate(startDate, endDate);

o in maniera più stringata (e meno leggibile) :

List result = DAOFActory.getDefaultDAOFactory().findByFoundationDate(startDate, endDate);

Notare come sia scomparso ogni riferimento alla tipologia di dao richiesta (RDBMS); se un domani questa dovesse cambiare occorre aggiornare solamente il metodo getDefaultDAOFactory() facendo ritornare la factory opportuna.

In questo modo il codice client lavora solamente con le interfacce (sia dei DAO che delle DAOFactory) realizzando un completo disaccoppiamento con le reali implementazioni realmente utilizzate. In Fig. 4 è presente il class diagram della struttura appena descritta.


Fig. 4 Abstract Factory applicato ai Data Access Object

 



Recupero intelligente delle strutture : le faces top

Una volta compresi i concetti visti nei punti precedenti, dovrebbe essere abbastanza semplice progettare la struttura dei Data Access Object per la propria applicazione, utilizzando uno degli approcci descritti precedentemente.

Il discorso proseguirà su dei particolari aspetti cui ci si trova di fronte quando si utilizzano applicazioni che usano i DAO. In particolare quello che ci interessa a questo punto è la struttura, la profondità e la granularità dei risultati delle ricerche effettuate con i metodi finder dei DAO. Per definizione i metodi di ricerca vengono scritti in quanto esiste qualcuno (Business Tier,Integration Tier, Presentation Tier) che ha necessità di utilizzarli; questo qualcuno potrebbe avere ad esempio una utilità diretta nell'utilizzo (per il compimento della propria Business logic) oppure potrebbe semplicemente fungere da ponte verso un altro tier (ad esempio verso il Presentation Tier che ha necessità di visualizzare i dati in un report).

A prescindere da tale utilizzo assumiamo che i dati vengano recuperati dall'Integration Tier (dai DAO) sotto forma di Value Object (o Data Transfer Object).

Il problema che ci si pone riguarda un utilizzo ottimale delle risorse allo scopo di consegnare all'utilizzatore dei value object che contengano solamente i dati necessari alla specifica elaborazione. Facciamo un esempio:

Primo Caso: un processo di Business ha la necessità di trovare tutte le band che sono state fondate tra il 2003 e il 2004 per verificare quali abbiano partecipato ad un certo evento. Tale processo utilizzerà (indirettamente attraverso un facade o direttamente lui stesso) il BandDAO chiamando il metodo findWhereFoundationDateIsBetween(). Di tutti gli attributi di ogni BandVO presente nella lista dei risultati, il processo in questione utilizzerà solamente l'ID della band, in quanto unico attributo necessario a verificare se la band era presente nella lista dei partecipanti dello specifico evento.

Secondo Caso: l'interfaccia utente (web) della applicazione permette una navigazione per data di fondazione delle band presenti nel catalogo. Qui la richiesta proviene dal Presentation Tier e passando tra i vari livelli arriva allo stesso punto di prima, cioè al BandDAO ed al suo metodo findWhereFoundationDateIsBetween(). La differenza con l'esempio precedente è che questa volta, ai fini di una corretta visualizzazione da parte dell'utente, sono necessari tutti gli attributi del BandVO.

Il metodo utilizzato nell'uno e nell'altro caso è lo stesso. Tuttavia nel primo caso serve un solo attributo dell'oggetto costituente il risultato, mentre nel secondo servono tutti. Il problema consiste nel creare, a parità di interfaccia, gli oggetti da servire ai richiedenti valorizzati a seconda delle loro esigenze.

Una soluzione di massima sarebbe quella di ritornare comunque tutti gli attributi, ma va da sé che non è molto efficiente nel caso l'entità Band fosse composta da numerosi attributi ed al processo ne serve uno solo o pochi.

4.1         Intento e motivazione

Le faces tentano di fornire una soluzione object-oriented al problema appena discusso. Il termine Face significa faccia, visuale. Ebbene il ruolo di una face è quello di valorizzare tutto o in parte uno specifico value object ( o data transfer object) secondo dei criteri dettati dall'utilizzo che di quel value object se ne deve fare.

Sicché chi si troverà ad utilizzare i risultati di una ricerca in cui intervengono tali componenti, troverà valorizzata nei value objects solamente la visuale (gli attributi) che gli interessano.

Questo permette di effettuare dei recuperi dati "intelligenti" evitando sempre di caricare tutto e di far viaggiare in rete cose che non servono a nessuno.

Le faces sono altresì dei DAO a tutti gli effetti, in quanto incapsulano dei criteri di accesso ai dati effettuando poi una valorizzazione "intelligente" dei risultati; lavorano inoltre in maniera congiunta con i DAO visti sinora; anzi, per una migliore chiarezza questi ultimi saranno identificati d'ora in poi come "DAO in senso stretto".

L'idea che si trova alla base delle faces è quella di incapsulare il concetto di query diverse inerenti la stessa entità, dove la diversità è data dalle informazioni che vengono recuperate (sia sulla tabella principale che su altre tabelle collegate da relazioni).

Proviamo subito a partire dall'ultimo anello della catena, chi richiede ed utilizza i dati, per vedere il codice che utilizza i DAO con le faces. Se si richiama alla mente la linea di codice necessaria ad effettuare una ricerca per nome vista sopra:

BandDAO dao = DAOFactory.getDefaultDAOFactory().getBandDAO()

List result = dao.findByName("Miller");

Con l'introduzione delle faces il codice diventa:

BandDAO dao = DAOFactory.getDefaultDAOFactory().getBandDAO(Face.BAND_FULL_FACE);

List result = dao.findByName("Miller");

      oppure:

BandDAO dao = DAOFactory.getDefaultDAOFactory().getBandDAO(Face.BAND_MINIMUM_FACE);

List result = dao.findByName("Miller");

 

Come si può notare la differenza consiste nel fatto che oltre che chiamare il DAO desiderato, è possibile specificare la face relativa ai risultati che si desiderano ottenere. Nel primo caso abbiamo una face che valorizza appieno gli oggetti di tipo BandVO ritornati nella lista dei risultati (BAND_FULL_FACE), mentre nel secondo, la face (BAND_MINIMUM_FACE) effettua la valorizzazione di alcuni attributi del BandVO.

Ho voluto mostrare queste righe di codice per far capire da subito lo scopo e l'utilizzo delle faces; ora andiamo ad analizzare che cosa succede dietro le quinte.

Innanzitutto introduciamo una interfaccia Face che tutte le faces dovranno implementare:

public interface Face {

VO getPerspectivedValueObject() throws ValorizationException, NoSuchResultException;       

List getPerspectivedValueObjects() throws ValorizationException;

}

Lo scopo dei due metodi è lo stesso, cambia il tipo d'oggetto su cui operano. Il primo metodo valorizza un solo value object (il risultato della ricerca è costituito da un elemento), il secondo effettua la stessa operazione ma su un insieme di value objects (il risultato della ricerca sono n elementi). Entrambi lanciano una eccezione nel caso in cui qualche problema si verificasse durante la valorizzazione. NoSuchResultException è una eccezione lanciata dai metodi che tornano (anche per i DAO vale lo stesso) un singolo risultato (es: findByPrimaryKey), ed indica che non esiste nessun risultato per la ricerca in questione.

L'interfaccia Face è anche un buon punto dove censire tutte le faces che il nostro sistema dovrà utilizzare. Fino adesso ne abbiamo individuate due: riguardano tutte il BandVO; una è una face minima (quella utilizzata dal processo di business a cui serviva solamente l'ID), l'altra è completa (tutti gli attributi); formalizziamo tali concetti inserendo due costanti mnemoniche all'interno dell'interfaccia:

public interface Face {

            // Solamente ID

            int BAND_MINIMUM_FACE = 1;

            // Tutti gli attributi

            int BAND_FULL_FACE = 2;

            ...
}

Il prossimo passo è quello di creare una interfaccia per tutte le face dell'entità Band che chiamiamo per l'appunto BandFace:

public interface BandFace extends Face,BandDAO {
}

notare come la BandFace E' una Face ed E' anche un DAO (per la precisione un BandDAO). Possiede quindi i metodi della prima come della seconda interfaccia, in quanto ogni concreto implementatore (ogni concreta XXXBandFace) dovrà incapsula dei criteri di accesso ai dati (DAO) e dovrà utilizzare tali dati per valorizzare una specifica visuale (set di attributi) dell'oggetto (o degli oggetti) BandVO.

Dopodiché andiamo ad implementare le due faces prima censite nell'interfaccia. Per semplicità ne riportiamo solamente una (quella che carica solo l'ID della band). Tutto quello che è omesso risulta simile come implementazione a quello che invece è descritto.

public class RDBMSBandMinimumFace implements BandFace {

// La query incapsulata da questa face.

private final static String SQL_FACE = "SELECT ID_BAND FROM BAND";

// La query SQL completa che verrà effettuara a runtime.

private StringBuffer sql = new StringBuffer(SQL_FACE);

// parametri da passare alla query

private Object [] params;

// implementazione del metodo di BandDAO per questa face

// crea la WHERE condition della query SQL.

public List findWhereFoundationDateIsBetween(Date start,Date end) throws DAOSysFindException{

            sql.append(" WHERE DATA_FONDAZIONE IS BETWEEN ? AND ?");

            params = new Object[]{start,end};

return getPerspectivedValueObjects();

}
// Esecuzione della query e valorizzazione della lista dei risultati

public List getPerspectivedValueObjects() {

Connection conn = null;

            PreparedStatement statement = null;

            ResultSet rs = null;

            List  result = new LinkedList();

            try {

                        conn = ....ottenimento connessione;

                        statement = conn.prepareStatement(sql.toString());

                        if (params != null) {

                                    for (int i = 0; i < params.length; i++){

                                                statement.setObject(i+1,params[i]);

}
rs = statement.executeQuery();

                        while (rs.next()){

                                    BandVO band = new BandVO();

                                    band.setID(rs.getLong("ID_BAND"));

                                    result.add(band);

}                                  
return result;

} catch(SQLException exception) {

            throw new DAOSysFindExcepton("Errore durante l'esecuzione della query");

} catch(ValorizationException) {

            throw new DAOSysFindException("Errore durante la valorizzazione degli oggetti");

} finally{

            ...chiusura resultset, statement e connessione.
}

}
public VO getPerspectivedValueObject() throws ValorizationException {

            // il codice è simile al seguente, con la differenza che la valorizzazione                  

            // riguarda un solo BandVO
}

}

La parte in rosso è quella in cui si esplica il ruolo della face. Notate come ad ogni ciclo del ResultSet viene creato un BandVO in cui viene valorizzata una sola property (ID). Ogni value object presente nella lista dei risultati avrà valorizzata solamente questo attributo.

4.2         Abstract Factory per le faces

Se per i DAO in senso stretto si è introdotto l'utilizzo di pattern creazionali (Factory Method e Abstract Factory) al fine di creare astrazione e disaccoppiamento del codice dalla loro reale implementazione, una cosa simile è necessaria anche per le faces, in quanto riprendendo l'esempio visto sopra, esisteranno quelle che operano su un database relazionale e quelle che lavorano con un database ad oggetti. L'unica differenza è che la decisione circa l'implementazione da utilizzare verrà presa dalle XXXDAOFactory, nascondendo a loro volta la reale implementazione utilizzata al client.

Applichiamo il pattern Abstract Factory anche alle faces. Ci sarà quindi una FaceFactory (superclasse astratta), una RDBMSFaceFactory ed una OODBMSFaceFactory. Riportiamo solamente le prime due citate:

public abstract class FaceFactory {

      public FaceFActory getFaceFactory(int whichFactory) {

case StorageType.RDBMS:

                              return new RDBMSFaceFactory();

                  case StorageType.OODBMS:

                              return new OODBMSFaceFactory();
}

public abstract BandFace createBandFace(int whichFace);

public abstract MusicianFace createMusicianFace(int whichFace);
//....metodi creazionali di tutte le altre face eventualmente presenti nel sistema.

}
//....face factory per le faces che lavorano su database relazionali.

public class RDBMSFaceFactory extends FaceFactory {

      public abstract BandFace createBandFace(int whichFace) {

            switch(whichFace) {

                        case Face.BAND_MINIMUM_FACE:

                                    return new BandMinimumFace();

                        case Face.BAND_FULL_FACE:

                                    return new BandFullFace();
          }

}

//....altri metodi creazionali di tutte le altre faces.
}

Da notare come questa Abstract Factory sia leggermente più complessa rispetto a quella utilizzata per i DAO. In effetti la riga di codice necessaria a richiamare una face sarà:

Face aFace =

FaceFactory

.getFaceFactory(StorageType.RDBMS)

.getBandFace(Face.BAND_MINIMUM_FACE);

Con queste istruzioni si vuole ottenere una BandFace (getBandFace) che valorizzi il solo ID dei value object ritornati (FACE.BAND_MINIMUM_FACE) da una FaceFactory che lavora su un database relazionale (StorageType.RDBMS). Non esiste in questo caso un metodo getDefaultFaceFactory() in quanto essendo il metodo creazionale richiamato all'interno dei DAO concreti, questi ultimi sanno perfettamente a quale famiglia appartengono.

Per chiudere il giro ed utilizzare le faces all'interno dei DAO occorre fare una piccola modifica ai DAO stessi: ognuno di loro deve gestire una face che detta le modalità di valorizzazione dei risultati; introduciamo quindi una superclasse (astratta) di nome BaseDAO che tutti i DAO estendono. Tale classe incapsula per l'appunto una face e pubblica i metodi get e set per tale attributo; possiede inoltre un costruttore che accetta una Face.

public abstract class BaseDAO {

protected Face face;

public void setFace(Face face) {

            this.face = face;

}
public Face getFace() {

            return face;
}

public BaseDAO(Face face) {

            this.face = face;
}

}

Visto che tutti i DAO dovranno estendere questa classe, è necessario aggiungere loro un costruttore che accetta in ingresso una Face e chiama super(face); inoltre le XXXDAOFactory concrete vanno modificate in questo modo:

public class RDBMSDAOFactory extends DAOFactory{

     

      // Face Factory su database relazionali utilizzata per la creazione delle face durante

// l'istanziazione dei DAO.

      private final static FaceFactory faceFactory = FaceFactory.getFaceFactory(StorageType.RDBMS);

      // Ritorna un DAO che lavora su database relazionale e valorizza i risultati sulla base dei criteri

      // specificati nella Face corrispondente alla costante mnemonica passata in ingresso.

      public BandDAO getBandDAO(int face) {

                  return new RDBMSBandDAO(faceFactory.createBandFace(face));

}

}

4.3         Utilizzo dei DAO in senso stretto come Mediator di faces

Arrivati a questo punto, dovreste avere già capito come i DAO utilizzano le faces al loro interno per effettuare l'accesso ai dati. Prendendo sempre il nostro BandDAO, nella sua concreta implementazione RDBMSBandDAO:

public class RDBMSBandDAO extends BaseDAO implements BandDAO {

            // La face proviene dalla DAOFactory (RDBMSDAOFactory in questo caso)

public RDBMSBandDAO(Face face) {

                        super(face);
}

public List findWhereFoundationDateIsBetween(Date start, Date end) throws DAOSysFindException {

            return ((BandFace)face).findWhereFoundationDateIsBetween(start,end);
}

public BandVO findByName(String name) throws DAOSysFIndException,NoSuchResultException {

            return ((BandFace)face).findByName(name);
}

}

Ecco che quindi i DAO in senso stretto divengono solamente dei mediatori, non accedono più direttamente ai dati se non delegando tale compito alla face corrente che viene impostata a runtime;

L'utilizzo del DAO in questione, nonostante la complessità sottostante, è abbastanza semplice e pulito; ecco il codice client:
BandDAO dao = DAOFactory.getDefaultDAOFactory().getBandDAO(Face.BAND_MINIMUM_FACE);

List result = dao. findWhereFoundationDateIsBetween(aDate, anotherDate);

Il codice è molto chiaro: si vuole ottenere una lista delle band la cui data di fondazione sia compresa tra aDate e anotherDate, ponendo come condizione di valorizzare nella lista dei risultati formata da un insieme di BandVO il solo attributo ID.



Introduzione dei Layer Supertypes top

In questo capitolo andiamo a realizzare una implementazione concreta degli strumenti sino ad ora descritti. In particolare i concetti visti precedentemente vengono estesi introducendo delle superclassi che implementano ed accentrano i comportamenti comuni semplificando la scrittura delle faces.

Si tenga conto che quella che segue è una possibile implementazione dei concetti presentati sinora; implementazione che, facendo riferimento ad uno scenario molto semplicistico, difficilmente si ripropone in questi termini nella realtà. La Fig. 5 mostra tutte le classi che intervengono nella discussione. Molte di queste sono state già analizzate nei capitoli precedenti, altre sono nuove. Diamo un'occhiata alla Fig. 5:

 

Fig. 5 struttura di una ipotetica implementazione per le dao e le faces

 



Struttura di una ipotetica implementazione per le dao e le faces top
  • BandDAO: è l'interfaccia che tutti i BandDAO devono implementare. Nella gerarchia presente in figura è presente un unico implementatore RDBMSBandDAO (DAO utilizzato per recuperare informazioni sulle band sotto forma di BandVO in un database di tipo relazionale);
  • BandFace: è l'interfaccia che estende Face (in quanto tale) e BandDAO (in quanto definisce l'interfaccia di un oggetto che incapsula criteri di accesso ai dati per il recupero di informazioni sull'entità Band);
  • Face: interfaccia base faces;
  •   DAOFactory: classe astratta base per le dao factories;
  • RDBMSDAOFactory: Factory per i DAO su database relazionale;
  • OODBMSDAOFactory: Factory per i DAO su database ad oggetti;
  •   FaceFactory: classe astratta base per le face factories;
  • RDBMSFaceFactory: factory per le faces su database relazionali;
  • OODBMSFaceFactory: factory per le faces su database ad oggetti;
  • StorageType: interfaccia di enumerazione dei sistemi di storage;
  • PlainBandFaceImpl: è una classe astratta e fornisce dei comportamenti base per tutte le face che lavorano su un BandDAO (RDBMS) ed in particolare valorizzano i value objects utilizzando solamente gli attributi della tabella di riferimento (supponiamo BAND), senza cioè andare in join con altre tabelle; la definizione di una super classe di questo tipo è utile per la scrittura di tutte le concrete face da utilizzare in quanto essa fornisce i comportamenti per i metodi di ricerca. Le concrete sottoclassi dovranno solamente provvedere alla valorizzazione dei value objects.
  • RDBMSBandFullFace e RDBMSBandMinimumFace : sono le faces concrete cui si accennava al punto precedente;
  • BandVO: Value object band;
  • VO : interfaccia tag per tutti i value object;

il class diagram seguente riassume l'implementazione delle faces utilizzata:

Fig.6 Face Supertype Layers

  • UnknownFaceException e UnknownFactoryException sono eccezioni lanciate dalle FaceFactory e dalle DAOFactory quando viene richiesta rispettivamente una face o una factory (DAOFactory o FaceFactory) che non esiste;
  • RDBMSBaseFace : implementazione base delle faces che lavorano su database relazionali. Provvede alla centralizzazione delle caratteristiche comuni; nell'analizzare le faces descritte negli esempi si sarà notato come vi è una grande quantità di codice ridondante (apertura e chiusura di connessioni, statement, resultset, etc...). Attraverso l'introduzione di questa classe base la scrittura di nuove faces si semplifica notevolmente;

Questa è una possibile implementazione di una struttura di accesso ai dati in cui DAO e faces collaborano assieme. Ovviamente, come ripeto, l'esempio fornito è molto scarno, ma proprio per questo dovrebbe servire meglio a comprendere il concetto di fondo ed il funzionamento base. Nella realtà l'ipotetico Domain Model della nostra applicazione risulta sicuramente più complesso costringendo a rivedere ed ampliare alcuni concetti visti sinora; Per quel che riguarda i progetti su cui ho lavorato le differenze principali con gli esempi visti di sopra sono:

  • Maggiore grado di complessità del Domain Model;
  • Faces che legano più tabelle tra di loro (non come nell'esempio che puntano su una sola tabella);
  • Coesistenza di più database (DB2 e Oracle); in questo caso è possibili adattare le DAOFactory e le FaceFactory di modo che ognuna punti su uno specifico db (invece che RDBMSDAOFActory e OODBMSDAOFactory avremo DB2DAOFactory e OracleDAOFactory). Cade in questo caso la validità del metodo getDefaultDAOFactory in quanto coesistono nella applicazione più di una DAOFactory da utilizzare per default.


Conclusione top

I concetti illustrati dovrebbero essere sufficienti a fornire una comprensione (quantomeno essenziale) dei Data Access Object;  Se da una parte la realizzazione di classi che accedono ai dati può, in relazione agli esempi affrontati, complicarsi di molto a seconda della realtà che ci si trova di fronte, dall'altra non è detto che tutto ciò che è stato presentato sia necessario e che il disegno, a seconda della specifica esigenza, divenga più snello e semplice (basti pensare all'implementazione del CatalogDAO di PetStore per rendersi conto della semplicità del disegno rispetto all'utilizzo di Abstract Factory per i DAO + faces).

In sostanza, tutto ciò che è stato discusso ha affrontato l'argomento da un punto teorico e quindi talora ha complicato le cose, talora le ha semplificate. La verità è che come tutte le cose, una buona implementazione di un sistema di classi per accedere ai dati la si realizza scontrandosi con i problemi che sorgono su situazioni reali, dove si verificano quelle puntuali eccezioni alla regola che costringono a passare dal "come dovrebbe essere" al "come è".

E' possibile scaricare i files relativi al codice trattato in questo articolo in formato HTMl a questo link: Data Access Object.zip



Bibliografia top

Gamma,Helm,Johnson,Vlissides - "Design Patterns: Elements of Reusable Object-Oriented Software", Addison-Wesley Pub Co,1998;

Martin Flower, "Pattern of Enterprise Architecture", Addison Wesley, Addison-Wesley Pub Co, 2002;

Deepak Alur, John Crupi, Dan Malks, "Core J2EE patterns",Prentice Hall PTR, 2001;

Guidelines, Patterns, and code for end-to-end Java applications : Java Pet Store Sample Application


 Attachments List
Generic DocumentData Access Object
Username:
Password:
To sign up for an account, click register... Register
Hide





Powered By



Campagna Anti-IF


Skin


PARTNER
Zio Budda
HostingJava


LICENZA



Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons

Sitemap  © 2002-2004 Copyright Information. Privacy . Today is domenica 1 agosto 2010