Introduzione
Dopo aver visto nelle puntate precedenti i temi legati al project management, alla progettazione delle entità basilari, alla definizione dello strato di persistenza, è ora di fare il punto della situazione per capire in che modo le varie parti del progetto possono essere messe insieme per realizzare l'applicazione. In pratica è giunto il momento di parlare di architettura del sistema, cercando di capire come e perché sia necessario trattare il modello architetturale.
Complessità tecnologica e architettura
L'architetto deve sempre tenere ben presente un altro aspetto quando realizza l'architettura del progetto: il livello di complessità tecnologica che si vuole adottare nel sistema.
Spesso si è portati a pensare che soluzioni semplici o rudimentali possano rivelarsi inadatte o insufficientei per rispondere a esigenze di un evoluto scenario enterprise: sovente la continua ricerca di soluzioni sofisticate e tecnicamente avanzate viene visto come un obiettivo onorevole e necessario.
Maggior complessità tecnologica o sofisticazione, se da un lato possono permettere di rispondere meglio alle complesse necessità di uno scenario enterprise moderno (performance, scalabilità, modularità, multicanalità, etc...), dall'altro portano ad avere prodotti che normalmente hanno un elevato costo di manutenzione e risultano difficili da far evolvere o "aggiustare".
Inoltre, un modello sofisticato porta a un prodotto che può essere compreso solo da poche persone (in genere i più bravi ed esperti), se non adeguatamente documentato; ma scrivere una buona documentazione è un processo quanto mai costoso. Componenti non documentati diventano patrimonio esclusivo di una sola risorsa; se la risorsa "brava" deve anche spendere del tempo per preparare una documentazione più corposa del normale, probabilmente il costo finale non sarà così vantaggioso. Ci si dimentica quindi che anche le persone possono essere considerate risorse chiave nella definizione della architettura. Se avere personale molto esperto è indubbiamente un vantaggio, è vero che sviluppatori "troppo" esperti possono scrivere codice non manutenibile da altri.
L'architettura deve essere quindi il più possibile semplice, compatibilmente con i requisiti funzionali e non funzionali imposti dal progetto e dal PID di progetto, come ampiamente detto nel primo articolo della serie dedicato al project management. L'architettura deve rifarsi a modelli e casistiche frequentemente adottate in altri progetti e deve essere il più possibile in linea con le "mode" del momento (in modo che si possa trovare in rete molta documentazione), appoggiandosi a standard di dominio pubblico rispetto a soluzioni proprietarie.
L'uso dei pattern abbinato ai classici modelli architetturali è la strategia vincente che ogni progettista dovrebbe sempre adottare. Se si propone una modello a tre layer con disaccoppiamento basato sul binomio Façade-Delegate e in cui il presentation layer utilizza il pattern MVC, forse non avremo prodotto un modello che eccelle per originalità, ma probabilmente anche uno studente del primo anno di corso sarà in grado di comprenderlo (e magari di completarlo se siamo a corto di personale).
Preassemblaggio, templating, prototyping
Migliorare il flusso di informazioni all'interno del gruppo di lavoro in modo da ridurre al minimo il diffondersi di concetti errati o male interpretati è uno degli obiettivi primari di ogni capo progetto. Il guadagno si ottiene sia semplificando l'architettura che provvedendo a far crescere il gruppo delle persone ad esempio migliorandone la preparazione di base.
Ogni alternativa che si presenta nel corso dello sviluppo è una possibile fonte di errore: introdurre strumenti che "limitano" la libertà di scelta è sicuramente un buon modo per limitare l'entropia del sistema: strumenti come il wiki si stanno diffondendo all'interno dei progetti non solo come tool per la condivisione di informazioni, ma anche come repository di librerie di esempi (snippet di codice). Un prototipo ben realizzato e condiviso sotto forma di mini schemi, template, parti di architettura, porzioni di progetto "preassemblate" evita ogni dubbio su come debbano essere realizzate determinate parti.
Il modus operandi è in questo caso quello del copia e incolla: rendere pubbliche le parti di codice che verranno poi usate più e più volte, permette di limitare fortemente l'"estrosità" del singolo (facilitando il propagarsi di un unico modo di implementare le cose), portando a un'omogeneizzazione della applicazione in tutte le sue parti.
Purtroppo, però, questo approccio apre a possibili ripercussioni negative: un pezzo di codice con un errore (che è comunque facile da scoprire) o una superficiale interpretazione di un problema con conseguente implementazione troppo vaga (molto più difficile da individuare o anche comprendere), possono dar vita a errori concettuali o funzionali che si propagano a macchia d'olio per tutto il progetto. Sbagliare tutti allo stesso modo è comunque un risultato ragguardevole: a parte la facile ironia, un errore comune, pur grave, replicato in molti punti è più facile da individuare e rifattorizzare che non una una pletora di differenti cattive interpretazioni.
Il pattern: mattone fondante della architettura
La nozione di pattern proviene dall'architettura: "Un pattern rappresenta una soluzione testata e ottimizzata a un problema ricorrente in diversi contesti" (Cristopher Alexander)
Proseguendo la metafora edilizia, potremmo dire che costruire un palazzo con mattoni standard e con componenti strutturali ben noti aiuta il lavoro del carpentiere in fase di costruzione e dell'artigiano nelle successive rielaborazioni e ristrutturazioni. Per questo motivo, la realizzazione di una buona architettura non può prescindere dalla adozione di pattern come elementi fondanti.
I pattern sono in genere classificati in vario modo a seconda dello scopo che si prefiggono e la nozione stessa di pattern è trasversale rispetto alla dimensione del problema:
- GRASP (implementazione, singole classi)
- GoF (design e architettura, gruppi di classi)
- J2EE Patterns (specifici dell'architettura SUN)
Normalmente tutti hanno un set di fili conduttori comuni:
- High Coesion / Low Coupling (alta coesione con basso accoppiamento)
- Separazione tra interfaccia ed implementazione
- Incapsulamento
- KISS (Keep It Simple, Stupid)
La parte restante di questo articolo (e parte del prossimo della serie) riporterà una breve panoramica su alcuni dei pattern più utili durante la fase di definizione della architettura della applicazione; lo scopo non è tanto quello di offrire l'ennesimo compendio sui pattern, per i quali è possibile trovare moltissimo materiale in rete (e su MokaByte se ne è parlato a lungo e in maniera molto dettagliata), ma piuttosto cercare di rivederli considerandoli come i vari "materiali da costruzioni" della architettura finale.
Il pattern Session Façade
Il pattern viene usualmente presentato come il modo migliore per fornire un punto di accesso univoco e semplice a una struttura complessa. Tale struttura può essere a volte oggetto di un processo di continua rivisitazione, processo che finisce per diventare la principale preoccupazione del progettista: la architettura può evolversi nel tempo, e l'implementazione può non essere coerente. Il costo di apprendimento della struttura complessa da parte di un utilizzatore può essere considerevole.
Si immagini ad esempio di avere un sistema come quello raffigurato nella figura 2, in cui la parte "core" sia organizzata in modo più o meno complesso: in assenza di un intermediario fra chiamante e componenti del sistema, si crea un accoppiamento stretto che impone ai client di conoscere struttura interna, interdipendenze e di mantenere un riferimento diretto con ognuno di tali componenti.
Questo design, porta a un sistema fortemente coeso, e di conseguenza con un bassissimo livello di disaccoppiamento: il costo di manutenzione in genere esplode esponenzialmente quando la complessità generale aumenta (anche non di poco). Per ogni variazione di uno dei componenti della architettura è necessario infatti modificare ogni cliente invocante.
Centralizzando il punto di accesso con un nodo centrale (il Façade appunto) si limita l'interdipendenza fra l'utilizzatore e il fornitore di una funzionalità/servizio. Chi accede ai servizi esposti ha conoscenza solo della classe Façade e delle classi usate come argomenti o tipi restituiti (pattern DTO che affronteremo in seguito) e delle eventuali eccezioni.
Una volta definito il punto di accesso centralizzato al sottosistema si deve verificare/proibire che non vengano effettuati accessi che scavalchino la Façade, ad esempio lavorando sulla visibilità delle classi del sottosistema (p.e.: visibilità package) oppure usando opportuni tool di verifica di regole architetturali (p.e.: Macker, Checkstyle, etc.).
Una possibile implementazione degenere del Façade è il blob, ossia "la-classe-che-fa-tutto" che porta a codice non leggibile e scarsamente gestibile (accessi concorrenti, problemi di merge o di lock sul sistema di versioning).
Il Façade rappresenta un punto di demarcazione anche per le eccezioni che trasporta: può essere utilizzato per mascherare o gestire le eccezioni di tipo tecnologico tramite il meccanismo del rewrapping [REW] in funzione del layer logico. Per esempio, un errore a livello dati non ha alcuna necessità di essere propagato allo strato di presentation sotto forma di SQLException: in questo caso una eccezione applicativa DataAccessException potrà essere certamente più utile ed efficace.
Il Façade è quindi un ottimo candidato a implementare una "politica di gestione" per comportamenti comuni a tutta l'applicazione: si pensi al log dell'applicazione o alla possibilità di verificare le performance a run-time del sistema, alla possibilità di introdurre meccanismi di autenticazione e autorizzazione sugli accessi.
Il Façade ha benefici influssi anche per quello che concerne lo sviluppo della applicazione nel suo complesso, dato che consente di ridurre il set di conoscenze necessaria a sviluppare determinate funzionalità. Questo significa che è possibile avere una evoluzione asimmetrica delle varie parti del sistema: una volta definito il punto di accesso, posso procedere a sviluppare front layer e business layer in modo indipendente. Si ha quindi un innegabile beneficio in termini di disaccoppiamento dei sottosistemi. Per lo stesso motivo è possibile differire determinate scelte implementative a una fase successiva a quella di prima stesura, favorendo la modularità complessiva.
Il disaccoppiamento delle operazioni di implementazione può essere estremizzato tanto da consentire di lavorare anche con versioni provvisorie ed incomplete: in questo caso il pattern è realmente efficace solo se è stata definita correttamente l'interfaccia in termini di semantica delle operazioni, delle classi usate come argomento e come risultato e se le eccezioni restituite sono ben progettate.
Il pattern Delegate
Il pattern Façade, come mostrato efficacemente nelle figure 2 e 3, nasconde il reticolo delle chiamate interne nel sistema. Rimane completamente da risolvere il problema relativo alla gestione della complessità infrastrutturale derivante dal processo di reperimento degli oggetti (remoti oppure no) che espongono la logica sotto forma di servizi. Il Façade in pratica non dice come il client possa reperire il Façade stesso.
Sia che si tratti di un oggetto remoto RMI, di un session bean EJB, oppure di un web service, la complessità infrastrutturale rimane a carico dell'invocante: nascondere tale complessità è tipicamente un compito che viene brillantemente risolto dal pattern Business Delegate, il quale si occupa di disaccoppiare l'accesso ai servizi di business dai client, nascondendo e centralizzando le operazioni di localizzazione e utilizzo del servizio di business: lo scopo di questo pattern è quindi quello di inoltrare richieste a oggetti (in genere risiedenti su layer differenti da quello dell'invocante) senza che vi sia conoscenza reciproca tra l'esecutore e il richiedente.
Questo pattern fornisce un'interfaccia uniforme ai client, nascondendo e centralizzando le problematiche di reperimento (lookup + create) e utilizzo dei componenti di business. Da un punto di vista logico, il pattern delegate ha un'importante conseguenza per quello che concerne la gestione delle eccezioni che potranno essere trasformate da tecnologiche (p.e.: java.rmi.RemoteException) in eccezioni applicative: il delegate infatti può essere visto come l'ultimo avamposto dello strato server, ed è qui quindi che verranno effettuate le eventuali riconversioni di errori e messaggi (piuttosto che all'interno del Session Façade, dove la scelta appare invece piuttosto forzata).
Da un punto di vista architetturale il business delegate si pone di fronte al Session Façade, in modo da nasconderne la complessità. A volte business delegate e Session Façade sono le due facce della stessa medaglia tanto che si possono accoppiare secondo uno schema uno-a-uno: i metodi del Façade sono replicati nel nome e nelle firme (con opportuna gestione delle eccezioni).
Conclusione
Giunti circa a metà del lungo cammino verso lo sviluppo simulato di un software tipo, questo mese si sono introdotti i concetti base di architettura e della sua costruzione grazie all'utilizzo di alcuni importanti pattern: la trattazione non voleva essere esaustiva sugli scopi e sugli obiettivi dei vari pattern, dato che rimandiamo per questo alla ricca bibliografia. Continueremo il mese prossimo con altri utili pattern (il DTO, il Proxy/Adapter, il Locator) e inizieremo a mostrare degli esempi di implementazione basati sulla tecnologia EJB.
Riferimenti
- [PID] Giovanni Puliti, "Il programmatore e le sue api - I parte: Lo startup di progetto", MokaByte 123, Novembre 2007
http://www2.mokabyte.it/cms/article.run?articleId=5JX-IVZ-HMT-L867f00000118073931_43f34d02
- [MDA] Doriano Gallozzi, "L'uso della Model Driven Architecture nello Sviluppo di Applicazioni J2EE", MokaByte 87, Luglio/Agosto 2004
http://www.mokabyte.it/2004/07/mda-1.htm
- [REW] Giovanni Puliti, "EJB e la gestione delle eccezioni: Interazione fra le eccezioni e le transazioni all'interno di un container EJB", MokaByte 80, Dicembre 2003
http://www.mokabyte.it/2003/12/ejbexceptions.htm
