In questo articolo affrontiamo la definizione dei requisiti e la realizzazione dei modelli UML per un ipotetico sistema software. Non ci soffermeremo sugli importanti dettagli teorici, peraltro spesso già trattati sulle pagine di MokaByte e nei libri curati dalla nostra rivista, ma passeremo in rassegna i diversi diagrammi e il loro uso contestuale alla progettazione.
Introduzione
Questo articolo è il primo di una serie nella quale cercheremo di indirizzare la progettazione di un ipotetico sistema software individuando tre importanti fasi:
- partendo dall’individuandone dei requisiti, la realizzazione dei modelli che rappresentano il sistema di riferimento, grazie alla definizione del dominio applicativo, per passare poi a progettare i componenti e le dipendenze reciproche, sia funzionali che tecnologiche;
- la scelta di una architettura applicativa flessibile, orientata agli obiettivi prefissati per ciascuna fase del ciclo di vita del software;
- la definizione del processo di gestione della configurazione, coerente con gli scenari di change management immaginati.
Queste tre fasi, strettamente legate fra loro, ci consentiranno di:
- individuare i moduli software e le loro reciproche dipendenze;
- creare il contesto per condurre lo sviluppo del software in parallelo e in modo isolato, minimizzando i tempi complessivi della fase di realizzazione;
- ridurre i tempi di integrazione dei moduli;
- introdurre le tecnologie che realizzano scenari composti e flessibili di integrazione e scalabilità;
- valutare l’impatto delle eventuali modifiche del software, aumentando l’efficacia nella gestione della manutenzione, riuscendo a circoscrivere gli interventi;
- definire, in relazione alle varie fasi del progetto, la composizione più adeguata delle figure professionali coinvolte;
- ottimizzare la pianificazione delle attività, in modo da poter differire nel tempo il rilascio di versioni diverse di ciascun componente logico e del sistema nel suo complesso.
In questa prima parte, il nostro sistema software sarà modellato in UML in coerenza con gli obiettivi che ci siamo preposti, presentando alcuni modelli la cui caratteristica è quella di essere indipendenti dalla complessità globale e dalle dimensioni del sistema. Questi modelli verranno presentati con un dettaglio sempre maggiore, partendo da una vista logica di alto livello per arrivare progressivamente alla vista di dettaglio dove vengono specificate le tecnologie di realizzazione, i calcolatori e i middleware di esecuzione.
Basi teoriche dell’UML
Il nucleo del linguaggio UML (Unified Modeling Language), da sempre gestito dal consorzio Object Management Group, fu definito nel 1996 da Grady Booch, James Rumbaugh e Ivar Jacobson, il cui obiettivo era quello di unificare in una versione industriale standard gli approcci precedenti e alcune tra le best practice di modellazione.
All’inizio degli anni novanta i metodi di modellazione di riferimento della comunità dell’Object Oriented erano quelli di Gary Booch, l’ OMT (Object Modeling Technique) di James Rumbaugh e quelli promossi da Ivar Jacobson, l’OOSE (Object Oriented Software Engineering) e l’Objectory. UML nasce proprio dalla fusione dei concetti presenti in questi metodi.
Il metodo di Booch, considerato efficace in fase di disegno e codifica, ma con qualche lacuna nella fase di analisi, è costituito da una serie di notazioni attraverso le quali il sistema viene analizzato e suddiviso in un insieme di viste a cui corrispondono alcuni diagrammi di modello.
L’OMT di Rumbaugh è riconosciuto particolarmente efficace nella fase di definizione dei requisiti. L’accuratezza relativa alla fase di esplorazione delle specifiche di un sistema lo hanno reso particolarmente adeguato alle fasi di test.
Particolarmente efficaci per l’analisi del dominio del problema sono i metodi di Jacobson, OOSE e Objectory, che vedono la formalizzazione del concetto di use case come strumento chiave nella rappresentazione dei requisiti di un sistema software.
L’UML consente di costruire modelli orientati agli oggetti che rappresentano, in termini di comportamento e di struttura, domini di genere diverso. In considerazione del fatto che si tratta di una sintassi e di regole di interpretazione, oltre che per la modellazione di sistemi software, l’UML viene sempre più spesso impiegato per descrivere domini di altri tipo anche in ambiti non strettamente legati alle tecnologie dell’informazione.
Modellazione del sistema
Di seguito descriveremo le operazioni necessarie per la modellazione di un sistema, partendo dalla fondamentale fase della formalizzazione dei requisiti. Per ogni operazione, presenteremo i modelli UML che ci aiutano in tale compito. Risulta chiaramente impossibile realizzare un sistema software senza una approfondita conoscenza del suo dominio applicativo. Questa conoscenza non può che essere quella degli specialisti del dominio che ne conoscono tutti gli aspetti, le regole e i problemi. Un progetto software inizia proprio dall’analisi del dominio in cui opera.
Requisiti funzionali: diagrammi dei casi d’uso
I casi d’uso (use case) rappresentano una tecnica per identificare i requisiti funzionali di un sistema. La loro codifica quindi deve essere realizzata nelle fasi iniziali del progetto, per essere poi approfondita, attraverso versioni più dettagliate dei modelli, subito prima dello sviluppo. I casi d’uso forniscono una visione del sistema dall’esterno e non riportano quindi correlazioni con le classi che implementano il software. Sono infatti basati sulla descrizione “narrativa” delle interazioni tra utenti e sistema, interazioni che si articolano in scenari diversi che hanno in comune lo scopo finale dell’attore (l’attore è chi prende parte a un sistema, non necessariamente un utente umano, ma anche un sottosistema, un dispositivo etc.). Ogni scenario è costituito da una sequenza di passi che esprime una particolare interazione utente-sistema. Eventuali passi complessi possono essere espressi a loro volta come un altro caso d’uso.
Figura 1 – Diagramma dei casi d’uso.
Business Object Model
Diversamente dagli Use Case, che danno una rappresentazione dinamica di un sistema, il Business Object Model fornisce una vista statica del dominio funzionale, descrivendone le entità rilevanti, le loro relazioni e i loro attributi, senza però evidenziare come questi cambiano nel tempo. Use Case e Business Object Model, fornendo una prospettiva diversa del dominio funzionale che rappresentano, concorrono vicendevolmente a fornirne una conoscenza sempre più approfondita. Possiamo considerare il Business Object Model una utile guida nelle attività di modellazione di dettaglio, come per esempio la definizione del dizionario e del modello fisico dei dati.
Figura 2 – Business Object Model.
Domain Driven Design
Con l’applicazione dei principi del Domain Driven Design (DDD) possiamo incrementare la capacità dei processi di sviluppo di modellare e implementare la complessità di un dominio applicativo. Il Domain Driven Design coordina aspetti di modellazione e di sviluppo mostrando come questi possano interagire con l’obiettivo di creare soluzioni sempre migliori. Una buona modellazione accelererà la fase di sviluppo mentre le evidenze provenienti dallo sviluppo miglioreranno la modellazione.
Le esperienze maturate nei primi decenni dello sviluppo del software, in cui ha dominato l’approccio “waterfall”, hanno dimostrato il forte limite della mancanza di feedback tra sviluppatori veri e propri e progettisti del sistema. In tal senso, le numerose metodologie “agili”, prendono atto della difficoltà di creare in anticipo un modello completo che copra tutti gli aspetti del dominio, e spingono verso un avanzamento collettivo. Attraverso l’impiego di una spiccata flessibilità dell’implementazione, caratterizzata dal continuo refactoring, e attraverso sviluppi iterativi a cui partecipano gli esperti del dominio funzionale, il gruppo di sviluppo otterrà una maggiore conoscenza del dominio applicativo.
Riportiamo di seguito la mappa di Eric Evans che illustra i pattern più importanti del Domain Driven Design.
Figura 3 – Eric Evans: mappa dei modelli del Domain Driven Design.
Tra i modelli di Evans prendiamo in esame il pattern Layered Architecture: esso trae spunto dalla considerazione che molta parte dello sviluppo software non è riferibile al dominio applicativo. Infatti accade spesso che, sulla base di valutazioni superficiali, il codice di supporto venga codificato negli oggetti di business del dominio. Questa contaminazione tra codice del dominio applicativo e codice di supporto renderà estremamente difficile implementare in modo coerente gli oggetti model driven rendendo molto complessa la gestione delle modifiche e impossibile l’automazione dei test.
In questo contesto diventa essenziale dividere la complessità del software in layer fortemente disaccoppiati, immaginando un modello dove ciascuno di essi sia autoconsistente e dipendente solo dai layer sottostanti. Questo risultato è raggiunto da questo pattern concentrando il codice relativo al modello di dominio in un layer, isolandolo da quello corrispondente all’interfaccia utente e all’infrastruttura. In questo modo gli oggetti di dominio avranno la sola responsabilità di esprimere il modello applicativo.
Riportiamo di seguito in forma grafica i quattro layer individuati da Evans.
Figura 4 – Eric Evans: Layered Architecture.
Mentre la User Interface ha la responsabilità di presentare le informazioni e interpretare i comandi dell’utente, possiamo considerare l’Application Layer come in uno strato applicativo senza logica funzionale. Nell’Application Layer viene codificata la logica di coordinamento delle attività dell’applicazione. Non ci sono quindi funzionalità di persistenza dei dati o degli stati degli oggetti di dominio, quanto piuttosto logica che mantiene lo stato di avanzamento di un task applicativo.
Nel Domain Layer, cuore della logica applicativa, sono contenute tutte le informazioni del domino che vengono persistite dal layer di infrastruttura costituito da librerie di persistenza, di comunicazione e di supporto.
Diagramma dei componenti
Il diagramma dei componenti UML, tra i modelli designati alla modellazione di alto livello, rappresenta il sistema attraverso l’individuazione dei suoi componenti, e delle reciproche dipendenze, che possiamo definire come unità autonome del sistema. Si tratta di modello indipendente dalle dimensioni, dalla complessità del sistema e dalle tecnologie di implementazione ed esecuzione.
In questa accezione i componenti rappresentano “pezzi” di software che possono essere acquisiti e aggiornati in modo indipendente. Suddividere il sistema in componenti rappresenta quindi una decisione che abilita aspetti tecnico/organizzativi come la scomposizione dell’effort di sviluppo ma che ha anche una importante rilevanza di marketing. In questo contesto, componenti, sottosistemi (insiemi di componenti) e relazioni reciproche possono essere in modo flessibile riusati e riposizionati. Saranno proprio le relazioni di dipendenza che ci permetteranno di evidenziare gli impatti derivanti dalla modifica di un componente. Di seguito riportiamo un esempio di diagramma di componenti (figura 5). e 6)
Figura 5 – Diagramma dei componenti.
Nel diagramma dei componenti di figura 6 vengono inoltre dichiarate anche le tecnologie di implementazione delle interfacce.
Figura 6 – Diagramma dei componenti con specificazione delle tecnologie di implementazione per le interfacce.
Diagramma dei componenti: specializzazione delle tecnologie
In questa vista, il progettista illustra le scelte tecnologiche che, rappresentate come dipendenze (verso framework, middleware, container, ecc.), verranno dettagliate nella prossima parte in termini di pattern di sviluppo. Attraverso questa specializzazione del diagramma dei componenti, viene infatti specificata la versione della JVM Java che sarà utilizzata, l’integrazione del framework di Spring e il container di esecuzione. Questo componente verrà quindi realizzato sotto forma di EJB in tecnologia Java EE e distribuito in un container WebSphere Application Server. Siamo quindi passati da un modello assolutamente logico a uno in cui viene data evidenza delle tecnologie di implementazione ed esecuzione. Nell’immagine che segue abbiamo indicato in giallo i componenti sviluppati nell’ambito del progetto rispetto a quelli di “terze parti” e di run time.
Figura 7 – Diagramma dei componenti:dipendenze tecnologiche.
Realizzazione del componente: diagramma di struttura composita
Una volta definite le dipendenze il progettista entra nel dettaglio del componente illustrandone la struttura interna attraverso il diagramma di struttura composita. Riportiamo di seguito il modello che realizza il componente: è evidente la scelta del progettista di introdurre un pattern che disaccoppia la logica di implementazione da quella di interfaccia e di presentazione.
Figura 8 – Diagramma di struttura composita.
Artefatti di distribuzione
Attraverso i diagrammi di package il progettista descrive la modalità con cui i moduli che realizzano un componente si articolano sugli artefatti di distribuzione. In ragione del fatto che il modello riduce la complessità dei sistemi, possiamo considerarlo tanto più importante quanto maggiori sono le dimensioni del sistema software che si vuole descrivere.
Nella figura di seguito illustriamo come il diagramma di struttura composita, relativo al componente di esempio, si articola negli artefatti di distribuzione. È chiaramente evidente il maggiore dettaglio di progettazione espresso; possiamo infatti notare la corrispondenza del pattern di sviluppo, indicato nel diagramma di struttura composita, sugli artefatti di distribuzione.
Figura 9 – Diagramma di struttura composita e artefatti di distribuzione.
Diagramma di distribuzione
Considerato come il derivato logico del diagramma di packaging, il diagramma di distribuzione descrive l’articolazione degli artefatti software sui corrispondenti sistemi e middleware applicativi di esecuzione. Questo modello mette in evidenza aspetti non legati all’implementazione del software ma alla possibilità di scalare i sistemi con architetture bilanciate e sistemi di alta affidabilità. Il diagramma riportato di seguito rappresenta un semplice modello in cui il servizio viene distribuito nell’EJB Container dell’Application Server. Possiamo notare come ciascuna libreria si articola sui nodi dei sistemi. Per distinguere le librerie dei framework o dei run time di esecuzione, abbiamo evidenziato in un colore diverso quelle che implementano il servizio.
Figura 10 – Diagramma di distribuzione.
Diagramma delle classi
L’ultimo diagramma UML che presentiamo è, probabilmente, quello più conosciuto e usato, il diagramma delle classi, attraverso il quale il progettista raggiunge il dettaglio necessario perch� possa iniziare la realizzazione. Siamo così passati da una visione di alto livello, il diagramma dei componenti, dove non c’è nessun riferimento alle tecnologie di realizzazione e ai sistemi di esecuzione, per arrivare a specificare il linguaggio di programmazione e i pattern di sviluppo. Si può infatti notare dal diagramma delle classi riportato di seguito le modalità di dettaglio con cui il progettista realizza il pattern di separazione dell’implementazione del servizio dalla sua interfaccia e quest’ultima dal facade tecnologico di presentazione. Nello specifico quest’ultimo implementa Spring EJB, modalità attraverso la quale si è scelto di isolare la complessità tecnologica di esposizione in una classe distinta da quella che realizza la logica applicativa. Vedremo nella seconda parte della serie come attraverso questo pattern potremo utilizzare in modo efficace implementazioni diverse della logica applicativa, o anche riesporre la stessa attraverso tecnologie diverse.
Figura 11 – Diagramma delle classi: implementazione di dettaglio del componente.
Conclusioni
In questa prima parte della serie, abbiamo presentato rapidamente come è possibile modellare un componente di un sistema software con alcuni diagrammi UML, partendo da una vista logica generica per arrivare progressivamente al dettaglio relativo al linguaggio di programmazione, ai pattern di sviluppo e alle tecnologie di esecuzione. Dopo aver formalizzato i requisiti, abbiamo infatti illustrato nel diagramma dei componenti le relazioni tra i componenti logici di un sistema e le loro dipendenze dai run-time di esecuzione. Attraverso il diagramma di struttura composita è stata definita l’architettura interna del componente e il pattern di sviluppo. Il successivo diagramma UML ha descritto l’articolazione degli artefatti software e il diagramma di distribuzione ne ha illustrato la collocazione sui sistemi, container e middleware. Il dettaglio maggiore è stato rappresentato dal diagramma delle classi dove il progettista illustra tutti i dettagli di implementazione, dal linguaggio all’articolazione delle classi che realizzano i pattern di sviluppo scelti. Dopo questo articolo “introduttivo”, nella prossima parte illustreremo l’articolazione dei progetti di sviluppo orientata ad attuare la modellazione realizzata.
Riferimenti
[1] UML Diagrams
http://www.agilemodeling.com/essays/umlDiagrams.htm
[2] Eric Evans “Domain-Driven Design: Tackling Complexity in the Heart of Software”, Addison Wesley, 2004
[3] Domain Driven Design
http://sosa.ucsd.edu/teaching/cse294/fall2007/dddbook.pdf
[4] UML Components
http://www.imamu.edu.sa/DContent/IT_Topics/UML%20Components.pdf
[5] La voce OMT su Wikipedia
http://it.wikipedia.org/wiki/Object_Modeling_Technique