Rappresentare l’architettura
Da sempre l’ingegneria informatica si è posta la questione di come rappresentare l’architettura del software e nel corso della sua storia sono state individuate diverse modalità o tecniche.
Oggi la rappresentazione a livelli (tier) è quella più diffusa: dall’inizio degli anni 2000 si parla di architettura multitier, detta anche n-tier, che coincide solitamente con una rappresentazione a tre livelli in cui nella parte più alta è rappresentato il livello denominato “presentazione”, nel livello centrale “l’elaborazione dei dati” e nel livello più basso è rappresentata la “persistenza dei dati”.
Più propriamente si parla dei livelli “Presentation”, “Business (o Logic)” e “Data”.
Dipendenza e accoppiamento
L’architettura in questione prevede che la direzione delle dipendenze sia dall’alto verso il basso, ovvero che il Presentation Layer dipenda dal Business Layer che a sua volta dipende dal Data Access Layer che dipende dal Database, come mostrato in figura 2
Questo forte accoppiamento oltre a produrre software difficile da testare ne aumenta la complessità rendendolo difficile da cambiare, cioè rigido, facile da rompere, cioè fragile, e difficile da riutilizzare, cioè immobile.
Inoltre, la rappresentazione multitier induce a pensare che necessariamente ad un estremo dell’architettura ci sia un utente e all’estremo opposto un database. Questa rappresentazione è lontana dalle moderne esigenze di fornire servizi autonomi rispetto all’utente e indipendenti dalla tecnologia.
Pensiamo che una buona architettura dovrebbe rendere esplicito il suo intento, concentrandosi sulle parti importanti dell’applicazione e cercando di rinviare il più a lungo possibile le scelte sui dettagli tecnologici.
“The database is just a detail that you don’t need to figure out right away” cit. Robert Martin (Uncle Bob)
L’importanza dei livelli
Proviamo ora a porci una domanda. Quale dei tre livelli è il più importante? Quale contiene effettivamente il core dell’applicazione?
Crediamo che non ci siano molti dubbi nell’affermare che il livello principale, quello che è più difficoltoso in termini di definizione di dettaglio, quello che contiene i valori differenzianti di questa applicazione con quella della concorrenza e che deve essere protetto dai capricci delle tecnologie è quello centrale.
Il problema è farlo “bene”, il problema è cioè costruire un layer realmente autonomo rispetto agli altri. Spesso si parte da questa precisa volontà di conservare tutto il valore nel livello centrale, ma poi nel corso dello sviluppo la pulizia vien meno e gli altri livelli si fanno carico più o meno consapevolmente della logica di business.
Logica applicativa e interfaccia utente
Si pensi a quanta logica è presente nell’interfaccia utente: avere la logica di business nell’interfaccia utente implica almeno due problemi.
Il primo è che se si decide di cambiare l’interfaccia utente, per esempio accostando all’interfaccia web una app nativa, quella logica dovrà essere reimplementata anche nella nuova interfaccia, con il rischio di errori, dimenticanze, incomprensioni e con la certezza di aver duplicato il codice, che poi dovrà essere mantenuto nel tempo…
Il secondo è che sarà molto difficile, se non impossibile, testare le logiche applicative. Gli unici test possibili potranno essere fatti a livello di interfaccia grafica con la conseguenza di avere test lenti e poco affidabili a causa delle dipendenze con gli altri livelli.
Logica applicativa e persistenza
Analogamente parte della logica di business potrebbe essersi infiltrata nel livello di persistenza con problematiche simili a quelli individuate poc’anzi.
Un’alternativa al Multi-tier
Tornando al tema centrale che è quello della rappresentazione dell’architettura, esiste un modalità migliore dell’architettura multitier per rappresentare, e quindi progettare, un’applicazione che sia effettivamente disaccoppiabile, cioè non rigida, non fragile e non immobile?
Un valido aiuto ci viene da Alistair Cockburn che introduce il concetto di architettura esagonale, nota anche come Port and Adapter [1].
L’idea è di superare la rappresentazione per livelli (layered model), coma una pila, ma preferire una rappresentazione concentrica (inside outside model) che pone al centro il core application: in esso risiedono gli oggetti di dominio che rappresentano nel loro insieme la logica e le regole di business.
All’interno del core application si parla il linguaggio del dominio applicativo e non vi sono riferimenti espliciti a dettagli tecnologici che sono invece situati all’esterno dell’esagono.
Core, port, adapter
Intorno al core application troviamo le port che fungono da confini (boundaries) tra gli oggetti di dominio e il mondo esterno. Possiamo dire che una port è una rappresentazione astratta di un servizio e che come vedremo in seguito è la chiave di volta per permettere di disaccoppiare il core domain dai servizi esterni.
In corrispondenza di ciascuna port, in una corona più esterna, troviamo gli adapter ovvero gli oggetti che si faranno carico di implementarla e parlare fisicamente con il mondo esterno.
A livello implementativo questo meccanismo viene realizzato attraverso l’utilizzo dei pattern Façade [2] e Adapter [3].
Un esempio: e-commerce
Facciamo un esempio. Nel contesto di un’applicazione di e-commerce, ipotizziamo di avere nel dominio la logica per determinare l’ammontare di un carrello e che vogliamo perfezionare l’ordine utilizzando un sistema di pagamento esterno, ad esempio Paypal. Per poterlo raggiungere introdurremo una port che, utilizzando lo stesso linguaggio del dominio, astrarrà il servizio di pagamento. Successivamente realizzeremo un adapter che implementando l’astrazione fornita dalla port dialogherà effettivamente con Paypal.
Nell’ipotesi in cui si presenti la necessità di sostituire il sistema di pagamento, basterà implementare un altro adapter che, basandosi sempre sull’astrazione fornita dalla port, dialogherà con il nuovo servizio.
Con lo stesso approccio sarà inoltre possibile testare le logiche di dominio in maniera indipendente dal servizio di pagamento e per farlo basterà implementare un mock adapter.
Il concetto di Port – Adapter può essere applicato a tutti i componenti che non costituiscono il core dell’applicazione: dalla persistenza dei dati alla presentazione, fino ai servizi periferici a cui si connette la nostra logica applicativa.
La rappresentazione non è il sistema, ma…
Per concludere questa introduzione ai temi dell’architettura esagonale, è necessaria una precisazione. La rappresentazione dell’architettura non è di per sé una panacea. E, viceversa, si possono fare applicazioni ottime dal punto di vista architetturale anche con una pessima rappresentazione architetturale.
È però evidente che una rappresentazione che induca i progettisti a farsi delle domande in termini di disaccoppiamento, testabilità, apertura al cambiamento, etc. è in generale da preferirsi ad una rappresentazione che sottace questi elementi.
Conclusioni
Nelle prossime puntate, con un punto di vista più tecnico, vedremo come alla base di questa architettura ci sia la dependency injection, un pattern che rende concreta la possibilità di iniettare un oggetto dall’esterno. Inoltre, rimanendo sul piano rappresentativo, vedremo altre architetture che derivano o sono simili all’architettura esagonale di Cockburn e che vengono utilizzate in contesti anche diversi dalla mera progettazione di un’architettura.