Il
problema
Il nostro cliente è la famigerata multinazionale
ACME (sì, proprio i fornitori ufficiali di Willy
il Coyote) questa volta nei panni di impresa operante
nel settore dell'IT; per via delle sue necessità
tecniche e commerciali, tale ditta possiede un sistema
informativo di notevoli dimensioni, una parte del quale
siamo chiamati a gestire.
La nostra responsabilità verte in particolare
sul mantenimento e sul controllo del sistema informativo
del quartiere centrale di ACME, sparpagliato in quattro
edifici - creativamente etichettati 'A', 'B', 'C' e
'D' - ognuno ovviamente composto di diversi piani e
stanze.
La gestione di un tale parco hardware e software presenta
notevoli difficoltà, ragion per cui si decide
di implementare una Web Application, tramite la quale
dovrà essere possibile monitorare in tempo reale
lo stato di funzionamento dei diversi componenti del
sistema informativo, in modo da poter inviare rapidamente
in loco un tecnico in caso di problemi; dal momento
che prevenire è meglio che curare, inoltre, la
nostra Web Application dovrà tener conto anche
dei dati in tempo reale riguardanti il carico dei diversi
apparati, così da poter individuare e risolvere
potenziali problemi in anticipo, prima che essi portino
al malfunzionamento o all'interruzione di una parte
dei servizi.
La
nostra applicazione dovrà quindi interfacciarsi
sia ai diversi database di ACME, contenenti l'inventario
aggiornato dei sistemi da monitorare ed i loro profili
di configurazione, sia ad ipotetici sistemi real-time
di verifica del carico; i dati provenienti da entrambe
questi sorgenti dovranno venire integrati trasparentemente,
così da creare l'illusione di una sorgente dati
unica.
Nella
sua versione iniziale, l'applicazione in oggetto dovrà
mettere in grado gli operatori di verificare lo stato
di funzionamento dei diversi dispositivi, oltre che
di gestire la relativa 'base dati globale' tramite inserimenti,
modifiche e cancellazioni di nodi di rete.
Tali operatori utilizzeranno una quantità di
sistemi client differenti, sulle cui caratteristiche
non è possibile, o è semplicemente antieconomico,
imporre vincoli di uniformità: in sostanza, si
richiede che l'applicazione sia portabile ed indipendente
da piattaforme specifiche ed eventuali moduli software
installati presso i sistemi degli utenti, che potranno
variare nel corso del tempo, e sui quali si vogliono
minimizzare i costi di manutenzione.
Analisi
del problema
La struttura inerentemente distribuita dell'applicazione
si presta assai bene ad una sua realizzazione in un
contesto a noi noto, vale a dire l'ambiente J2EE.
Inoltre le caratteristiche dell'interfaccia ed i requisiti
di universalità dell'accesso ci indirizzano in
maniera naturale all'implementazione di un client Web-based.
L'implementazione
dell'applicazione richiesta - che con molta originalità
battezzeremo 'NetView' - è tutt'altro che elementare,
e come ogni sistema distribuito richiede la valutazione
di un numero significativo di fattori teorici e pratici.
Nel corso della nostra trattazione ci concentreremo
essenzialmente sugli aspetti legati agli scopi di questa
serie di articoli, vale a dire primariamente sulle caratteristiche
del layer di presentazione (interfaccia utente) e più
in breve sulle scelte fondamentali nel design di sistemi
distribuiti con interfacce utente in Java, senza una
comprensione delle quali la conoscenza di JSP, JSTL
e Java Server Faces [3] non solo non sarebbe efficace,
ma rischierebbe addirittura di portare all'implementazione
di architetture sbilanciate e penalizzanti.
L'implementazione
di esempio di NetView è scaricabile dalla sezione
Risorse: la sua installazione, nonché la verifica
ed il confronto dei sorgenti - contenuti nell'archivio
.war - con il materiale presentato in questo articolo,
sono fortemente raccomandati e costituiscono un ausilio
fondamentale per la comprensione degli argomenti trattati.
NetView,
secondo quanto detto, è composto di tre blocchi
fondamentali, che andiamo ora ad esaminare.
Il
sistema informativo
Il sistema informativo cui NetView deve interfacciarsi
è costituito da un insieme di risorse eterogenee
e probabilmente appartenenti a generazioni informatiche
diverse. Questa è una situazione che si presenta
con continuità nella realizzazione di applicazioni
distribuite, in cui il nuovo deve non solo convivere,
ma utilizzare e 'rimodernare' l'esistente, possibilmente
senza introdurre alcun mutamento in sistemi, ancorché
datati, comunque perfettamente funzionanti e centrali
per la vita d'impresa e per la gran parte dei software
di gestione e produzione.
Nel nostro caso è ad esempio ragionevole attendersi
di aver a che fare con una pluralità di database
multigenerazionali, sistemi di controllo in tempo reale,
sistemi di distribuzione ad oggetti e componenti ad
accesso diretto, i quali NetView dovrà integrare
e gestire in maniera armonica, fornendo l'astrazione
di un sistema informativo unitario.
Le
regole di business logic
Lo strato di business logic è il contenitore
delle regole di trattamento dei componenti del sistema
informativo; in altre parole mentre lo strato 'sistema
informativo' deve fornire a NetView una visione globale
ed un accesso unitario ad un mondo in realtà
segmentato e probabilmente disorganizzato, lo strato
'business logic' incapsula le regole per l'ottimizzazione
e la gestione sicura di tale patrimonio informativo,
indipendentemente da come esso risulti fisicamente raggiungibile
ed utilizzabile.
Lo
strato di presentazione
Il presentation tier, su cui si concentra in particolare
la nostra attenzione, è responsabile per la realizzazione
dell'interfaccia utente, vale a dire per quei meccanismi
software che consentono agli utenti di accedere ed interagire
con i contenuti del sistema informativo. Al contrario
di quest'ultimo e dello strato di business logic l'interfaccia
utente non è necessariamente 'una'. Il suo ruolo
è infatti semplicemente quello di 'punto d'accesso
alle informazioni', ed è quindi perfettamente
legittimo implementare nel corso del tempo diverse interfacce
utente - eventualmente basate su framework differenti
(HTML, Swing, SWT [4], Applet, architetture particolari
quali Droplets [5], etc.) - le quali convivono e permettono
a diverse classi di utenti di accedere al medesimo sistema
informativo, del quale forniscono viste di volta in
volta diverse per struttura e funzionalità. Nel
nostro campo, un esempio di presentation tier alternativo
potrebbe consistere in un'applicazione WML o light-HTML
con la quale un gruppo gli amministratori di sistema
potrebbe accedere attraverso un'interfaccia utente semplificata
ad informazioni on-time sui soli componenti di loro
diretta responsabilità.
Si noti infine che nel senso più ampio del termine
i servizi Web oggi così di moda non sono altro
che interfacce utente, essendo il loro fine precisamente
quello di fornire un punto d'accesso e di interazione
con un patrimonio informativo a prescindere dalle sue
caratteristiche fisiche; la loro particolarità
è quella di non essere rivolti ad utenti reali,
ma ad utenti automatizzati, vale a dire a procedure
o oggetti remoti. Un servizio Web, insomma, è
un'interfaccia utente per computer.
L'implementazione
del sistema informativo
Esaminate sommariamente le componenti fondamentali del
nostro sistema, passiamo a considerarne l'implementazione
pratica.
Un'implementazione
completa del sistema informativo è, ovviamente,
al di là dei nostri interessi; per i nostri fini
viene fornita una semplice implementazione costituita
da una classe singleton InformationSystem, che rappresenta
la visione unitaria del mondo back-end di NetView ed
implementa il pattern Business Delegate. Tale implementazione
non comunica con componenti esterni, ma utilizza le
classi Collection di Java per implementare meccanismi
minimi di memorizzazione e simula la comunicazione con
componenti esterni di controllo tramite la generazione
di valori casuali.
Volendo paragonare l'intero sistema informativo ad un
iceberg, la classe InformationSystem ne rappresenta
la punta, e ne costituisce l'unica parte visibile: per
tutti i rimanenti componenti di NetView l'unico punto
di interazione con il sistema informativo è la
classe InformationSystem.
In un'applicazione reale tale classe verrebbe probabilmente
implementata come una somma di strati EJB, facenti capo
ad un Session EJB stateless secondo il Pattern Session
Facade, il quale avrebbe la responsabilità di
organizzare ed armonizzare i dati provenienti dai vari
database, sistemi di controllo remoto e dispositivi
di rete. Alla luce delle recenti evoluzioni del protocollo
EJB, la comunicazione con sistemi esterni di controllo
potrebbe venire realizzata tramite Message-Driven EJB
- ad esempio basati su JMX - le cui caratteristiche
di asincronicità risulterebbero ideali in questo
contesto.
Sono ovviamente ipotizzabili implementazioni anche assai
diverse da quanto abbozzato,e la sempre crescente disponibilità
di protocolli alternativi, realmente object-oriented
e meno penalizzanti dal punto di vista delle performance,
permette - e secondo alcuni consiglia - soluzioni diverse
e non basate su EJB; si rimandano i lettori interessati
a tali argomenti alla vasta letteratura esistente in
materia, così come agli articoli incentrati su
tematiche di Software Design e Pattern in corso di pubblicazione
presso MokaByte.
Il
nostro sistema informativo è basato su due tabelle:
la tabella Nodi, che contiene sostanzialmente l'inventario
dei dispositivi di ACME che dobbiamo monitorare, e la
tabella Categorie, che contiene l'elenco delle classi
di dispositivi gestibili (Router, Application Server,
etc.). Tali tabelle possiedono chiavi primarie elementari
e sono collegate tra di loro tramite una relazione uno
a molti (Categorie -> Nodi).
L'implementazione
dello strato di business logic
In applicazioni di produzione il nostro business tier
potrebbe venire implementato tramite una pluralità
di oggetti remoti - quali Entity EJB confinati al server
layer dell'applicazione o oggetti JDO, anche in considerazione
delle caratteristiche delle diverse basi dati - fisicamente
distribuiti in rete tramite CORBA, RMI o framework innovativi
come JavaSpaces; tale pluralità di componenti
risulterebbe accessibile all'interfaccia utente tramite
uno o più oggetti proxy basati sui pattern Business
Delegate e Data Access Object.
Nell'implementazione
d'esempio lo strato di Business Logic è fuso
all'interno della classe InformationSystem, ed esporta
all'interfaccia utente i contenuti del 'database' attraverso
oggetti facenti capo ad apposite classi lightweight.
Tali oggetti - usualmente denominati Value Object -
sono per definizione 'plain object', vale a dire poco
più che semplici contenitori di dati funzionalmente
elementari, costruiti in maniera tale da ridurre il
traffico di rete e da non generare accessi distribuiti
per l'accesso alle loro proprietà, ed in implementazioni
reali potrebbero essere classi 'specchio' di oggetti
Entity EJB.
La
versione proposta di NetView conosce due classi di questo
genere, CategoryRow e NodeRow, i cui oggetti corrispondono
rispettivamente alle righe delle tabelle Categorie e
Nodi.
Alla classe NodeRow spetta anche il compito di integrare
informazioni non ottenute dal nostro ipotetico database:
a questo fine, per simulare una lettura da sistemi remoti
di controllo, le colonne 'Carico attuale' e 'Data ed
ora di ultima rilevazione del carico' della tabella
Nodi vengono rigenerate ad ogni accesso.
Si noti che la classe InformationSystem, nella sua veste
di strato di business logic, funge da Business Delegate
verso i metodi generici di accesso ai dati contenuti
nelle classi Row, ricoprendo così nei confronti
degli altri componenti NetView il ruolo di punto unico
di accesso ai dati.
L'implementazione
dello strato di interfaccia utente
L'analisi del layer di presentation tier completa la
sintesi strutturale dell'implementazione di NetView
che, seppur applicazione giocattolo, deve per evidenti
motivi didattici presentare un'impostazione ed un'architettura
quanto più realistiche possibili.
A livello di presentation tier si pone il problema della
separazione tra forma e contenuti, la cui soluzione
ideale consiste in un decoupling formale e rigoroso.
La struttura implementata rispetta questi requisiti
e separa chiaramente le funzionalità di presentazione
dei dati da quelle della loro preventiva elaborazione
e della comunicazione con gli strati superiori del sistema,
ed è basata su un'architettura integrale MVC
e sui pattern Front Controller e Service to Worker.
Un nodo fondamentale di ogni applicazione distribuita,
infine, è costituito dai servizi di autenticazione
/ autorizzazione e tracing; nel contesto del nostro
esempio, tuttavia, tali aree funzionali non rivestono
per noi alcun interesse, e non essendo peraltro legate
alla comprensione dei meccanismi e delle dinamiche di
funzionamento ed integrazione di JSP e JSTL non verranno
prese in considerazione dall'implementazione di NetView.
Il
Front Controller
Il punto d'accesso unico all'applicazione è costituito
dal servlet FrontControllerServlet, per cui viene impostato
nel file web.xml l'alias 'main' così da permettere
l'accesso alla nostra applicazione tramite un'URL del
tipo 'http://<server>/netview/main'.
La responsabilità di tale servlet consiste unicamente
nel ricevere dal browser la richiesta di una determinata
azione da parte dell'utente ('visualizza un elenco di
nodi', 'modifica un nodo', etc.) e nel forwardare tale
richiesta ad un'appropriata classe di gestione Java,
genericamente indicata come Dispatcher: il Front Controller
non possiede alcuna conoscenza semantica in merito alle
azioni dell'interfaccia utente, e si limita semplicemente
a smistare le richieste ricevute alla classe pertinente.
Tale classe ritorna l'URL di un componente dinamico
- denominato View e nel nostro caso implementato tramite
pagine JSP - il quale implementa la vera e propria 'interfaccia
utente' e che verrà attivato dal Front Controller.
Spetta al Front Controller anche il compito di gestire
situazioni di errore a livello globale di applicazione
e di impostare nel contesto di esecuzione delle View
eventuali valori globali, nel nostro caso un riferimento
al sistema informativo.
Nell'implementazione fornita l'associazione tra classi
Dispatcher ed azioni viene impostata in maniera statica
all'interno del codice di FrontControllerServlet; in
un'applicazione reale tali dati verrebbero piuttosto
desunti da un repository esterno - quale un servizio
JNDI - presso il quale ogni singolo Dispatcher si potrebbe
registrare all'avvio.
I
Dispatcher
Per ogni azione richiesta dall'utente viene invocata
un'apposita classe di gestione, denominata Dispatcher.
La responsabilità di tale classe consiste nell'analizzare
il contesto di esecuzione e, alla luce dei parametri
ricevuti e delle proprie conoscenze semantiche, nell'impostare
il contesto di pagina per l'attivazione della View successiva.
Questo passo comprende l'istanziazione o l'acquisizione
di dati e riferimenti ad oggetti in grado di gestire
sezioni complesse del processo di presentazione ed elaborazione
dei dati. Tali oggetti vengono denominati View Helper,
e vengono resi disponibili alla View in maniera da permetterle
di delegare ogni attività non elementare a componenti
esterni.
L'obiettivo è semplificare al massimo il contenuto
delle View, che dovrebbero idealmente consistere unicamente
in presentazione di dati ed in interazioni algoritmicamente
elementari con componenti esterni di gestione.
Così facendo si riduce al minimo l'accoppiamento
tra presentation e business layer, aumentando la flessibilità,
l'estensibilità e la scalabilità del sistema,
abbattendone al contempo i costi di manutenzione e gestione.
Inoltre questa pratica conduce a pagine JSP effettivamente
prive di codice Java - nell'intera implementazione di
NetView non è presente nessuno scriptlet JSP
- che gli Web Designer possono progettare, realizzare
ed estendere in piena autonomia, a tutto vantaggio dell'economia
dello sviluppo collaborativo del software.
Nell'implementazione d'esempio di NetView vengono utilizzati
come oggetti View Helper i Value Object CategoryRow
e NodeRow, oltre allo stesso oggetto singleton InformationSystem.
Infine il Dispatcher può comunicare liberamente
con l'InformationSystem attraverso l'handle fornitogli
dal FrontController a cui, in caso di errori che non
è in grado di correggere o gestire, può
propagare eventuali eccezioni.
Gli
oggetti View
La presentazione dell'applicazione è responsabilità
degli oggetti View, le cui caratteristiche tecniche
dipendono fortemente dall'architettura della soluzione
distribuita: una finestra in Swing, per intenderci,
è un oggetto View tanto quanto una pagina HTML.
Nel nostro caso gli oggetti View sono pagine JSP, le
quali realizzano l'interfaccia utente sulla base del
contesto impostato dal Dispatcher, sfruttano gli algoritmi
di gestione dei dati contenuti negli oggetti View Helper
per elaborare e presentare le informazioni tramite JSTL
ed HTML e dialogano con il resto del sistema NetView
per mezzo del Front Controller, interagendo con il quale
ottengono l'effetto di 'far girare' l'applicazione.
Si noti che le cardinalità di View, Dispatcher
ed azioni non devono necessariamente coincidere: un
unico Dispatcher, o un'unica View, possono essere responsabili
per diverse azioni.
Analisi
dell'interfaccia utente
Dal momento che la nostra attenzione verte essenzialmente
sull'interfaccia utente, ha senso analizzare NetView
a partire dalle diverse azioni cui esso è in
grado di rispondere.
La
prima azione che considereremo è front: essa
è anche l'azione di default, vale a dire l'azione
automaticamente eseguita se l'utente non ha richiesto
alcuna azione particolare tramite il parametro action.
Tale azione fa capo alla View front.jsp, la cui prima
riga - come per tutte le View - dichiara l'utilizzo
della libreria JSTL core:
"<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"
%>".
Il compito dell'azione front è presentare la
home page della nostra applicazione, che visualizza
un messaggio di benvenuto unitamente alla data ed ora
attuali ed alla stringa di identificazione del browser
dell'utente.
La prima informazione viene ottenuta mediante una semplice
JSP action "<jsp:useBean>" tramite la
quale viene istanziato un oggetto java.util.Date, rigenerato
ad ogni accesso alla pagina e visualizzato tramite un
tag JSTL <c:out>. Come ci si può attendere
,la visualizzazione di un oggetto Java passa per la
sua rappresentazione sotto forma di String, ottenuta
automaticamente da JSTL invocando il relativo metodo
toString().
L'identificazione del browser è ancora più
semplice, e consiste semplicemente nella visualizzazione
della proprietà user-agent dell'oggetto header
- uno degli oggetti impliciti presentati nel precedente
articolo - corrispondente al request header della richiesta
HTTP corrente. Trattandosi di un oggetto implicito non
è ovviamente richiesta la sua dichiarazione tramite
"<jsp:useBean>".
La home page - come ogni pagina di NetView - include
le due risorse esterne topbar.jsp e bottombar.jsp.
La struttura delle pagine di NetView è in effetti
composta di tre sezioni: una barra superiore, un frame
di contenuti ed una bottom bar. Questa struttura, con
diverse variazioni, è una costante di gran parte
delle Web Application. Le due barre hanno il compito
di realizzare un look & feel standard che dia un
senso di continuità tra le diverse pagine prodotte
dall'applicazione.
Il frame di contenuti rappresenta la parte dell'interfaccia
che effettivamente varia durante la navigazione all'interno
dell'applicazione. Nella nostra implementazione ogni
View (ossia ogni pagina JSP) si preoccupa unicamente
del disegno di tale frame, e si limita ad utilizzare
le due risorse suddette per le altre sezioni.
La barra superiore ha inoltre la funzione di menu principale,
e consente all'utente di accedere alle funzionalità
di visualizzazione dei nodi (sia globale che per categoria).
La selezione della categoria di visualizzazione dei
nodi avviene tramite un'interazione JavaScript / JSTL
realizzata dal seguente codice:
<form
method="post" name="nodesbyidForm"
action="/netview/main">
<input type="hidden" name="action"
value="nodesbycatid" />
<select name="catId" onChange="document.nodesbyidForm.submit();"
>
<c:forEach var="category" items="{informationSystem.allCategories}">
<option value="<c:out value='{category.id}'/>">
<c:out value="{category.desc}" />
</option>
</c:forEach>
</select>
</form>
La
comprensione di questo codice è la chiave per
l'utilizzo corretto di JSTL: sorgenti di NetView alla
mano, i punti salienti di queste righe sono:
Tag <select>: Apriamo una listbox, che in seguito
alla selezione di un elemento causerà automaticamente
il post del form, portando all'esecuzione dell'azione
nodesbycatid (visualizza nodi per categoria);
tag <c:forEach>: iteriamo sulla proprietà
allCategories di informationSystem, un riferimento al
nostro sistema informativo che viene reso disponibile
ad ogni View; il suo metodo getAllCategories() ritorna
l'elenco di tutte le categorie disponibili sotto forma
di Collection di oggetti CategoryRow; la variabile category,
dichiarata nell'attributo var del tag <c:forEach>,
sarà il nostro iterator su tale Collection;
a questo punto per ogni elemento della suddetta Collection,
vale a dire per ogni CategoryRow, verrà eseguito
il codice sino alla chiusura del tag <c:forEach>;
per ogni CategoryRow viene generato un tag <option>,
il cui attributo value è pari al valore visualizzato
dal tag <c:out value='{category.id}' />, vale
a dire al codice della categoria (proprietà getId()
di CategoryRow). L'innesto di tag JSTL ed HTML è
perfettamento legittimo e completamente trasparente
al client: visualizzando il sorgente HTML all'interno
del browser si noterà che i tag JSTL sono stati
sostituiti dal loro valore. Nei sorgenti riportati,
per chiarezza, in ogni innesto JSTL / HTML si è
provveduto ad utilizzare caratteri terminatori di stringa
differenti (" e ') anche se ciò non è
assolutamente richiesto da JSP. In effetti, queste due
righe avrebbero prodotto esattamente lo stesso risultato:
<option
value="<c:out value='{category.id}'/>">
<option value="<c:out value="{category.id}"/>">
il
valore visualizzato in ogni <option> all'interno
della list box è {category.desc}, vale a dire
la descrizione della categoria (proprietà getDesc()
di CategoryRow);
si noti che né l'oggetto informationSystem né
l'oggetto category vengono dichiarati o definiti in
azioni JSP "<jsp:useBean>"; nel primo
caso, infatti, ci limitiamo ad utilizzare il meccanismo
di ricerca automatica di JSTL, già esaminato
nel precedente articolo, che raggiunge automaticamente
gli oggetti nei contesti di pagina, richiesta HTTP,
sessione ed applicazione. Nel secondo caso è
invece sufficiente la definizione di tale elemento come
Iterator nell'attributo var del tag <c:forEach>.
La
top bar e la bottom bar, il cui contenuto è tendenzialmente
statico, vengono incluse da ogni View con delle direttive
"<%@ include %>".
Selezionando
'Tutti i nodi' dal menu principale viene eseguita l'azione
allnodes: tale azione, attraverso il Dispatcher AllNodesDispatcher,
porta all'attivazione della view nodes.jsp.
Selezionando invece dal menu principale una categoria
viene eseguita l'azione nodesbycatid, la quale attraverso
il Dispatcher NodesByCatIdDispatcher ci conduce alla
medesima View nodes.jsp.
Tale View è in grado di discriminare sulla presentazione
che le viene richiesta in base al valore del parametro
action, che viene reso disponibile ad ogni View.
In ambedue i casi la nostra View creerà una tabella
HTML per visualizzare i dati della Collection nodes,
che viene inserita nel contesto della richiesta di pagina
dai Dispatcher. Il Dispatcher AllNodesDispatcher inserisce
in tale attributo l'elenco di tutti i nodi disponibili
(AllNodesDispatcher.java:19-23) mentre il Dispatcher
NodesByCatIdDispatcher vi inserisce i soli nodi appartenenti
alla categoria richiesta:
//
Recuperiamo la categoria che funge da criterio
int catId=Integer.parseInt(request.getParameter("catId"));
//
Ed otteniamo i nodi corrispondenti
Collection nodes=informationSystem.getAllNodesByCategoryId(catId);
//
Inseriamo i nodi nel contesto della richiesta
request.setAttribute("nodes",nodes);
//
Rendiamo disponibile la categoria selezionata alla View
request.setAttribute("category",
informationSystem.getCategoryById(catId));
Il
codice presentato non fa altro che ricuperare il parametro
catId (confrontare le righe esaminate in precedenza,
topbar.jsp:25-38) ed utilizzarlo per recuperare l'elenco
dei nodi richiesti, che vengono inseriti nel contesto
della View come attributo di nome "nodes".
Nel
caso di azione nodesbycatid viene anche passato alla
View un oggetto CategoryRow corrispondente alla categoria
richiesta, in modo che la pagina JSP possa riproporre
all'utente il criterio da lui impostato.
A
titolo di esempio, consideriamo come la View nodes.jsp
utilizza il valore del parametro action per impostare
correttamente il titolo HTML della pagina:
<title>NetView
-
<c:choose>
<c:when test="{action=='allnodes'}">
Tutti i nodi
</c:when>
<c:when test="{action=='nodesbycatid'}">
Nodi per categoria
</c:when>
</c:choose>
</title>
La
leggibilità di JSTL è sicuramente uno
dei suoi maggiori pregi, anche a costo di una certa
ridondanza.
L'esperienza nell'utilizzo di questa tag library insegna
che, proprio per via di questa prolissità, l'espressione
di algoritmi non elementari in JSTL è estremamente
scomoda. Questa caratteristica è paradossalmente
da intendersi come assolutamente positiva, dal momento
che conduce in maniera naturale all'estrapolazione di
tali algoritmi in oggetti Java esterni di tipo View
Helper, e quindi all'implementazione di soluzioni distribuite
corrette, flessibili e manutenibili.
La
produzione dell'elenco dei nodi in forma tabellare è
estremamente semplice, e consiste nell'iterazione sulla
collezione di oggetti NodeRow e nella visualizzazione
delle proprietà di ogni nodo all'interno di corrispondenti
elementi <td>:
<c:forEach
var="node" items="{nodes}">
<tr>
<td><a href="/netview/main?action=nodeform&subaction=edit&id=<c:out
value='{node.id}' />" title="Modifica"><img
src="Open24.gif" border="0" alt="Apri"
/></a\>
</td>
<td><a href="javascript:deleteNode(<c:out
value='{node.id}' />);" title="Cancella">
<img src="Remove24.gif" alt="Cancella"
border="0" /></a\>
</td>
<td><c:out value="{node.id}"/></td>
<td><c:out value="{node.desc}"/></td>
<td><c:out value="{node.catDesc}"/></td>
<td><c:out value="{node.state}"/></td>
<td><c:out value="{node.load}"/></td>
<td><c:out value="{node.lstRdLoad}"/></td>
<td><c:out value="{node.tdsLoad}"/></td>
<td><c:out value="{node.building}/{node.floor}/{node.room}"/></td>
</tr>
</c:forEach>
I
punti interessanti del codice presentato sono i due
tag <a>, in cui si utilizza ancora la tecnica
di innesto JSTL / HTML per creare rispettivamente un
link dinamico ed un'invocazione ad una funzione JavaScript.
Esaminiamo
ora l'azione nodeform: essa viene invocata dalla View
nodes.jsp per visualizzare i dati di un singolo nodo
all'interno di un form per permetterne l'edit (subaction
edit con parametro id pari al codice del nodo) ovvero
per effettuare l'inserimento di un nuovo nodo (subaction
add).
Tale azione porta all'esecuzione del Dispatcher NodeFormDispatcher,
il quale ha il compito di rendere disponibili alla View
i dati da precaricare nel form, vale a dire - in caso
di modifica - i dati del nodo selezionato, ed in caso
di inserimento il progressivo automatico del codice
del nodo.
Per far questo NodeFormDispatcher utilizza il parametro
id per risalire ai valori del nodo, che inserisce uno
ad uno nel contesto di pagina:
NodeRow
nr=informationSystem.getNodeById(Integer.parseInt(
request.getParameter("id")));
request.setAttribute("id",Integer.toString(nr.getId()));
request.setAttribute("desc",nr.getDesc());
Tali attributi verranno proposti come valori di default
nel form di edit dalla View nodeform.jsp, ancora una
volta innestando JSTL in HTML:
<input
type="text" name="desc" value="<c:out
value='{desc}' />" size="50" />
Tale
View utilizza il meccanismo già visto in topbar.jsp
per produrre la listbox delle categorie, questa volta
arricchito di un <c:if> per selezionare automaticamente
l'elemento della listbox corrispondente alla categoria
del nodo corrente.
Un <c:if> viene utilizzato anche per tradurre
lo stato del nodo (un carattere tra 'C', 'T' e 'P')
in un radio button di facile utilizzo.
Si
noti che alla tecnica di innesto di JSTL / HTML si affiancherà,
nelle specifiche di JSP 2.0, una soluzione alternativa
chiamata 'implicit scripting', la quale permette di
utilizzare espressioni in linguaggio EL liberamente
all'interno delle pagine Web. La linea HTML precedente
potrebbe quindi venire così riscritta:
<input
type="text" name="desc" value="{desc}"
size="50" />
I
vantaggi dell'implicit scripting, in termini di leggibilità,
sono piuttosto ovvi, ma vengono bilanciati da possibili
rischi di incompatibilità con pagine HTML che
già utilizzano le sequenze di caratteri '{' e
'}'.
Tale tecnologia è in effetti già disponibile
in alcuni Application Server - quali il Tomcat 5 - ma
non è stata utilizzata nell'implementazione di
NetView per garantire una maggiore compatibilità
della nostra Web Application con i diversi ambienti
J2EE Server attualmente disponibili.
In
seguito alla conferma del form viene eseguita l'azione
nodepost. A tale azione, cui si può arrivare
anche direttamente dalla View nodes.jsp confermando
la cancellazione di un nodo, spetta la responsabilità
di riportare nella base dati l'operazione di inserimento,
modifica o cancellazione di nodi effettuata dall'utente.
Il Dispatcher NodePostDispatcher ricupera in caso di
inserimento o modifica tutti i dati del nodo inseriti
dall'utente del form, e li utilizza per riempire una
nuova istanza di NodeRow, di cui poi richiede la persistenza
ad InformationSystem:
NodeRow
nr=new NodeRow();
nr.setId(Integer.parseInt(request.getParameter("id")));
nr.setDesc(request.getParameter("desc"));
.
.
.
if ("add".equals(subaction))
informationSystem.addNode(nr);
.
L'esito
della richiesta di persistenza viene tradotto in un
assegnamento all'attributo di richiesta HTTP "result",
che viene utilizzato dalla View nodepost.jsp per fornire
all'utente un opportuno messaggio di conferma o di errore.
Nel caso di subaction delete l'unico parametro da estrarre
dalla richiesta è il codice del nodo, la cui
richiesta di cancellazione viene quindi inviata ad InformationSystem.
L'ultima
azione da esaminare è error: tale azione viene
automaticamente invocata dal Front Controller in presenza
di situazioni non risolvibili, e provoca la visualizzazione
di una semplice pagina di errore.
La
quadratura del cerchio
L'implementazione presentata non può che lasciarci
soddisfatti: NetView, nella sua semplicità, è
basato su di un'architettura distribuita formalmente
corretta, organizzata nel rispetto delle best practice
e dei pattern più aggiornati, e potenzialmente
dotata di quei requisiti di estensibilità, scalabilità
e performance che costituiscono la cifra dello sviluppo
distribuito.
D'altronde
- come in ogni sistema complesso - è nei punti
di giuntura dei componenti che si nascondono i problemi.
Nel caso delle interfacce utente, in particolare, sono
perfettamente ammissibili diverse interpretazioni, spesso
squisitamente 'filosofiche' del rapporto tra View, Dispatcher
e Helper: la logica di NetView, che abbiamo cercato
di partizionare tra queste tre grandezze con la massima
oculatezza, avrebbe potuto venire implementata interamente
in ognuna di esse, senza il bisogno ricorrere ad altre
componenti.
Sarebbe in particolare stato perfettamente legittimo
rendere i Dispatcher poco più di 'gusci vuoti',
spostando tutta la logica di gestione - si pensi in
particolare alle azioni effettuate dai Dispatcher NodeFormDispatcher
e NodePostDispatcher - a livello di Helper e di View
JSP.
O, addirittura, l'intera applicazione avrebbe potuto
essere codificata in JSP e JSTL, senza alcun ricorso
a strutture esterne. Per quale motivo, ad esempio, non
può essere direttamente la View, in base alle
azioni dell'utente, a caricare i dati dei nodi e popolare
il form di edit, ovvero a comunicare le variazioni dell'utente
al sistema informativo ?
Nonostante l'architettura presentata in questo articolo
sia teoricamente corretta, è possibile individuare
un numero di approcci alternativi e non meno corretti.
E sono, ovviamente, immaginabili anche implementazioni
di NetView decisamente più sbilanciate verso
l'interfaccia utente, in cui potrebbero mescolarsi presentation
e business tier.
La scelta tra queste possibilità rappresenta
la 'quadratura del cerchio', in cui oltre alle inevitabili
considerazioni teoriche è sempre più necessario
far posto a riflessioni pratiche: il bisogno di produrre
applicazioni distribuite a ritmi sempre più rapidi
non può che portare alla semplificazione delle
architetture, ed in particolare allo sbilanciamento
verso lo strato di interfaccia utente, il quale - in
particolare nelle applicazioni di livello medio-basso
- rappresenta il punto di partenza e di arrivo nel design
delle soluzioni software, ed il metro di misura e di
paragone della qualità dell'applicativo.
La crescente importanza delle interfacce utente si riflette
sia nella struttura di framework come .NET sia nelle
direttive di sviluppo del mondo Java, caratterizzato
da un pullulare di strati di presentazione open-source
e commerciali, uno dei quali - Java Server Faces - porremo
presto al centro della nostra attenzione.
Conclusioni
In questo articolo abbiamo introdotto e sommariamento
descritto l'architettura di NetView, una semplice applicazione
distribuita corporate, passando quindi all'analisi dell'implementazione
del suo presentation tier, realizzato attraverso un
Web Client JSP basato su JSTL Core.
Nel
prossimo articolo svilupperemo ulteriormente il nostro
Web Client arricchendolo di funzionalità basate
sugli altri moduli di JSTL: XML Processing, Internationalization
Capable Formatting e Relational DB Access.
Bibliografia
[1] JSP - JavaServer Pages: http://java.sun.com/products/jsp
[2] JSTL - JSP Standard Tag Library: http://java.sun.com/products/jsp/jstl
[3] JSF - Java Server Faces: http://java.sun.com/j2ee/javaserverfaces
[4] SWT / Eclipse: http://www.eclipse.org
[5] Droplets: http://www.droplets.com
Risorse
Scarica
qui il codice presentato nell'articolo.
(link a <<netview-1.zip>>)
L'implementazione di esempio di NetView è stata
testata sotto JBoss 3.0.3 e Tomcat 5 (Milestone 5.0.0)
in ambiente Linux.
Lavinio
Cerquetti si occupa di design e sviluppo del software
in ambienti distribuiti ed in architetture J2EE multi-tier.
Può essere contattato all'indirizzo di e-mail
lcerquetti@mokabyte.it
|