Cosa vuoi fare da grande?
Volevo fare il Software Architect, quindi, come per ogni cosa che chiunque vuole fare, ho cominciato a documentarmi sul ruolo di questa figura. E la domanda che mi sono posto è “Qual è il compito di un Software Architect?”
Inizialmente, suppongo come molti, mi sono formato per fornire soluzioni architetturali adeguate, proporre soluzioni con il giusto mix di semplicità e complessità per supportare i progetti di chi si affidava al mio team per costruire la propria soluzione. Poi, frequentando e confrontandomi con le persone giuste, ho scoperto che, forse questa è solo una parte del lavoro del Software Architect.
C’è un’altra domanda, che negli articoli di questa serie, mi sono già posto, ossia: “Perché sviluppiamo software?”. È quello che dovremmo chiederci tutti. Noi sviluppiamo software per risolvere problemi di business. Il nostro lavoro non è scrivere del codice che funziona: per quello ci sono i kata, molto utili per affinare le nostre tecniche, per impararne di nuove, ma non risolvono problemi di business. Il nostro compito è progettare sistemi che funzionino e risolvano problemi.
Mettere insieme ordine e disordine
Ed è proprio qui che nascono i veri problemi, perché nella realtà esistono due tipi di sistemi, i sistemi ordinati, e i sistemi disordinati (fig. 1).
Il software, come potete notare dall’immagine, appartiene ai sistemi ordinati, e infatti è costituito da regole matematiche e ingegneristiche rigorose e prevedibili. Il business, invece, appartiene alla categoria dei sistemi disordinati, così come noi esseri umani.
Il compito del Software Architect, dunque, è quello di progettare un sistema ordinato che risolva i problemi di un sistema disordinato. Avete presente il gioco delle forme, in cui bisogna incastrare le forme nel posto corretto? Provate a incastrare la forma cubo nell’alloggiamento in cui deve entrare la forma cerchio… Ecco, il Software Architect ha questo obiettivo.
Per risolvere problemi di business, dobbiamo imparare il linguaggio del business o, meglio, dobbiamo essere in grado di creare un linguaggio, e un modello, che sia noi che il business possiamo capire, e su cui possiamo ragionare. Dobbiamo essere in grado di collegare fra loro diversi linguaggi fra persone che hanno diversi background che compongono il team che realizzerà il prodotto. Parafrasando la metafora di Gregor Hohpe, il valore di un software architect, si misura in base ai piani di un grattacielo che è in grado di coprire. Il suo valore sta proprio nell’aiutare tutti i partecipanti a trovare questo linguaggio comune, e di conseguenza, un modello comune su cui costruire il prodotto finale.
Linguaggi e modelli
Su questi punti E. Evans non aveva dubbi, e lo abbiamo visto nei dettagli. Ubiquitous Language e Bounded Context sono due pattern fondamentali per Domain–Driven Design, che si basano proprio sul linguaggio e sulla modellazione della realtà.
Il primo elemento, il linguaggio, è necessario per stabilire di cosa stiamo parlando in modo chiaro, senza argomenti o conoscenza implicite. Tutto deve essere chiaramente esplicitato e compreso dall’intero Team. Anche nelle conversazioni fra persone succede la stessa cosa. Se vi unite ad una conversazione fra amici quando questa è già iniziata, la domanda spontanea che viene da porre è “Di cosa state parlando?”. Senza questa richiesta di identificazione del contesto si rischia seriamente di deragliare, di parlare di un argomento simile, ma non dell’argomento stesso della conversazione. La cosa migliore che ci può capitare è una risata collettiva, la peggiore rispondere in maniera inopportuna a una domanda. Senza conoscere il contesto è impossibile creare un modello mentale su cui ragionare.
Nella creazione di prodotti software si parla molto spesso di modelli, e lo stesso E. Evans ne parla dettagliatamente nel suo iconico blue book, ma cosa intendiamo esattamente per modello? L’architettura del software è basata sui modelli, la programmazione a oggetti ne ha fatto il punto focalizzante della sua teoria. Modellare la realtà in oggetti ideali. Spesso, però, questi modelli falliscono proprio nel loro intento principale: essere un’immagine coerente della realtà. Ma perché?
I filosofi non erano programmatori, però…
Se torniamo indietro, ai tempi delle grandi discussioni filosofiche del mondo greco, possiamo stare certi che non si trattasse di scegliere fra programmazione funzionale o paradigma a oggetti. Eppure, nelle enunciazioni di due personaggi che non la pensavano esattamente allo stesso modo riguardo alla visione della realtà possiamo riscontrare quasi la stessa divergenza che c’è fra programmazione funzionale e Object Oriented Programming.
Il primo personaggio è Eraclito (535-475 a.C.) secondo il quale “Nessun uomo entra due volte nello stesso fiume, perché non è lo stesso fiume e lui non è lo stesso uomo”. Ossia, il mondo, secondo Eraclito, è un flusso in costante divenire.
Il secondo filosofo è invece Platone (428-348 a.C.) il quale, più di un secolo dopo, sosteneva che “Il mondo sensibile che ci circonda è solo una copia imperfetta delle Forme che rappresentano la vera realtà”.
Due visioni opposte del mondo. Non dimentichiamo che, nella filosofia occidentale moderna, il pensiero di Platone, insieme a quello di Aristotele, ha costituito per secoli il nucleo fondamentale delle nostre convinzioni. Molti aspetti del nostro modo di interpretare il mondo e le cose che ci circondando, derivano dal pensiero filosofico di Platone e di coloro che, a più riprese dall’età tardoantica fino al rinascimento, si sono rifatti al pensiero di questo filosofo. Siamo figli di Platone quando adottiamo il paradigma a oggetti dell’OOP.
Domain-Driven Design — che peraltro non rinnega OOP — è invece più vicino al pensiero di Eraclito, perché, pur conscio dell’importanza del modello per capire e affrontare il problema, è altresì convinto convinto che un modello è soggetto a mutazioni, e che è immerso in un fluido divenire. Il che, a guardar bene, è anche alla la base della metodologia Agile.
Il grosso problema, infatti, non è il modello in sé, ma è il modello pensato con l’idea che sia corretto e valido per sempre. Questa è la “trappola” reale, e quando scopriamo che ciò che sappiamo — o, peggio, che pensiamo di sapere — è pericolosamente distante dalla realtà, allora viene prodotto il cigno nero, secondo la celebre metafora di Nassim Taleb.
Lo scopo di un modello
A cosa mi serve un modello? Con un esempio pratico è molto facile capirlo. Io sono un grande appassionato di montagna, in tutte le stagioni. Quando voglio raggiungere un rifugio che non conosco, la prima cosa che faccio è procurarmi le informazioni sul sentiero da seguire e sull’altimetria da superare. Una carta topografica che mi fornisca informazioni sugli elementi naturali che contraddistinguono la zona in modo peculiare (fiumi, laghi, passi, etc.), ma anche le strutture inserite dall’uomo, come appunto i sentieri: è il modello perfetto per risolvere il mio problema.
Se invece conosco bene il modo per raggiungere il rifugio, ma la mia intenzione è andare a ripetere qualche via di arrampicata che non conosco in zona, allora mi serve un altro tipo di mappa. Mi serve uno schizzo della via di arrampicata con le indicazioni della lunghezza dei tiri di corda, delle protezioni in loco, con un’indicazione delle difficoltà di ogni tiro e altre informazioni simili. Non mi interessa la fotografia della montagna che andrò a scalare, perché, per quanto bella essa possa essere, non mi fornisce le informazioni necessarie.
Un appassionato di fotografia potrebbe obiettare che, in entrambi i casi — carta topografica o schema della via di arrampicata — mi sto perdendo la bellezza del paesaggio perché nessuna mappa da me scelta mi restituisce la bellezza dei luoghi. E dal suo punto di vista ha perfettamente ragione; ma abbiamo obiettivi differenti!
Secondo Rebecca Wirfs-Brock, un modello è la rappresentazione semplificata di una cosa, o di un fenomeno, che enfatizza intenzionalmente alcuni aspetti, trascurandone altri. È un’astrazione con un uso specifico in mente. E un’astrazione può essere precisa solo se il contesto è fissato e compreso da tutti i partecipanti alla comunicazione.
Lo schizzo della via di arrampicata è funzionale solo se tutti quelli che verranno con me hanno come obiettivo la ripetizione della via. Se vogliono godersi la passeggiata, hanno fra le mani un modello inadatto.
Ulteriori considerazioni di ordine “filosofico”
Tornando nel nostro mondo, ossia la progettazione di sistemi che risolvono problemi di business, il linguaggio diventa uno strumento fondamentale per la comprensione chiara del problema fra tutti i membri del Team. Se pensiamo all’esempio precedente, non possiamo permetterci di mescolare escursionisti e scalatori e pretendere che utilizzino la stessa mappa (modello) per raggiungere il loro diverso scopo. C’è bisogno di un’intesa sui termini del problema da risolvere e sul conseguente modello da realizzare.
Secondo Wittgenstein, prima ancora di Evans, “I limiti del mio linguaggio significano i limiti del mio mondo”: esattamente allo stesso modo, i limiti dell’Ubiquitous Language costituiscono i limiti, i bound, del nostro contesto (Bounded Context). Il linguaggio non è solo descrittivo, ma costruttivo, e definisce il modo in cui i team comprendono e affrontano il dominio, o una parte di esso.
Ontologia
Un modello, in Domain-Driven Design, può essere visto come una rappresentazione ontologica parziale, costruita per riflettere aspetti rilevanti di una realtà complessa. L’ontologia è la branca della filosofia che studia ciò che esiste e il come possiamo classificare e comprendere le cose che ci circondano. È un modo per organizzare la realtà, stabilendo proprio le categorie di oggetti e comprendendone le relazioni tra di essi. In pratica, è proprio come costruire una “mappa” di ciò che esiste e del modo in cui questi elementi si collegano fra loro. Esattamente ciò che cerchiamo di fare con i pattern Bounded Context e Context Mapping quando applichiamo DDD nei nostri progetti.
Teleologia
Un’altra domanda fondamentale che ci dobbiamo porre, quando cerchiamo di modellare un problema, è il suo perché, il suo scopo. “Il perché è più importante del come” (seconda Legge dell’Architettura del Software). La teleologia è un’altra branca della filosofia che si occupa proprio dello studio degli scopi o delle finalità di un processo o di un’entità. Si concentra proprio sul perché qualcosa accade, cercando di capire il fine o l’obiettivo che una determinata cosa, o evento, sta cercando di raggiungere. Qual è lo scopo di questo modello in questa parte del sistema?
Appare chiaro ora perché, quando esploriamo il dominio su cui andremo a lavorare, è importantissimo, se non addirittura fondamentale, che siano presenti tutti i soggetti coinvolti dal progetto.
Fenomenologia
Nell’analisi del dominio che riguarda il prodotto che intendiamo creare, dovrà essere presente non solo chi ha pensato di realizzarlo, ma chi dovrà utilizzarlo, chi dovrà venderlo, chi dovrà realizzarlo e chi dovrà finanziarlo. Dobbiamo valutarne, e convalidarne, tutti gli aspetti. Per questo compito ci viene in aiuto la fenomenologia, che è lo studio dell’esperienza umana e di come percepiamo e interpretiamo la realtà. Invece di concentrarsi su ciò che esiste indipendentemente da noi, la fenomenologia si focalizza su come viviamo, sperimentiamo e comprendiamo le cose nel nostro mondo, su come vediamo e sentiamo le cose. È come un’analisi dettagliata dei fenomeni così come appaiono alla nostra coscienza. Ecco perché è fondamentale il contributo di tutti, perché ognuno ha la propria percezione della realtà che lo circonda; e noi, in qualità di software architect, le dobbiamo considerare tutte.
Bello, sì… ma io volevo fare il Software Architect
Tutto molto bello e interessante, ma perché è importante modellare correttamente? Vediamo di analizzare, nel quotidiano di un architetto software, l’impatto di questo approccio, partendo da una considerazione fra coupling & cohesion.
Due componenti sono tanto più integrati fra loro, ossia la loro forza di integrazione è molto più forte, quanta più conoscenza si scambiano. Viceversa, sono tanto più distanti fra loro quanta meno conoscenza hanno uno dell’altro.
In pratica, nel primo caso, se ne modifico uno, quasi certamente devo modificare anche l’altro; viceversa, nel secondo caso, potrei non aver bisogno di modificare entrambi i componenti per apportare modifiche a uno di essi. È sbagliato, quindi, avere troppo accoppiamento? “Dipende”, è la classica risposta del consulente. Ma riassumiamo le possibili combinazioni in una tabella e vediamo di capire quando scegliere la coesione, e quando scegliere il disaccoppiamento.
All’interno di un determinato Bounded Context, ossia dentro al modello, mi aspetto una forte coesione fra i componenti che lo compongono, uno scambio intenso di informazioni e conoscenza. Non ho paura di questo forte accoppiamento: dopotutto, se devo modificare il comportamento di un Bounded Context, accetto di modificare i componenti al suo interno, perché so che l’ho strutturato correttamente, e quindi è isolato dagli altri.
Viceversa, se i due componenti appartengono a due Bounded Context diversi, allora, fra i due, ci deve essere accoppiamento basso o nullo, perché non voglio doverli modificare entrambi in caso di modifica ad uno solo di essi.
Bounded Context e Microservices
Questo chiarimento è fondamentale perché spesso associamo il pattern del Bounded Context ai microservizi, parlando indifferentemente dell’uno o dell’altro come se fossero la stessa cosa, ma non lo sono affatto.
I confini del Bounded Context sono costruiti attorno allo scopo del modello, quindi, in questo caso, ricerchiamo un’alta coesione fra i suoi componenti. I confini di un microservizio, contrariamente, sono costruiti attorno alla dimensione dell’immagine dell’artifact da distribuire; per questo motivo cerchiamo una grande distanza dagli altri microservizi, o se preferite, un bassissimo accoppiamento. Bounded Context e microservizi hanno forze trainanti diverse fra loro: ciò che mantiene sana la nostra codebase è la chiarezza dei confini semantici fra i Bounded Context; che si tratti di un monolite modulare o di un microservizio poco importa. La separazione fisica la dobbiamo fare solo se e quando ne vale la pena. Spesso è inutile partire subito con i microservizi, aggiungendo complessità accidentale al progetto; manteniamo la semplicità e vediamo cosa succede, vediamo cosa impariamo a mano a mano che procediamo con l’esplorazione del dominio su cui stiamo lavorando.
Il rischio più grosso in cui possiamo cadere, come software architect, è quello di prendere decisioni importanti nel momento in cui la nostra conoscenza del dominio è bassa, proprio all’inizio del lavoro. Dobbiamo prima ridurre il debito tecnico, e poi decidere. Parafrasando Socrate “Ciò che devo imparare lo imparo facendolo, conscio che l’unica cosa che so è di non sapere”.
Lo stesso Dan North nel suo ormai famoso articolo Introducing Deliberate Discovery [1] sostiene questo concetto, aggiungendo che, all’inizio di ogni progetto dovremmo avere l’umiltà di porci almeno al secondo livello di ignoranza [2] ossia “So di non sapere qualcosa”. Troppo spesso ci troviamo forzati a proporre soluzioni in tempi stretti, ma una buona architettura richiede tempo, richiede conoscenza, altrimenti il rischio di sbagliare, di per sé comunque altissimo, è quasi certo. Come dire: se pensi che una buona architettura sia costosa… provane una mediocre e poi fai i conti…
Conclusione
Il ruolo del Software Architect va ben oltre la definizione di strutture e pattern tecnici: è un equilibrio costante tra logica e adattabilità, tra rigore e flessibilità. Non si tratta solo di scrivere codice o progettare soluzioni ottimali, ma di creare un linguaggio comune che unisca tecnologia e business, team di sviluppo e stakeholder.
Un buon Software Architect non cerca risposte definitive; piuttosto, fornisce opzioni, modelli che si evolvano con la realtà del contesto. Ogni decisione architetturale deve essere presa con consapevolezza, sapendo che il cambiamento è inevitabile e che la vera sfida sta nella capacità di apprendere, adattarsi e guidare il team attraverso le complessità del dominio.
Alla fine, il valore di un Software Architect non si misura solo nella qualità dell’architettura realizzata, ma nella capacità di rendere il team più forte, più coeso e più capace di affrontare le sfide del futuro.
Riferimenti
[1] Dan Nort, Introducing Deliberate Discovery. 2010
https://dannorth.net/introducing-deliberate-discovery/
[2] I 5 livelli di ignoranza
https://wiki.c2.com/?OrdersOfIgnorance
Sono fondamentalmente un eterno curioso. Mi definisco da sempre uno sviluppatore backend, ma non disdegno curiosare anche dall'altro lato del codice. Mi piace pensare che "scrivere" software sia principalmente risolvere problemi di business e fornire valore al cliente, e in questo trovo che i pattern del DDD siano un grande aiuto. Lavoro come Software Engineer presso intré, un'azienda che sposa questa ideologia; da buon introverso trovo difficoltoso uscire allo scoperto, ma mi piace uscire dalla mia comfort-zone per condividere con gli altri le cose che ho imparato, per poter trovare ogni volta i giusti stimoli a continuare a migliorare.
Mi piace frequentare il mondo delle community, contribuendo, quando posso, con proposte attive. Sono co-founder della community DDD Open e Polenta e Deploy, e membro attivo di altre community come Blazor Developer Italiani.