MokaByte 89 - 8bre 2004 
Utilizzo di architetture
"rich thin client" e benefici del pattern Half-Object

di
Alessandro Trivilini

Lo scorso mese si è detto che applicazioni Web complesse, pur se sempre attraenti dal punto di vista del deployment e della manutenzione, soffrono sempre più per i limiti delle interfacce Html e del loro limitato livello di interattività. Vediamo allora in questo articolo un esempio di tecnologia che sfrutta al meglio il paradigma ad oggetti per realizzare applicazioni Web, senza usare Html come GUI.

Introduzione
Lo scorso mese un articolo di Sandro Pedrazzini ha introdotto la tecnologia basata sul concetto di "rich thin client" (RTC, [3]). Con questo articolo si intende continuare idealmente il discorso, mostrando un esempio pratico di utilizzo.
Tra i prodotti elencati nell'articolo di Pedrazzini, figurava anche Canoo ULC, di cui è stata riportata in modo sommario l'architettura, perché ritenuta particolarmente interessante. Questa permette infatti a chi sviluppa un'applicazione di utilizzare oggetti in rete in modo completamente trasparente, senza doversi cioè preoccupare del fatto che alcuni elementi dell'applicazione (widget, nel caso specifico) si trovino sul client e altri sul server.
Il design pattern utilizzato in ULC per rendere trasparente la comunicazione è il pattern Half-Object, anche noto in letteratura come HOPP (Half-Object Plus Protocol ([2]).
In questo articolo ci preme soprattutto mostrare gli effetti di questo pattern sulla libreria RTC in questione. Lasciamo ad altri, in un eventuale prossimo articolo, l'incombenza di entrare nei dettagli dell'architettura e di mostrare in modo più specifico l'implementazione del pattern.

 

Motivazione: libreria RTC
L'articolo dello scorso mese ha spiegato come con l'aumentare della popolarità e della diffusione del paradigma Web molte nuove applicazioni siano state scritte con interfaccia Web e molte vecchie siano state adattate per fornire un'interfaccia Web di tipo Html.
In molti casi, ci si è però accorti che questo approccio crea problemi di sviluppo e di manutenzione. Il motivo principale è determinato dal fatto che Html non è pensato per realizzare e gestire interfacce utente (GUIs) altamente interattive. Per risolvere i limiti di Html, gli sviluppatori di applicazioni Web sono così costretti ad integrare numerose tecnologie, come applet Java, Html, JSP, JavaScript, ActionScript, istanze XML proprietarie, ecc., che creano miscugli di codice e non fanno altro che complicare il lavoro di sviluppo e, soprattutto, di manutenzione.
Una possibile soluzione a questo problema è naturalmente quella di tornare all'idea di rich client, utilizzando tecnologie come Swing o SWT, che offrono maggiore interattività, con sistemi di eventi avanzati e widgets preconfezionati a disposizione. Il prezzo da pagare in questo caso è però la presenza di codice applicativo sul client (fat client), con conseguenze sia nello sviluppo (separazione client/server) che nel deployment (l'applicazione non risiede più interamente nel server, perciò ogni modifica all'applicazione comporta un aggiornamento del client).
La soluzione mostrata era quella di una libreria RTC (rich thin client) che abbini i vantaggi di entrambi i paradigmi, permettendo l'utilizzo di interfacce grafiche avanzate (rich), senza dover installare sul client codice applicativo. Abbiamo anche visto come Java, grazie alla sua onnipresenza, abbia giocato e giochi tuttora un ruolo determinante nello sviluppo e nella diffusione di queste librerie.

 

Trasparenza nella comunicazione
Come viene realizzata in ULC ([4]) l'idea di gestire l'applicazione interamente sul server, pur permettendo il suo utilizzo con architettura client/server, in cui il client rappresenta la GUI dell'applicazione?
Il concetto è relativamente semplice e viene reso possibile proprio dall'utilizzo del pattern Half-Object.
Questo pattern permette a ULC di offrire una API per Swing a livello server. Chi sviluppa utilizza questa API direttamente nel server (che corrisponde all'applicazione), senza nemmeno specificare la separazione client/server. È come se i widgets della GUI venissero utilizzati nel server. L'effettiva separazione tra client e server avviene solo durante l'esecuzione. In runtime, ogni oggetto si suddivide in due: una parte client (client half object) e una parte server (faceless server half object), che interagiscono tra di loro, come mostrato in fig. 1.
La gestione sul client dei relativi mezzi oggetti avviene tramite il cosiddetto UI Engine, un motore generico di interfaccia, non più grande di 300 KB. La sua funzione è paragonabile a quella del browser per le applicazioni Web.


Figura 1

Il pattern permette di nascondere l'elemento di comunicazione tra client e server, in modo tale che lo sviluppatore possa utilizzare i widgets di Swing come se questi si trovassero sul server.
L'implementazione del pattern prevede un proxy per ogni componente Swing. Il proxy rappresenta quindi le componenti Swing e viene suddiviso in due metà oggetti (half-object): la metà sul client interagisce effettivamente con Swing, mentre la metà sul server mette a disposizione la API per lo sviluppatore.
Considerando la presenza di uno standard JRE su client e su server, questo approccio permette di lavorare con una libreria relativamente ridotta e un design pulito.

 

Modello object-oriented
La funzionalità richiesta dalla GUI viene realizzata con codice Java object-oriented, al posto di miscugli di codice e tecnologie eterogenee. Il vantaggio per lo sviluppatore è evidente: può lavorare con un insieme minimo di utensili e un modello di programmazione semplice. In realtà sviluppa un'applicazione come se questa fosse stand-alone, diventando client/server unicamente in fase di esecuzione.
Come detto, affinchè questo sia possibile il client deve possedere il motore di presentazione (UIEngine). Questo motore, esattamente come un browser per Html, non contiene codice specifico di un'applicazione e può quindi essere utilizzato con ogni tipo di programma ULC. Siccome lo UIEngine è realizzato completamente in Java, può essere eseguito come applet (quindi senza necessità di installazione), come applicazione stand-alone, oppure in ambiente JNLP, cioè con il meccanismo di Java Web Start a disposizione del linguaggio Java.

 

Scaricamento dei dati "lazy"
La trasparenza nella comunicazione ha parecchi effetti interessanti. Tra questi la possibilità di gestire il traffico server-client dei dati in modo "lazy". Cosa significa? Significa che unicamente gli elementi effettivamente visualizzati vengono scaricati sul client. Gli altri rimangono sul server e vengono inviati al client unicamente in caso di bisogno. Facciamo un esempio: un utente sta visionando nella sua interfaccia grafica i dati di una lista con migliaia di elementi. Solo i dati che vede nella sua finestra sono stati effettivamente scaricati (10, 20, a dipendenza della dimensione del frame). Gli altri non si sono mossi dal server. Si sposteranno verso il client solo se l'utente effettuerà un'operazione di scrolling. Questo aiuta a spiegare come mai un'applicazione sviluppata sul modello RTC genera in media molto meno traffico di una tradizionale applicazione Web con interfaccia Html, in cui ad ogni interazione ci sono paginate di codice che vengono inviate al client.

 

Utilizzo
L'utilizzo di widget di una libreria RTC implementata con il pattern half-object può essere resa simile all'utilizzo di semplici elementi di interfaccia Swing, mantenendo nascosta la separazione client/server il fatto che la metà oggetto sul client in realtà delega la funzionalità grafica ad un elemento Swing, attraverso il pattern Adapter ([1]).
Nel caso di ULC, per ogni widget Swing (classi con il nome che inizia con "J", ad esempio JButton), esiste una corrispondente classe che inizia con "ULC" (ad esempio ULCButton), con la cui API lo sviluppatore deve interagire.
(In realtà non sempre è possibile questa conversione, sia perché è stata data maggiore attenzione all'ortogonalità delle singole API, sia perché essendo diverso il contesto di programmazione, ci possono essere alcune operazioni particolari che hanno senso unicamente in un contesto client/server. Si tratta però di una percentuale relativamente ridotta, rispetto a quanto può essere convertito direttamente).
Entriamo nel dettaglio di alcuni semplici esempi.

 

Confronto con Swing
Per fare un confronto a livello di codice con l'utilizzo di Swing, consideriamo l'esempio di una finestra contenente un pulsante.
Ecco la versione Swing della finestra, realizzata con una nuova classe che modelli la finestra stessa ereditando da JFrame.

public class JButtonWindow extends JFrame{
  JButtonWindow(String title){
    super(title);
    JButton button = new JButton("First Appearance");
    getContentPane().add(button);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
  }
}

Questo diventa il codice necessario per instanziare un oggetto e visualizzare la finestra:

JFrame frame = new JButtonWindow("JButtonWindow");
frame.setVisible(true);

Come detto, se per ogni classe Swing che rappresenta un widget, ne esiste una corrispondente ULC, questo significa che nel nostro caso avremo almeno ULCFrame e ULCButton a disposizione.
Ecco quindi il codice corrispondente in ULC:

public class ULCButtonWindow extends ULCFrame{
  ULCButtonWindow(String title){
    super(title);
    ULCButton button = new ULCButton("First Appearance");
    add(button);
    setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
  }
}

Le differenze sono dovute a scelte di semplificazione (add() chiamata direttamente sul frame, invece che sul content pane), o perché, basandosi ULC su Swing, ci sono operazioni che sono implicite (chiamata pack()).
Per il resto non ci sono grosse differenze. Da notare: il fatto che l'applicazione giri completamente sul server e che in runtime invii messaggi al client per mostrare la GUI non è visibile nel codice, e nemmeno lo sviluppatore deve preoccuparsene.
Questo è il codice di chiamata

ULCFrame frame = new ULCButtonWindow("ULCButtonWindow");
frame.setVisible(true);

Aggiunta di eventi
Vediamo un ulteriore aspetto che dimostri quanto la trasparenza riguardo l'architettura client/server sia totale. Aggiungiamo un'azione al pulsante appena creato. Facciamo in modo che il testo del pulsante venga modificato al primo click.
Ecco la versione Swing:

class ChangeTextAction implements ActionListener{
  public void actionPerformed(ActionEvent event){
    JButton source = (JButton)event.getSource();
    source.setText("Changed...");
  }
}

Ed ecco la versione ULC:

class ChangeTextAction implements IActionListener{
  public void actionPerformed(ActionEvent event){
  
  ULCButton source = (ULCButton)event.getSource();
  
  source.setText("Changed...");
  }
}

Se consideriamo che nello scenario di deployment più tipico l'azione viene eseguita sul server, può sorprendere come questo fatto non abbia nessun impatto a livello di codice. L'azione viene eseguita sul server e le modifiche nella metà del widget che riguardano l'interfaccia grafica vengono inviate all'engine grafico del client.
ULC mette a disposizione dello sviluppatore degli elementi di simulazione della comunicazione client/server. Questo significa che lo sviluppo avviene su un'unica macchina, esattamente come se si trattasse di un'applicazione stand-alone. Il codice sopra dimostra perciò come una libreria RTC potrebbe essere facilmente utilizzata al posto di Swing anche per applicazioni stand-alone. Solo nel caso in cui, per regioni di scalabilità, si desiderasse introdurre un'architettura distribuita, si passerebbe ad un deployment client/server, senza toccare una sola linea di codice.

 

Esempio applicativo
I produttori di librerie RTC forniscono vari esempi di applicazioni implementate con la loro tecnologia.
Restando in un ambito conosciuto, mostriamo l'interfaccia grafica di un negozio virtuale.


Figura 2
(clicca sull'immagine per ingrandire)

L'applicazione in questione è messa a disposizione come servlet e la comunicazione con il client avviene via http (o https). Da un punto di vista architetturale si tratta quindi di un'applicazione Web a tutti gli effetti, che però, invece di un browser, necessita dell'engine grafico di 300 KB.
L'applicazione, con una serie di altri esempi, può essere facilmente provata all'indirizzo http://www.canoo.com/ulc/demos/onlineshop.html
Da notare come l'inserimento di un elemento nel carrello della spesa possa essere eseguito con una semplice operazione di drag-and-drop.

 

Conclusioni
In questo articolo abbiamo ripreso il discorso delle architetture rich thin client, proposte in un articolo di Pedrazzini del mese scorso [3]. Abbiamo parlato del pattern Half-Object, anche conosciuto come pattern HOPP, senza entrare nel dettaglio della sua architettura, ma osservando il suo beneficio maggiore: la trasparenza della comunicazione tra elementi distribuiti.
Questo pattern sta alla base dell'architettura di Canoo ULC, menzionata, assieme ad altre, nell'articolo dello scorso mese a proposito di tecnologie rich thin client.
Con questo modello è possibile rendere trasparente il fatto che un oggetto esista e venga utilizzato in più spazi di indirizzamento. Grazie a questo pattern una libreria RTC può utilizzare widget grafici nell'applicazione lato server, senza preoccuparsi del fatto che poi questi in runtime abbiano una loro rappresentazione grafica sul client.

 

Bibliografia
[1] Gamma et al.: Design Patterns, Elements of Reusable Object-Oriented Software, Addison Wesley, 1995.
[2] Meszaros G.: Pattern: Half-Object+Protocol Pattern, in Coplien, Schmidt: "Patterns Languages of Program Design", Addison-Wesley, Reading 1995.
[3] Pedrazzini S.: Java: la piattaforma ideale per architetture "rich thin client".
[4] UltraLightClient, Canoo ULC: http://www.canoo.com/ulc


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