Dopo aver parlato di BlazeDS e aver visto degli esempi pratici di utilizzo, ci occupiamo di un altro interessante progetto open source, GraniteDS, che permette di integrare un front end in Flex con un‘applicazione Java-based fornendo molte interessanti funzionalità.
Introduzione
Granite Data Services (GDS) è un’alternativa open source ad Adobe LiveCycle Data Service (LCDS) e BlazeDS che facilita l’integrazione tra Flex e Java. Tuttavia GDS e BlazeDS (o LCDS) non sono intercambiabili: le applicazioni progettate per BlazeDS devono essere modificate per essere compatibili con GDS e viceversa. Lo scopo principale del progetto è fornire un framework per integrare applicativi Flex, EJB3, Seam, Spring, Guice, POJO.
GDS è progettato per essere leggero, robusto, veloce e facilmente configurabile e sin dalle prime versioni comprende un potente strumento di code generation, Gas3, eseguibile sia come task ant, che come plugin per Eclipse.
Caratteristiche principali di GDS
Granite Data Services pone grande enfasi all’integrazione con le tecnologie Java EE ed è stato definito una “open adapter architecture” che facilita
- l’interoperabilità con i più popolari web framework
- l’interazione con Ejb3 session beans (con o senza JBoss Seam)
- l’interazione con Spring beans con Acegi security
- la compatibilità con Google Guice services (con Warp persistence)
- il supporto verso servizi POJO
Da questo punto di vista però non vediamo grosse differenze tra GraniteDS e BlazeDS se non parliamo di importanti caratteristiche di GDS come ad esempio il controllo sul processo di serializzazione.
La comunicazione tra client Flex e server Java EE può avvenire infatti con tre codifiche:
- XML (HttpService)
- SOAP (WebService)
- AMF (RemoteObject)
L’ultima soluzione, AMF con RemoteObject, è quella più efficiente e GraniteDS fornisce una implementazione completa di tale protocollo e tutta una serie di factories per effettuare chiamate remote verso servizi lato server implementati attraverso POJO, EJB 3, Seam, Spring, e Guice.
Grazie al supporto verso AMF3, GraniteDS pemette l’uso di RemoteObjects nei file MXML. Ad esempio, supponendo di avere un servizio chiamato Person con il metodo findAllPersons() è possibile richiamarlo dal lato client con (Person.mxml):
<mx:Application xmlns_mx="http://www.adobe.com/2006/mxml" creationComplete="srv.findAllPersons()"> ="{srv.findAllPersons.lastResult}" />
Tuttavia il protocollo AMF, sia in LCDS/BlazeDS sia in GraniteDS, non fornisce nessun modo per trasferire campi privati o protetti. Solo i campi pubblici, non statici e non transitori vengono presi in considerazione. Non è possibile serializzare proprietà che si vuole tenere private, a meno di rendere gli Entity Beans, Externalizable (implementando diversi metodi readExternal/writeExternal sia dal lato Java che AS3). Questo è un compito tedioso che può generare inoltre diversi errori difficili da rintracciare (ed è inoltre impossibile da adottare quando non si possiede il codice degli entty bean).
Per superare tale limitazione GraniteDS fornisce uno specifico meccanismo di serializzazione chiamato externalizers [1], [2].
Externalizers
Con gli Externalizers GDS, non è necessario far diventare i JavaBeans Externalizable, ed è possibile usare il Gas3 per generare i bean AS3 fortemente tipizzati, con campi privati per le informazioni non pubbliche e con il supporto al lazy loading (vedi dopo).
Supponiamo di avere un entità bean rappresentante una persona in EJB3 (person.java)
package test.granite.ejb3.entity; import java.io.Serializable; ... @Entity public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Integer id; @Version private Integer version; @Basic private String firstName; ... public Integer getId() { return id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } ... }
Questo entity bean ha una proprietà read-only (id), una completamente privata (version) e una read/write (firstName). Con la serializzazione standard non saremmo in grado di mandare i campi id e version al client Flex. Potremmo rendere pubblici i campi e corredarli di metodi getters e setters, ma ovviamente esporremmo questi campi a modifiche accidentali. Una soluzione è far implementare all’entity bean l’interfaccia java.io.Externalizable invece della java.io.Serializable, ma questo come abbiamo detto richiede l’implementazione dei metodi readExternal e writeExternal, compito tedioso e soggetto a errori.
Con gli externalizers di GraniteDS, senza modificare il bean possiamo serializzare tutte le proprietà dell’entity e grazie a Gas3 non occorre neanche scrivere manualmente il bean in AS3. Ecco un bean generato in automatico con Gas3 (Person.as):
package test.granite.ejb3.entity { import flash.utils.IDataInput; ... use namespace meta; [Bindable] public class PersonBase implements IExternalizable { private var __initialized:Boolean = true; private var __detachedState:String = null; private var _firstName:String; private var _id:Number; private var _version:Number; meta function isInitialized(name:String = null):Boolean { if (!name) return __initialized; ... } public function set firstName(value:String):void { _firstName = value; } public function get firstName():String { return _firstName; } public function get id():Number { return _id; } ... public function readExternal(input:IDataInput):void { __initialized = input.readObject() as Boolean; __detachedState = input.readObject() as String; if (meta::isInitialized()) { ... } } public function writeExternal(output:IDataOutput):void { output.writeObject(__initialized); output.writeObject(__detachedState); if (meta::isInitialized()) { ... } } } }
Il bean generato riproduce tutte le proprietà trovate nel JavaBean e aggiunge il supporto per l’inizializzazone lazy.
Lazy Loading
Quando si usano tool di persistenza come Hibernate, TopLink, EclipseLink, OpenJPA o DataNucleus, si corre il rischio di caricare l’intero database se non si usa una strategia lazy per il fetching. Ci sono 2 tipi di associazioni tra bean: proxy (single-valued associations) e collections (come List, Set, Bag e Map). Granite Data Services, tramite gli Externalizer supporta entrambi i tipi di associazioni.
Gravity
Il Data Push in GDS, chiamato Gravity, è implementato come un servizio Comet-like con messaggi AMF3 inviati tramite HTTP (no RTMP, no specific internet port), ed è basato sul protocollo Bayeux. Questa implementazione è disponibile per Tomcat 6.0.14+, JBoss 4.2.2+ e Jetty Continuations 6.1.15+ e fornisce pure supporto per adattatori JMS. GDS usa un canale specifico (org.granite.gravity.channels.GravityChannel) che deve essere usato per le destinazioni data push. Le implementazioni del Data Push di BlazeDS e di GDS sono entrambe basate sullo stesso tipo di architettura Comet-like e quindi sono molto simili.
La configurazione di Gravity viene fatta attraverso una sezione dedicata nel file granite-config.xml. [3]
Gas3
Gas3 è un potente strumento di code generation implementato sia come plugin per Eclipse che come task Ant. Il progetto di questo strumento è nato dall’idea di un processo di sviluppo degli applicativi strutturato nelle seguenti fasi:
- creare il modello del database scrivendo gli entity bean EJB3;
- usare Gas3 per generare i corrispondenti AS3 dei bean per replicare il modello dati dal lato client;
- scrivere la business logic con session beans, Spring, Guice o Pojo;
- infine scrivere l’applicazione Flex (mxml).
Abbiamo già visto l’utilità di questo strumento parlando degli externalizer dove è possibile usare il Gas3 per generare facilmente i bean AS3, la cui scrittura tediosa può portare a errori.
Tide/Client framework
Per far comunicare un applicativo Flex con un server Java, abbiamo visto che è possibile utilizzare i RemoteObject e invocare i servizi esposti dal server. Un’altra soluzione che ci viene offerta da GraniteDS è quella di usare un framework che, oltre a semplificare la programmazione Flex lato Client minimizza la quantità di codice necessario per far funzionare tale interazione. L’approccio è simile a quello di JBoss Seam e inizialmente lo scopo principale del progetto era quello di estendere il modello di programmazione Seam ai client Flex e rendere l’uso dei componenti Seam più facile. Questa è anche la ragione per la quale la prima integrazione di Tide è stata fatta proprio con questo framework.
Tide è basato molto sul concetto di client context, che si interpone nelle comunicazioni tra client e server. Questo contesto client fornisce una cache per gli entity e un container per i componenti, allo stesso modo del Seam Context. Le caratteristiche principali sono:
- i client possono lavorare con oggetti Hibernate/JPA detached fortemente tipizzati grazie al supporto dell’inizializzazione lazy e al generatore Gas3;
- tutte le interazioni client/server sono fatte esclusivamente tramite chiamate di metodi su servizi esposti dal server, ed in tal modo si rispettano le politiche di sicurezza definite nel lato server;
- tutti le istanze di entity sono uniche nel contesto Tide. Questo permette di mantenere un corretto data binding nelle chiamate di servizi remoti.
I componenti UI definiti negli MXML possono essere registrati nel Tide context e quindi possono lanciare eventi all’interno del context o, nel caso in cui si registrino come observer, possono essere avvisati ogni volta che viene lanciato l’evento corrispondente.
Nell’ultima versione inoltre è stato aggiunto il supporto verso le conversazioni JCDI.
Google App Engine support
Google App Engine (GAE) permette di creare applicazioni web il cui deploy può essere fatto sullo stesso sistema in cui risiedono le applicazioni Google. Queste applicazioni sono facili da mantenere e facili da scalare non appena le esigenze di traffico e di storage lo richiedano. Con il GAE non ci sono server da mantenere: basta fare l’upload delle applicazioni per renderle disponibili agli utenti.
GAE è tra le piattaforme supportate da GDS, ed è possibile vedere un’applicazione funzionante all’indirizzo [4] che mostra l’integrazione tra GDS, Spring MVC e JDO/DataNucleus. Il progetto Eclipse è prelevabile all’indirizzo [5].
L’ambiente GAE è molto interessante perche’ pienamente clustered: ogni richiesta può essere gestita da una differente e imprevedibile istanza server. L’unico modo per permettere un collegamento tra differenti richieste o utenti è usare il datastore o la memcache (o una combinazione di entrambi). In questa implementazione di GraniteDS si è scelto di usare le API memcache che permettono una comunicazione più veloce tra i processi e perche’ è più facile da usare. Lo svantaggio è che la memcache non è un archivio affidabile e che le sottoscrizioni dei client o alcuni messaggi potrebbero essere persi sotto condizioni di utilizzo estremi. Per applicazioni non critiche tuttavia la situazione è accettabile.
In particolare c’è un nuovo provider Gravity per GAE basato su Memcache. L’implementazione è abbastanza semplice: le sottoscrizioni Gravity e i canali vengono memorizzati nella cache con una scadenza configurabile. Ogni canale mantiene un certo numero di messaggi pendenti nella cache. I publisher memorizzano nuovi messaggi aggiornando il numero di messaggi correntemente presenti e i subscriber controllano ad intervalli regolari il contenuto del canale prelevando i messaggi ed aggiornando il contatore.
Documentazione ed esempi
GraniteDS è corredato inoltre di una ricca documentazione, consultabile online [1], dove vengono trattate tutte le caratteristiche principali di GDS. Nella distribuzione principale di GDS sono inclusi anche una serie di progetti di esempio (nella cartella examples) molto utili da utilizzare come modelli per le proprie applicazioni. Questi gli esempi presenti:
- graniteds_pojo: un semplice esempio con un POJO session service chiamato tramite RemoteObject da MXML;
- graniteds_chat: una semplice chat che dimostra l’uso di Gravity per gestire una comunicazione real-time tra client Flex;
- graniteds_ejb3: semplice rubrica con EJB3/JPA;
- graniteds_spring: semplice rubrica con Spring/JPA; viene mostrata anche l’integrazione con la sicurezza in Spring;
- graniteds_guice: semplice applicazione CRUD con Guice/WARP;
- graniteds_tide_ejb3 / graniteds_tide_spring / graniteds_tide_seam / graniteds_tide_jcdi: semplice rubrica con EJB3/JPA, Spring/Hibernate, Seam/JPA, o JEE6/JCDI. Vengono illustrati il framework lato client Tide, paged collections, lazy loading, client-side authorization integration, multiuser data updates (lo si può vedere aprendo l’applicazione con differenti browser) e l’uso del JCDI (introdotto con JEE 6) per il binding dei componenti:
- seam_flexbooking: Un esempio completo che mostra la piena integrazione con Seam.
Conclusioni
Questa trattazione, lungi dall’essere un resoconto esaustivo del progetto GDS, ha avuto come obiettivo quello di illustrare alcune caratteristiche interessanti di GraniteDS e del perche’ molti sviluppatori lo preferiscono a BlazeDS. Granite Data Services offre un framework che permette lo sviluppo di applicazioni RIA utilizzando le migliori tecnologie sia lato client che lato server. È inoltre un progetto open source con una ricca documentazione ed una comunità di sviluppatori molto attiva.
Riferimenti
[1] Il sito ufficiale del progetto GraniteDS
http://www.graniteds.org/
[2] GraniteDS Blog
http://graniteds.blogspot.com/
[3] Data Push con Gravity
http://www.graniteds.org/confluence/display/DOC20/3.+Data+Push+(Gravity)
[4] Un’applicazione di esempio su GAE (effettuare il login con admin/admin o user/user)
http://gdsgae.appspot.com
[5] Progetto eclipse dell’applicazione di esempio deployata su GAE
http://www.graniteds.org/public/download/gdsgae.zip
Nato a Vittoria (RG) ha conseguito la laurea in Ingegneria Informatica all’Università di Catania nel luglio del 2003. Si interessa del mondo Java da più di 10 anni e dopo aver lavorato per 3 anni come sviluppatore software su piattaforma J2EE, oggi svolge attività di consulenza e formazione in provincia di Treviso e cura il sito www.tradingonline-news.com