Nel precedente articolo, abbiamo visto un esempio di integrazione tra client Flex e server Java in EJB3. In questo articolo andiamo avanti introducendo il framework Tide e illustrandone le principali caratteristiche.
Introduzione
Abbiamo già visto un esempio di utilizzo del framework GraniteDS per integrare un client in Flex con il server in Java, e precisamente in EJB3. In questo articolo introdurremo il framework Tide e parleremo delle sue principali caratteristiche.
Il progetto Tide è stato sviluppato per creare delle applicazioni Flex con un’architettura semplice in grado di sfruttare alcune caratteristiche simili a quelli di JBoss Seam, come la dependency injection, un container lato client di managed entity e l’integrazione con componenti server. A causa di queste similitudini, la prima integrazione è stata fata con JBoss Seam, e poi successivamente anche con Spring, EJB3 e CDI. In genere, l’approccio che si usa con Tide è quello di minimizzare la quantità di codice necessaria a far funzionare l’interazione tra server e client.
Il framework Tide
Tide rappresenta la parte Data Service di GDS e si prefigge come scopo quello di semplificare il modello di programmazione dei client Flex quando occorre effettuare l’integrazione con framework lato server.
Uno dei principali obiettivi di Tide è quello di rimuovere tutto il codice ridondante e poco utile sia lato server che lato client, come le destinazioni per i Remote Object nel service-config.xml e mxml.
Tide è basato molto sul concetto di client context, il quale fa da proxy per tutte le comunicazioni tra client e server. Questo client context fornisce una cache ed un component container in maniera del tutto simile al PersistentContext o Seam context, lato server.
Il framework Tide è ispirato pesantemente a JBoss Seam, con un’architettura basata su componenti stateful. Esso fa propri i più importanti concetti di Seam: bijection, events, conversations e fa uso delle annotazioni Flex 3. Come Seam riduce la complessità usando classi AS3 annotate che richiedono una configurazione minima.
Un’applicazione Tide è composta da 3 tipi di componenti:
- Managed entities (il data model);
- UI view (componenti Flex mxml o ActionScript annotati);
- Tide components (classi ActionScript annotate).
L’architettura è centrata sul concetto di Tide context, che funge da bus per gli eventi tra la UI view, i Tide components e il server, e come un container per i managed entities.
I Tide components contengono la logica dell’applicazione ed in generale svolgono le seguenti attività:
- sono in attesa di events (provenienti dall’interfaccia o dal server);
- contengono la logic dell’applicazione client;
- interagiscono col server.
Un esempio
Consideriamo un semplice esempio dove lato server abbiamo un componente Seam che gestisce una semplice ricerca:
Seam component
@Stateful @Name("hotelSearch") @Scope(ScopeType.SESSION) @Restrict("#{identity.loggedIn}") public class HotelSearchingAction implements HotelSearching { @PersistenceContext private EntityManager em; private String searchString; private int pageSize = 10; private int page; @DataModel private List hotels; public void find() { page = 0; queryHotels(); } ... private void queryHotels() { hotels = em.createQuery( "select h from Hotel h where lower(h.name) like #{pattern} or lower(h.city) like #{pattern} or lower(h.zip) like #{pattern} or lower(h.address) like #{pattern}") .setMaxResults(pageSize) .setFirstResult( page * pageSize ) .getResultList(); } ... public List getHotels() { return this.hotels; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } @Factory(value="pattern", scope=ScopeType.EVENT) public String getSearchPattern() { return searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%'; } public String getSearchString() { return searchString; } public void setSearchString(String searchString) { this.searchString = searchString; } @Remove public void destroy() {} }
Applicazione MXML
(Step 1) import org.granite.tide.Seam; [Bindable] (Step 2) private var tideContext:Context = Seam.getInstance().getSeamContext(); // Components initialization in a static block { (Step 3) Seam.getInstance().addComponents([HotelsCtl]); } [Bindable] [In] (Step 4) public var hotels:ArrayCollection; private function search(searchString:String):void { (Step 5) dispatchEvent(new TideUIEvent("searchForHotels", searchString)); }
Componente Tide
import mx.collections.ArrayCollection; [Name("hotelsCtl")] [Bindable] public class HotelsCtl { [In] (Step 6) public var hotels:ArrayCollection; [In] (Step 7) public var hotelSearch:Object; (Step 8) [Observer("searchForHotels")] public function search(searchString:String):void { (Step 9) hotelSearch.searchString = text; hotelSearch.find(); } }
Analisi dell’esempio
Facendo riferimento agli step del codice riportato sopra, vengono eseguite diverse operazioni che riassumiamo di seguito.
Step 1
L’applicazione inizializza il Tide context e registra se stessa in tale contesto. Per fare questo il preinitialize handler definito nell’applicazione principale deve invocare Seam.getInstance().initApplication() (se cambia la teconologia server verrà invocato Spring.getInstance().initApplication() o Ejb.getInstance().initApplication() o Jcdi.getInstance().initApplication()). L’MXML principale è quindi registrato nel Tide context e tutti gli MXML annotati con [Name] verranno registrati automaticamente nel context con il loro nome (o con quello fornito nell’annotazione). Questo è molto importante perchè permette al framework Tide di intercettare gli eventi lanciati dall’applicazione e di notificare i componenti interessati ad alcuni eventi. Ecco l’esempio se avessimo avuto un altro componente mxml, annotato con [Name]
...
Login.mxml
[Name] <![CDATA[ import org.granite.tide.seam.security.Identity; ...
Login.mxml verrebbe registrato nel Tide context con il nome loginUI. Questo consente l’injection con l’annotazione [In] e la possibilità di lanciare Tide events. Il nome potrebbe essere impostato con [Name(“someName”)].
I componenti definiti dentro l’MXML possono ora essere registrati nel Tide context.
Step 2
L’applicazione principale inizializza e ottiene il Tide context.
Step 3
Viene registrato in Tide il componente HotelsCtl. Tide effettuerà la scansione della classe del componente che verrà individuata grazie all’annotazione. Una volta registrato, sarà possibile istanziare e usare l’unica istanza del componente tramite tideContext.HotelsCtl.
Step 4
L’annotazione [In] definisce un data binding tra la variabile chiamata hotels nel Tide context e la variabile hotels del componente mxml. Inoltre ci sarà un legame anche con la variabile server hotels del Seam context e Tide sincronizza i due context a ogni RPC.
Step 5
Quando l’utente clicca sul pulsante search, l’applicazione lancia un TideUIEvent. La classe che definisce questo evento ha un campo per mantenere delle informazioni (in questo caso la stringa da ricercare inserita dall’utente).
Step 6
Il componente Tide usa l’injection con la variabile del context hotels. In questo modo, sia qui che nel file mxml, avremo sempre la stessa collezione perche la fonte dei dati è la stessa variabile (quella del Tide Context).
Step 7
Il componente Tide usa l’injection anche con la variabile context hotelSearch. Poichè non abbiamo ne’ definito un tipo per questa variabile e ne’ esiste un componente Tide registrato con questo nome, Tide istanzia un oggetto proxy. Qualunque chiamata di metodo su questo oggetto proxy provocherà una chiamata remota dello stesso metodo verso un componente Seam server chiamato hotelSearch.
Step 8
Il componente Tide dichiara un metodo observer (annotato con Observer). Questo metodo verrà invocato quando nel context verrà lanciato un evento del tipo aspettato (nel nostro caso searchForHotels). Quindi questo metodo verrà eseguito quando l’utente clicca sul pulsante di ricerca. Il metodo riceverà l’evento come parametro.
Step 9
Viene impostata la proprietà searchString sull’oggetto proxy e successivamente viene invocato il metodo find. Quando viene invocata find Tide sincronizza le proprietà trasmettendo la stringa al server e poi esegue la chiamata di metodo sul componente Seam. Sul componente server, hotels è annotato con @DataModel, in tal modo Tide intercetta l’outjection e spedisce il valore indietro al client Flex assieme al risultato della chiamata remota. Sul client, Tide effettua il merge tra questa collezione e la collezione hotels presente nel Tide context. Il risultato è che il contenuto del DataGrid, legato a questa variabile, verrà aggiornato.
Questo semplice esempio mostra molti dei concetti che stanno dietro al framework Tide. Usando il contesto come repository centrale si disaccoppia l’UI layer dalla logica dell’applicativo minimizzando anche la quantità di codice necessario.
Usando Tide e Seam sul server si ottiene un’integrazione automatica che consente ai programmatori di concentrarsi solamente sulla logica dell’applicativo.
Conclusioni
In questo articolo abbiamo parlato del framework Tide illustrando la logica che sta dietro questo progetto. Nel prossimo articolo indagheremo ulteriormente su Tide illustrando altre caratteristiche importanti che si rilvelano molto utili per l’integrazione tra client e server.
Riferimenti
[1] Il sito ufficiale del progetto GraniteDS
http://www.graniteds.org/
[2] GraniteDS Blog
http://graniteds.blogspot.com/