Clean Architecture

Architettura del software, definizione e scopi

Generalmente, quando si parla di sviluppo software, la prima cosa che viene in mente ai meno esperti è la scrittura di codice che, opportunamente “inserito” all’interno di un computer, produce uno specifico comportamento.

Uno sviluppatore sa che il proprio lavoro non si esaurisce con ciò. Esso si costituisce di molte altre fasi come l’analisi preliminare dei requisiti, gli unit test, i test di integrazione con altri componenti, il debug, il deploy, etc.
Negli sviluppatori la parola “architettura” evoca l’immagine di rispettabili e potenti individui lautamente remunerati che impartiscono ordini dai loro uffici.

Ma che cos’è l’architettura del software? Quali sono le mansioni dell’architetto per le quali è, ragionevolmente, ben pagato?

Per prima cosa è necessario sfatare il mito secondo il quale l’architetto non è uno sviluppatore. Generalmente, agli inizi delle loro carriere, gli architetti sono stati brillanti programmatori e, spesso, continuano ad occuparsi di incarichi specifici di questi ultimi (anche se in misura minore). Contemporaneamente però, supportano il team di lavoro stabilendo un piano che massimizzi la produttività.

Questa è la definizione che il libro “Clean Architucture” di Robert C. Martin dà alla parola architettura:

“L’architettura di un sistema software è la forma data a tale sistema da coloro che l’hanno realizzato. L’aspetto di tale forma è dato dalla suddivisione di tale sistema in componenti, dalla disposizione di tali componenti e dai modi in cui tali componenti comunicano fra loro.
Lo scopo di tale forma è di facilitare lo sviluppo, il deployment, il funzionamento e la manutenzione del sistema software che essa contiene.
La strategia che governa questa idea di “facilitare” consiste nel lasciare aperte quante più opzioni possibili, per il tempo più lungo possibile.

Questa affermazione può stupire coloro che pensano che l’obbiettivo dell’architettura del software sia quello di far funzionare correttamente l’intero sistema. Ciò, benché sia necessario, non è sufficiente da un punto di vista architetturale. Sono attivi in tutto il mondo sistemi che, benché abbiano una architettura pessima, funzionano correttamente.  Questo perché i problemi architetturali non si palesano nel funzionamento ma, piuttosto, nelle fasi di deploy, manutenzione, estensione o testing.

Lo scopo principale dell’architettura è quello di far sì che il sistema sia facile da comprendere, sviluppare, manutenere ed estendere. Tutto ciò con l’obbiettivo primario di minimizzare i costi di queste operazioni e di massimizzare la produttività dei programmatori.

Il valore del software

Partendo da quanto detto finora, possiamo evidenziare due tipi di valore diversi nel software: il comportamento e la struttura.

Spesso i programmatori tendono a non considerarli entrambi ma indirizzare tutte le proprie energie sul meno importante di essi. Ciò è in controtendenza con l’essenza del lavoro del programmatore, il quale dovrebbe garantire che entrambi i valori siano il più alti possibile.

Comportamento

Il software è un “bene” che, in un certo senso, ha come fine ultimo il risparmio, o l’accumulo, di denaro.
I programmatori sono assunti per massimizzare i profitti degli utilizzatori, o proprietari, delle macchine che loro stessi programmano.

Per farlo, in prima istanza vengono supportati i clienti nello stendere un documento di specifica e successivamente viene steso il codice che consente alle loro macchine di soddisfare i requisiti richiesti (perdonate la semplificazione).

Quando tali requisiti non vengono rispettati, i programmatori sono costretti ad avviare i loro debugger e a correggere il problema.

Molti programmatori sono convinti che il loro lavoro consista in questo; credono che il loro compito sia implementare i requisiti su una macchina e correggere eventuali bug. Purtroppo, o per fortuna, non è tutto qui.

Architettura

Il secondo valore del software ha a che fare con la parola stessa: soft-ware.

Spesso sembra che gli sviluppatori non diano, a questa parola composta, il giusto peso. Questo perché, mentre appare bene in mente il significato di ware (prodotto, articolo, bene… da vendere), riguardo alla parola soft tendiamo a ridurre il significato al fatto che non sia un componente fisico. In realtà la parola soft rappresenta il secondo valore.

Esistono due modi per cambiare il comportamento di una macchina: quello pesante/costoso/poco flessibile/dispendioso ha un nome: hard-ware.
Il software, al contrario, è il modo leggero per farlo.

Va da sé che un sistema software non manutenibile, non estendibile, non facilmente deployabile su ambienti diversi, non testabile, sia paradossalmente l’opposto del suo stesso nome. Generalmente ci si accorge di non avere software abbastanza soft, e quindi di aver tra le altre cose creato un’architettura discutibile, quando gli interventi ed i relativi costi di modifica al software non sono più proporzionati all’ampiezza di questi ultimi, ma alle loro caratteristiche.

La differenza tra ampiezza e forma è il motivo per il quale spesso vi è una sproporzione tra costi e dimensioni della modifica, che spesso si va ampliando con il passare del tempo: è il motivo per il quale il primo anno di sviluppo è molto più economico del secondo e il secondo è molto più economico del terzo e così via.

“Dal punto di vista dei committenti, essi stanno semplicemente fornendo un flusso di interventi di una certa portata. Dal punto di vista degli sviluppatori, i committenti stanno fornendo una sequenza di pezzi di un rompicapo; il loro compito sarà quindi quello di produrre un rompicapo di complessità sempre crescente. Ogni nuova richiesta è più difficile da applicare della precedente, perché la forma del sistema non corrisponde alla forma della richiesta.”

Purtroppo se sono gli sviluppatori stessi a dimenticarsi di dare importanza alla struttura ed alla forma del sistema, non possiamo pretendere che lo facciano il reparto marketing, il reparto commerciale o il management.

Quando sono concordati nuovi sviluppi, manutenzione, bugfix, deploy o scadenze, ogni parte in causa cercherà di fare il bene dell’azienda basandosi esclusivamente sul proprio punto di vista. Il nostro da sviluppatori dovrà essere quello di difendere la qualità del software, mentre il commerciale, ad esempio, cercherà di fare il bene dell’azienda agendo sulle scadenze. Accontentare le altre parti in causa, accorciando i tempi di sviluppo e curandosi solo marginalmente dell’architettura, renderà lo sviluppo sempre più costoso ed ogni modifica diventerà, progressivamente, sempre più difficile, fino a congestionare qualsiasi nuova estensione.

Opzioni aperte

Come abbiamo appena visto, il secondo valore è quello più sottovalutato, ma, al contempo, quello di maggiore importanza, perché questo è ciò che rende soft il software.

Il modo in cui il software viene reso soft consiste infatti nel lasciare aperte il maggior numero di opzioni possibili, e il più a lungo possibile. Questo cosa significa? E soprattutto, quali decisioni dobbiamo rimandare per lasciarle “aperte”? Si tratta dei dettagli che sono trascurabili.

Dobbiamo quindi fare una distinzione fondamentale tra due “elementi” del nostro software:

  • Politiche
  • Dettagli

Le politiche comprendono tutte le regole operative e le procedure. Proprio nelle politiche si colloca il vero valore del sistema.

I dettagli invece sono componenti necessari per far comunicare vari attori quali: esseri umani, sistemi esterni o programmatori con le politiche.

L’obiettivo dell’architetto è quello di studiare e progettare un sistema che riconosca le politiche come elemento essenziale, rendendo i dettagli irrilevanti per queste. Questo fa sì che le decisioni su tali dettagli possano essere differite.

Dettagli o Politiche?

Analizziamo due dei dettagli che spesso vengono trattati erroneamente come politiche, la cui identificazione prematura e la natura non modulare (parleremo più avanti di modularità e dipendenze tra moduli), generano un sistema rigido e difficilmente estendibile.

DATABASE

Il Database è solamente un meccanismo che usiamo per spostare i dati avanti e indietro fra la superficie del disco e la memoria RAM. Il database, in realtà, non è altro che un grande ammasso di bit nel quale memorizzare i dati a lungo termine. Ma solo raramente usiamo i dati in tale forma.

L’errore di trattare il database come dettaglio è prodotto dal fatto che, spesso, tendiamo ad associare il database al modello dati.

Questa è una distinzione fondamentale: il database non è il modello dati!
Il modello dati e le entità di cui è composto il sistema, sono veramente significativi per l’architettura di quest’ultimo, mentre il database è solo un software, un servizio che fornisce l’accesso ai dati.

Dal punto di vista dell’architettura, tale servizio è irrilevante perché rappresenta un dettaglio di basso livello e non dovremmo permettergli di influenzare la struttura primaria del sistema.

Alle logiche di business, che sono il vero valore aggiunto delle applicazioni, non interessa che il software venga utilizzato per memorizzare, su un disco magnetico, il modello dati che utilizzano. Come successivamente vedremo, questo dovrebbe essere limitato alle funzioni del livello più basso dell’architettura.

Alcuni framework (ORM) molto diffusi, al netto della loro indiscussa utilità, hanno accresciuto questo “misunderstanding” tra modello dati e database, in quanto consentono il passaggio di righe e tabelle di un database in tutto il sistema sotto forma di oggetti. Questo è però un errore, per l’architettura, in quanto accoppia i casi d’uso, le regole operative e, nei casi peggiori, anche la UI alla struttura relazionale dei dati.

WEB

Poche rivoluzioni hanno sconvolto il mondo come l’avvento del web.

Poche rivoluzioni hanno sconvolto gli sviluppatori ed i loro programmi come l’avvento del web.

Chi era sviluppatore negli anni della nascita del web ha vissuto in prima persona lo tsunami travolgente che è stato l’arrivo di internet e, molto probabilmente, ha anche avuto a che fare con programmi monolitici la cui spaghettificazione del codice e la mancanza di una buona architettura non ha permesso la transizione di tale sistema da desktop a web.

Il web in realtà non avrebbe dovuto cambiare nulla. Il Web è solo l’ultima di una serie di oscillazioni che il nostro settore ha attraversato.

“Prima del Web, vi era l’architettura client-server. Prima ancora vi erano i minicomputer centrali con i loro terminali stupidi. Prima ancora, vi erano i mainframe con i loro terminali intelligenti a fosfori verdi (che in fin dei conti avevano una notevole analogia con i browser odierni). Prima ancora, vi erano le sale computer e le schede perforate… E la storia si ripete. Non riusciamo a deciderci su dove vogliamo collocare la potenza di calcolo. Facciamo avanti e indietro fra centralizzazione e distribuzione. E, immagino, tali oscillazioni continueranno anche in futuro.”

Ormai tutti vogliono applicazioni web, servizi web, e gli sviluppatori cadono nello stesso errore che hanno compiuto i loro predecessori nel settore: costruiscono applicazioni ritagliate per il web che non resisterebbero ad una nuova oscillazione o cambio di paradigma.

Gli architetti devono ragionare sul lungo termine; tutti i cambiamenti a “breve scadenza” devono essere tenuti a debita distanza dal nucleo centrale di regole operative. Il web dovrebbe essere considerato come un qualsiasi device di I/O o come una GUI. Come, quindi, un dettaglio, tracciando, in quest’ottica, apposite delimitazioni che rendano le logiche operative indipendenti da esso.

Delimitazioni

Le delimitazioni sono un concetto fondamentale nell’architettura di un sistema. Esse sono linee tracciate tra gli elementi software che impediscono a quelli che si trovano da un lato di conoscere cosa avviene nel lato opposto.

L’architettura del software è infatti l’arte di tracciare tali linee (alcune nelle fasi iniziali del progetto, altre in corso d’opera) allo scopo di ritardare il più possibile decisioni inutili che possano contaminare la logica operativa accoppiando componenti.

Abbiamo detto che dobbiamo tracciare linee allo scopo di rimandare il più possibile decisioni premature, ma come capire ciò che è prematuro da ciò che è indispensabile? Le uniche decisioni fondamentali da prendere nelle fasi iniziali del progetto dovrebbero essere solamente quelle riguardanti i requisiti operativi (i casi d’uso) del sistema.
Nelle decisioni assolutamente rimandabili, ci sono ad esempio:

  • La GUI
  • Il web
  • Device di I/O
  • Il database
  • Le librerie di utility
  • Il motore di dependency injection

Una buona architettura di sistema non dipende da tali decisioni, rendendo possibile la loro scelta il più tardi possibile, senza alcun impatto significativo.

Architettura a plugin

“La storia della tecnologia dello sviluppo software parla del modo in cui creare comodi plugin che consentano di creare un’architettura del sistema che sia scalabile e manutenibile.”

Le regole operative, come detto, devono rimanere fortemente disaccoppiate ed indipendenti dai dettagli (cioè dai componenti opzionali) in quanto essi dovrebbero poter cambiare in ogni momento senza comportare uno stravolgimento delle nostre logiche.

Considerando l’interfaccia un plugin, è possibile switchare tra interfaccia web, client/server, SoA, desktop, console in maniera agevole.

Poiché abbiamo scelto di trattare il database come un plugin, possiamo sostituirlo con qualsiasi database SQL o NOSQL, in cloud, sul proprio server, o qualsiasi altro tipo di tecnologia.

Ovviamente non è detto che queste sostituzioni siano banali, alle volte è necessario anche molto lavoro. Ciononostante, un’architettura di questo tipo garantisce che tale modifica sia quantomeno più pratica e meno prona ad avere effetti indesiderati a cascata.

La regola della dipendenza

La figura a cerchi concentrici sottostante, rappresenta una tipica Onion architecture. Più ci spostiamo verso l’interno, più è elevato il livello del software. I cerchi esterni rappresentano i dettagli; quelli interni le politiche.

Questo tipo di architettura si basa sul principio dell’inversione di controllo (inversion of control), chiamata nel libro di Uncle Bob anche come “regola della dipendenza”. Le dipendenze presenti nel codice sorgente devono puntare solo all’interno, verso le politiche di alto livello.

I livelli più interni non devono conoscere nulla dei cerchi più esterni e, più in particolare, il nome di qualcosa che è dichiarato in un cerchio esterno non deve trovarsi menzionato dal codice di un cerchio più interno. Questo ragionamento vale per le funzioni, le classi, le variabili e qualsiasi altra entità software dotata di un nome.

Facendo sì che le politiche non dipendano dai dettagli, saremo in grado di trattare questi ultimi come plugin e poterli sostituire o estendere con nuovi senza modificare le logiche operative (da notare che nella figura possiamo ritrovare quanto detto finora: UI, DB e Web sono nei cerchi più esterni e dipendono dai casi d’uso e dalle entità, non il contrario).

Onion Architecture

Entità

Le entità incapsulano le regole operative critiche dell’azienda. Un’entità può essere un oggetto dotato di metodi o può essere un insieme di strutture e funzioni.

Incapsulano le regole più generali e di alto livello; sono probabilmente gli elementi meno soggetti a cambiamenti qualora dovesse mutare qualcosa all’esterno.

Non saranno influenzati, ad esempio, da una modifica ai layout di pagine web, o dal tipo di database, o dal tipo di libreria per il logging che useremo.

Casi d’uso

In questo livello di codice sono implementati tutti i casi d’uso del sistema.

È qui gestito il flusso dei dati in entrata ed in uscita dalle entità, utilizzando le regole operative critiche di queste ultime.

Modifiche a questo strato dell’architettura non influenzeranno le entità, inoltre, similmente a queste ultime, non sarà influenzato da elementi nei livelli più esterni come framework per lo unit test, il database o i dispositivi I/O. Tuttavia, il livello dei casi d’uso, è influenzato da modifiche alle entità che, qualora cambiassero le loro regole operative, porterebbero ripercussioni nel codice qui presente.

Adattatore di interfacciamento

Questo strato intermedio è costituito da un insieme di adattatori che convertono i dati dal formato impiegato per i casi d’uso e le entità, al formato più appropriato per gli attori più esterni dell’architettura, come il database o il Web.

Un esempio calzante sono le architetture MVC o MVP, che risiedono in questo anello dell’architettura.

Il codice che si trova entro questo cerchio non dovrebbe conoscere nulla, ad esempio, del database. Qualora si usasse un database SQL, allora qui non dovremmo trovare neanche una riga di script SQL, ma solamente dei componenti con lo scopo di adattare le entità usate dai casi d’uso ad un formato più consono ad essere consumato dal cerchio più esterno.

Sempre in questo livello si trova qualsiasi altro adattatore necessario per convertire i dati da una forma esterna, come quella di un servizio esterno, alla forma interna usata dai casi d’uso e dalle entità.

Framework e driver

Il livello più esterno dell’architettura a cipolla è generalmente costituito, come già sostenuto, dai framework e da strumenti come il database e il web o la UI.

Generalmente l’unico codice che qui scrivibile è quello di collegamento che comunica con gli strati più interni del cerchio, che di solito è di dimensioni molto ridotte.

Nel livello dei framework e dei driver si trovano tutti i dettagli, tra cui il Web e il database.

Link utili all’approfondimento:

Attraversamento delle limitazioni tramite Dependency Inversion Principle:
https://martinfowler.com/articles/dipInTheWild.html

Onion architecture (i nomi degli anelli potrebbero cambiare ma i principi rimangono invariati):
https://www.c-sharpcorner.com/article/onion-architecture-in-asp-net-core-mvc/

Ovviamente il libro “Clean architecture”