Nel nostro percorso di analisi delle tecnologie per la costruzione di un‘applicazione, pubblichiamo questo mese una analisi comparata su quanto detto in precedenza relativamente alle tecnologie di persistenza. Nei prossimi articoli della serie, affronteremo l‘analisi della parte di presentation logic.
Introduzione
Giovanni: In questa serie di articoli, mese dopo mese, abbiamo presentato tecnologie e metodologie per la realizzazione di una ipotetica applicazione Java EE. Lo scopo, come più volte detto, non è quello di portare il lettore a una completa ed esaustiva conoscenza dei vari strumenti presentati (EJB, Hibernate e, nei prossimi articoli, JSF, Ajax, log4j e molti altri), ma piuttosto cercare di mostrare quale sia la strada che, partendo dall’analisi, conduce fino alla progettazione e implementazione con gli strumenti proposti.
Per quanto concerne gli strumenti di persistenza, la serie si è sdoppiata per portare avanti il discorso parallelamente sui due framework di persistenza più utilizzati in questo momento, vale a dire EJB e Hibernate. Le due serie di articoli portate avanti da Alfredo e da me hanno cercato di illustrare pregi e difetti di entrambi gli strumenti, mostrando come implementare lo strato di persistenza, sulla base delle considerazioni teoriche fatte nelle prime puntate della serie. Similmente, la serie proseguirà nei prossimi mesi con lo stesso spirito ma concentrando l’attenzione sulle tecnologie e sugli strumenti necessari per la realizzazione della presentation logic.
Prima di procedere in tal senso, con Alfredo abbiamo pensato che forse poteva essere utile presentare una breve analisi comparata relativamente a quanto detto nei mesi passati circa la realizzazione del layer di persistenza. Lo scopo è di proporre ai lettori una sintesi ragionata mettendo a confronto pregi e difetti di EJB e Hibernate. Per motivi di spazio concentreremo l’attenzione solo sugli aspetti rilevanti e su quegli che in qualche modo servono per “classificare” efficacemente un tool di ORM.
Considerazioni generali su EJB
Giovanni: Con la versione 3.0 (e ancora di più con la 3.1) EJB è migliorata molto. Ora abbiamo una tecnologia matura, che unisce la potenza di un framework enterprise (per la parte session già presente in precedenza con la 2.x) con la semplicità dell’approccio POJO annotation-based.
Si tratta di una nuova filosofia di lavoro che mi ha piacevolmente colpito, specialmente perche’ la semplificazione dello sviluppo ha migliorato il modo in cui gli IDE moderni gestiscono i componenti enterprise.
Mi piace molto anche la nuova separazione dei compiti (session per la Business Logic e entities per la persistenza) che non lascia spazio a dubbi o cattive interpretazioni (prima un entity poteva essere invocato da remoto, cosa permessa adesso solamente ai session bean).
EJB segue comunque l’impostazione delle release precedenti, offrendo un livello di astrazione maggiore rispetto a strumenti container-less come ad esempio Hibernate: per certi versi lo preferisco, anche se ancora mancano delle parti, o la semantica non è così espressiva. Questo livello di astrazione del framework, se da un lato limita un po’ l’iniziativa del programmatore (ad esempio l’impossibilità di inserire direttamente SQL potrebbe essere un problema), parallelamente consente di eliminare complessità e di limitare le scelte; è senza dubbio vero che troppa potenza espressiva può essere un’arma a doppio taglio, se non accompagnata da una approfondita conoscenza della teoria di un ORM (concetto tanto banale quanto applicabile a ogni framework o strumento enterprise).
In EJB troviamo una forte automazione nella gestione del lazy loading (per certi versi un bene), anche se avrei preferito una semantica (annotazioni) più potente per definire il comportamento in memoria e la gestione diretta (non manuale, ma con approccio astratto con le annotazioni) degli oggetti. Queste cose forse verranno sistemate nelle prossime release della specifica, magari grazie a una maggiore fusione con strumenti quali Hibernate. Il mio giudizio è comunque positivo.
Infine, per quello che riguarda l’ipotetica competizione che si sta prefigurando fra EJB e Hibernate, è presumibile che con il tempo andrà a sparire a causa della sempre maggiore affinità fra i due prodotti.
Anche se di recente Sun ha mostrato maggiore interesse per TopLink (più che altro per ragioni di affinità commerciale con Oracle), la comunità dei programmatori sta spingendo per portare a una sempre maggiore integrazione delle due parti. Come fa giustamente notare Alfredo, Hibernate è un prodotto, EJB una specifica. Se è vero che le due cose non sono direttamente confrontabili, certamente non le vedo in diretta contrapposizione ma anzi potrebbero diventare complementari.
Considerazioni generali su Hibernate.
Alfredo: Hibernate sin dalle sue prime versioni è stato senza dubbio il framework ORM di maggiore successo nel mondo Java. Una delle ragioni del suo successo a mio parere è stato aver messo a disposizione da subito un framework ORM molto più semplice da utilizzare dei famigerati Entity Bean che fino alla versione 2.1 erano veramente complessi e difficili da utilizzare. Il successo di Hibernate ha quindi indirizzato la stesura delle nuove specifiche EJB 3.0 (JSR220) tanto che molti degli sviluppatori di Hibernate, compreso il suo ideatore Gavin King, vi hanno contribuito pesantemente. Ciò non significa assolutamente che EJB 3.0 sia una copia di Hibernate, anche perche’ EJB 3.0 è un aspecifica che va oltre la persistenza, ma sicuramente l’esperienza del successo di Hibernate ne ha influenzato positivamente la stesura. Essendo EJB 3.0 una specifica ed Hibernate un prodotto è difficile fare un confronto serrato. Tant’è che Hibernate con i suoi moduli Hibernate Annotations e Hibernate EntityManager fornisce una implementazione della JPA (Java Persistence Api), la parte della specifica EJB 3.0 dedicata alla persistenza. Ciò significa che una delle scelte possibili a disposizione dell’architetto è quella di aderire a EJB 3.0 scegliendo Hibernate come framework di implementazione dello strato di persistenza.
Abbiamo detto che Hibernate è un tool ORM anche se in realtà esso fornisce servizi che vanno oltre all’obiettivo di base del mapping tra oggetti Java e tabelle di database relazionali. La gestione delle transazioni programmatica o dichiarativa, il lazy loading, l’eager loading, le funzioni di caching ne fanno un tool completo e ormai affidabile per la realizzazione del proprio strato di persistenza.
L’esperienza sul campo con Hibernate mi fa dare senza dubbio un giudizio molto positivo anche se è bene avvertire il lettore che utilizzare un ORM non è cosa semplice come si crede, ne’ tantomeno è la soluzione per tutti i problemi. È vero che l’utilizzo di Hibernate riduce le righe di codice da dedicare all’accesso ai dati, ma introduce anche la necessità nel team di sviluppo di nuove competenze al contrario di ciò che si pensa (l’idea sbagliata è che l’utilizzo di Hibernate consenta di potersi affidare a sviluppatori meno esperti visto che tanto ci pensa il tool!). L’attività di mapping del modello a oggetti su quello relazionale non è cosa banale e lo stesso utilizzo delle librerie ne richiede un’approfondita conoscenza affinche’ esse possano essere utilizzate al meglio.
Mapping semplice (1 tabella 1 entity): quanto lavoro si deve fare, come funzionano gli IDE, quanto offrono come approccio visuale per “disegnare” lo strato di persistenza?
Giovanni: Rispetto al passato e agli assurdi vincoli imposti dagli entity beans 2.x, adesso la situazione è decisamente cambiata. Non mi riferisco alle tanto vituperate prestazioni della release precedente (sulle quali ci sarebbe da discutere), ma piuttosto alla evidente incapacità di creare un modello in memoria che fosse anche minimamente realistico.
Adesso creare mapping fra un oggetto e una tabella (con le varie forme di cardinalità e navigazione) è una operazione non solo possibile ma anche semplice. Il mapping diretto e secco (una tabella –> un bean) è realizzabile in modo molto semplice anche grazie agli strumenti visuali messi a disposizione dai più recenti IDE.
Alfredo: A mio parere, il mapping uno ad uno tra oggetti e tabelle è estremamente semplice in Hibernate sia che si usi il vecchio approccio con i metadati XML sia che si faccia utilizzo delle annotazioni in stile JDK 5.0. Hibernate Tools è un insieme di tool per Hibernate 3 messi a disposizione come plugin di Eclipse che aiutano nelle diverse operazioni. Esiste un editor per i file di mapping XML, una console per configurare connessioni a database ed eseguire query HQL, tool per effettuare reverse engineering di schemi di database in file di mapping o bean annotati, task di Ant per eseguire forward e reverse engineering tra modello a oggetti e modello relazionale. Insomma il progetto è ormai a un ottimo grado di maturità e fornisce secondo me tutti gli strumenti di sviluppo necessari.
Mapping un po’ più articolati
Giovanni: Con la nuova specifica 3.0 (e in particolare con la 3.1) si fa quasi tutto, (per la prima volta in EJB) in modo anche piuttosto preciso.
La possibilità di realizzare mapping su gerarchie di oggetti (cioè mappare legami padre-figlio) secondo le varie strategie disponibili (una tabella per entità, una tabella in fondo alla gerarchia o soluzioni miste), ha davvero colmato una delle più importanti carenze del framework. Adesso mi posso sbilanciare nel dire che EJB 3.0 offre tutti gli strumenti per realizzare le applicazioni di tutti i giorni. Non ci sono più i bizzarri vincoli imposti dalla specifica precedente che di fatto ne impediva l’utilizzo. Restano scoperti quei casi particolari che a volte mi è capitato di incontrare come richieste del team di sviluppo su progetti per i quali ho lavorato. Mi riferisco a cose del tipo un bean mappato su più tabelle o cose del genere. Per questi casi rimane ancora valida come soluzione migliore la creazione di “view” sulle tabelle per poi mappare il bean su tali viste. Ricordo comunque che un entity bean è da considerarsi sempre una rappresentazione in memoria di un dato memorizzato nel sistema di persistenza, quindi una riga di una tabella. Se serve aggregare più colonne che arrivano da tabelle differenti o si opera con una collezione di entity beans (ovvero si opera una Join in maniera OOP) oppure si crea la vista aggregando quello che serve (attenzione che questo equivale a rifattorizzare concettualmente il modello dati).
Alfredo: Il problema del corretto mapping tra oggetti e database è a mio parere il punto cruciale dell’utilizzo di un ORM qualunque esso sia. In tal caso è a monte che vanno prese le giuste decisioni e ciò richiede una ottima conoscenza delle tecniche di modellazione a oggetti ma, è inutile negarlo, anche una buona conoscenza del mondo relazionale. Questo è tanto più vero quando si vuole effettuare un mapping più articolato andando a trasportare nel mondo relazionale concetti quali ad esempio ereditarietà e polimorfismo.
Dalla mia esperienza, l’approccio che lo sviluppatore molto spesso segue è quello di effettuare un mapping uno a uno a partire da una serie di tabelle con un insieme di oggetti, facendo corrispondere questi ultimi alla struttura delle tabelle stesse. È chiaro che con questo approccio si perdono molti dei vantaggi nell’utilizzo di un ORM. Anzi, per schemi molto semplici direi che si introduce una inutile complicazione.
Ancora peggiore è l’approccio in base al quale a partire da una serie di oggetti si genera uno schema di database, magari con qualche tool automatizzato, senza tenere minimamente conto delle caratteristiche del modello relazionale e delle regole da seguire per realizzare database efficienti e performanti.
Va sempre ricordato che ogni qualvolta che si aggiunge un nuovo strato alla nostra architettura sicuramente si introduce della complessità della quale va tenuto conto. Non è raro trovare in progetti che usano Hibernate prestazioni di accesso ai dati non ottimali rispetto a un tradizionale approccio JDBC-based; ma attenzione: questo accade non perche’ Hibernate non sia performante, tutt’altro, ma perche’ la scarsa conoscenza e l’illusione che un framework possa essere usato come una scatola chiusa senza la conoscenza della sua filosofia di funzionamento porta inevitabilmente a errori dai quali è poi difficile tornare indietro.
Tecniche di persistenza avanzata (lazy loading, allineamenti di chiavi etc.)
Giovanni: In questo settore EJB probabilmente non è completamente a suo agio. Il lazy loading, per quanto disponibile in maniera indiretta (e solo dopo essersi letti attentamente le pagine delle specifiche), è sempre un’arma a doppio taglio. Quello che ho notato è che il maggior livello di astrazione del framework (rispetto a Hibernate), induce il programmatore di una applicazione EJB 3.x ha scordarsi di certi aspetti.
Da questo punto di vista, la presenza in Hibernate di metodi appositamente pensati per “riattaccare” il bean allo storage spesso spinge il programmatore a tenere alta l’attenzione su quanto fatto. Per contro, ho spesso visto che l’approccio un po’ più a basso livello di Hibernate può portare in certi casi a qualche problema. La mancanza di un container infatti sposta tutta la responsabilità sul programmatore: il sistema non svolge per te le operazioni che non vengono fatte dal codice scrtto.
Probabilmente la soluzione ideale è un intelligente mix delle due politiche, cosa che presumo si potrà avere con la prossima generazione di ORM.
Alfredo: Come default Hibernate utilizza la strategia lazy-loading quando deve acquisire entità o insiemi di entità e utilizza degli oggetti che hanno funzione di proxy per eseguire il caricamento vero e proprio in base alle necessità. È possibile, agendo sui metadati, scegliere le politiche di fetching dei dati più appropriate come eager loading e batch-fetching. Inoltre Hibernate mette a disposizione un’architettura di cache a due livelli che può essere utilizzata per ottimizzare le prestazioni. Le possibilità sono molte e coprono secondo me tutte le esigenze. Hibernate consente addirittura nei casi più “disperati” di eseguire normali query SQL sul database. Il mio consiglio è sempre lo stesso: studiare approfonditamente il framework nelle parti di interesse per capire come sfruttare al meglio tutte queste possibilità.
Solidità alla evoluzione del modello dati (quanto devo lavorare se cambia un campo, una chiave o una relazione)
Giovanni: Reputo l’approccio di EJB 3.x basato sull’uso di annotazioni enormemente vantaggioso da questo punto di vista. Personalmente, sia che si disponga di un editor RAD per la parte di persistenza, sia che si operi “a mano”, ho trovato facile e intuitivo lavorare con annotazioni e direttamente sul codice Java tutte le volte che mi sono trovato a dover rifattorizzare il layer di mapping. Le prestazioni (anche se non espressamente misurate) della fase di refactoring hanno dato valori competitivi se non migliori rispetto a altri strumenti. Da questo punto di vista non rimpiango in nessun modo le tonnellate di XML presenti nelle applicazioni EJB 2.x e in Hibernate.
Alfredo: Effettivamente concordo con l’osservazione di Giovanni. Lavorare sui metadati in XML al variare del modello dei dati non è cosa molto agevole e spesso è un’attività piuttosto onerosa e soggetta a frequenti errori. Oggi con Hibernate questo problema è superato visto che, a patto di utilizzare la JDK 1.5, si può sfruttare la libreria Hibernate Annotations che è JPA compliant oltre ad offrire un superset di funzionalità.
Conclusione
Alfredo: Rispetto ad alcuni anni fa oggi le possibili scelte per la implementazione di uno strato di persistenza in applicazioni Java è piuttosto ricca. Rimanendo sempre valido il principio di codificare tutto lo strato di persistenza con il buon vecchio approccio JDBC, oggi esistono framework sofisticati e affidabili che assolvono a questo fondamentale compito.
La scelta a mio parere più sensata per chi dovesse affrontare oggi la scelta per un’applicazione è quella di studiare EJB3.0 e quindi JPA in particolare. Questo perch��� adottare le specifiche standard secondo me è sempre il modo migliore per garantire affidabilità, portabilità e longevità alle proprie applicazioni. Una volta presa familiarità con le tecniche ORM e con la specifica, resta da scegliere quale implementazione adottare per il proprio strato di persistenza. A questo punto consiglierei la scelta di Hibernate che implementa la specifica JPA, fornisce numerose funzionalità aggiuntive ed è sufficientemente maturo ed affidabile.
Alfredo Larotonda, laureato in Ingegneria Elettronica, lavora da diversi anni nel settore IT. Dal 1999 si occupa di Java ed in particolare dello sviluppo di applicazioni web J2EE. Dopo diverse esperienze di disegno e sviluppo di applicazioni web per il mercato finanziario e industriale, si occupa ora in particolare di aspetti architetturali per progetti rivolti al mercato della pubblica amministrazione. È Sun Certified Enterprise Architect (SCEA) e ha inoltre conseguito le certificazioni SCJP, SCWCD 1.3, SCWCD 1.4, SCBCD.
Giovanni Puliti ha lavorato per oltre 20 anni come consulente nel settore dell’IT e attualmente svolge la professione di Agile Coach. Nel 1996, insieme ad altri collaboratori, crea MokaByte, la prima rivista italiana web dedicata a Java. Autore di numerosi articoli pubblicate sia su MokaByte.it che su riviste del settore, ha partecipato a diversi progetti editoriali e prende parte regolarmente a conference in qualità di speaker. Dopo aver a lungo lavorato all’interno di progetti di web enterprise, come esperto di tecnologie e architetture, è passato a erogare consulenze in ambito di project management. Da diversi anni ha abbracciato le metodologie agili offrendo ad aziende e organizzazioni il suo supporto sia come coach agile che come business coach. È cofondatore di AgileReloaded, l’azienda italiana per il coaching agile.