Vai al contenuto principale
Categorie articolo: Code

DDD, microservizi e architetture evolutive: il ruolo del Software Architect

23 Giugno 2025 - 10 minuti di lettura

Nella penultima tappa del mio personale trattato sul mondo del DDD, ho fatto chiarezza sulle differenze tra Event Sourcing ed Event Streaming.

In questo mio ultimo articolo, che come gli altri prende spunto da una mia pubblicazione sulla rivista web MokaByte, curerò una digressione sulla figura del Software Architect.

Cosa vuoi fare da grande?

Volevo diventare un Software Architect. Come spesso accade quando si aspira a un ruolo, ho iniziato documentandomi per capire quali fossero le reali responsabilità di questa figura. La domanda che mi sono posto è stata semplice: “Qual è il compito di un Software Architect?”

All’inizio, come molti, pensavo che il focus fosse progettare soluzioni architetturali adeguate. Soluzioni che bilanciassero semplicità e complessità per supportare i progetti di chi si affidava al mio team. Con il tempo, e grazie al confronto con le persone giuste, ho capito che questa è solo una parte del lavoro.

C’è un’altra domanda che, anche in altri articoli di questa serie, ho già esplorato: “Perché sviluppiamo software?”. È una domanda fondamentale, che chi lavora nel settore dovrebbe porsi. Sviluppiamo software per risolvere problemi di business. Il nostro lavoro non si limita a scrivere codice funzionante. Per questo tipo di esercizio ci sono i kata: utili per affinare le tecniche e sperimentare, ma non risolvono problemi concreti.

Il nostro vero compito è progettare sistemi che funzionino e che risolvano problemi.

Mettere insieme ordine e disordine

Ed è proprio in questo punto che emergono le vere complessità. Nella realtà, infatti, ci troviamo a gestire due categorie di sistemi: quelli ordinati e quelli disordinati (come illustrato nella figura seguente).

Sistemi ordinati e sistemi disordinati

Il software, come si nota dall’immagine, rientra nei sistemi ordinati. È governato da regole matematiche e ingegneristiche, quindi risulta rigoroso e prevedibile. Il business, al contrario, è un sistema disordinato, esattamente come gli esseri umani che lo animano.

Il compito di un Software Architect è quindi progettare un sistema ordinato capace di risolvere problemi che nascono in un contesto disordinato. Avete presente il gioco in cui bisogna inserire forme diverse nei rispettivi spazi? Provate a incastrare un cubo nello spazio riservato a un cerchio… Ecco, questa è la sfida quotidiana dell’architetto del software.

Per affrontare problemi di business, serve parlare il linguaggio del business. O, meglio ancora, è necessario costruire un linguaggio e un modello condivisi, comprensibili sia dal team tecnico che dagli stakeholder. Il Software Architect deve quindi saper connettere linguaggi differenti, mediando tra competenze eterogenee e favorendo la collaborazione all’interno del team.

Parafrasando la metafora di Gregor Hohpe, il valore di un architect si misura in base al numero di piani di un grattacielo che riesce a mettere in comunicazione. Il suo contributo consiste nell’aiutare tutte le persone coinvolte a trovare un linguaggio comune e, di conseguenza, un modello condiviso su cui costruire il prodotto finale.

Linguaggi e modelli

Su questi aspetti, E. Evans non aveva dubbi. Lo abbiamo visto nel dettaglio: Ubiquitous Language e Bounded Context sono due pattern fondamentali del Domain-Driven Design. Entrambi si basano su un uso consapevole del linguaggio e sulla modellazione della realtà.

Il linguaggio è il primo elemento da costruire. Serve per definire chiaramente di cosa si sta parlando, eliminando ogni ambiguità o conoscenza implicita. Tutto deve essere esplicito e condiviso da tutto il team. Succede anche nelle conversazioni informali: se ci si unisce a un gruppo di amici a chiacchierare, la prima domanda che nasce spontanea è “Di cosa state parlando?”. Senza questa richiesta di identificazione del contesto, si rischia di fraintendere il tema. A volte ne nasce una risata, altre volte si risponde fuori luogo a una domanda. Senza contesto, non si può costruire un modello mentale affidabile.

Nel software si parla spesso di modelli. Anche Evans ne discute ampiamente nel suo celebre “blue book”. Ma cosa intendiamo davvero per modello? L’architettura software si fonda su modelli. La programmazione a oggetti ne ha fatto un pilastro, puntando a modellare la realtà in oggetti ideali. Tuttavia, questi modelli spesso falliscono nel loro obiettivo principale: rappresentare fedelmente la realtà. Ma perché?

I filosofi non erano programmatori, però…

Se torniamo indietro nel tempo, ai periodi delle grandi discussioni della filosofia greca, è evidente che il dibattito non verteva sulla programmazione funzionale o sull’Object Oriented Programming. Eppure, nelle posizioni di due pensatori con visioni opposte della realtà, possiamo ritrovare una contrapposizione simile a quella fra i due paradigmi.

Il primo è Eraclito (535–475 a.C.), secondo cui “Nessun uomo entra due volte nello stesso fiume, perché non è lo stesso fiume e lui non è lo stesso uomo”. La realtà, per Eraclito, è un flusso continuo e mutevole.

Il secondo è Platone (428–348 a.C.), che sosteneva: “Il mondo sensibile che ci circonda è solo una copia imperfetta delle Forme, che rappresentano la vera realtà”. Una visione diametralmente opposta.

Non va dimenticato che il pensiero di Platone, insieme a quello di Aristotele, ha costituito per secoli la base della cultura occidentale. Molti aspetti del nostro modo di interpretare il mondo derivano direttamente o indirettamente dalle sue teorie. Quando utilizziamo il paradigma a oggetti dell’OOP, siamo inconsapevolmente figli di Platone.

Il Domain-Driven Design, pur non rinnegando l’OOP, si avvicina di più alla visione di Eraclito. Riconosce l’importanza del modello, ma lo considera soggetto a evoluzioni continue, immerso in un flusso in divenire. Una prospettiva che è anche alla base dei principi dell’Agile.

Il problema non risiede nel modello in sé, ma nell’idea che il modello sia corretto e valido per sempre. È questa la vera trappola. Quando scopriamo che ciò che sappiamo — o che crediamo di sapere — è distante dalla realtà, allora si manifesta il cigno nero, per usare l’efficace metafora di Nassim Taleb.

Lo scopo di un modello

A cosa serve un modello? Un esempio pratico aiuta a chiarirlo. Sono un appassionato di montagna in ogni stagione. Quando desidero raggiungere un rifugio che non conosco, cerco innanzitutto informazioni sul sentiero e sull’altimetria. Una carta topografica che descriva gli elementi naturali della zona — come fiumi, laghi, passi — e le infrastrutture realizzate dall’uomo, come i sentieri, rappresenta il modello ideale per affrontare l’escursione.

Se invece conosco già il percorso ma intendo ripetere una via di arrampicata che non ho mai affrontato, ho bisogno di un modello diverso. In quel caso, mi serve uno schizzo tecnico della via, con le lunghezze dei tiri, le protezioni presenti e il livello di difficoltà di ciascun tratto. Una fotografia della parete, per quanto suggestiva, non mi sarebbe utile: non mi fornirebbe le informazioni operative necessarie.

Un appassionato di fotografia potrebbe osservare che, scegliendo mappe tecniche, mi sto privando della bellezza del paesaggio. E avrebbe perfettamente ragione: ma stiamo perseguendo obiettivi diversi.

Secondo Rebecca Wirfs-Brock, un modello è una rappresentazione semplificata di un fenomeno o di un oggetto, che enfatizza volutamente certi aspetti e ne trascura altri. Si tratta di un’astrazione costruita con uno scopo preciso. E un’astrazione può essere utile solo se il contesto è chiaro e condiviso da tutti gli attori coinvolti.

Lo schizzo della via di arrampicata è utile solo se tutti i partecipanti mirano a completare quella specifica via. Se invece l’obiettivo è una semplice passeggiata, quel modello diventa inadatto.

Ulteriori considerazioni di ordine “filosofico”

Tornando alla progettazione di sistemi orientati alla risoluzione di problemi di business, il linguaggio assume un ruolo centrale nella comprensione condivisa del problema tra tutti i membri del Team. Riprendendo l’esempio precedente, non possiamo mescolare escursionisti e scalatori e pretendere che utilizzino la stessa mappa (modello) per raggiungere obiettivi diversi. È indispensabile definire con chiarezza i termini del problema e costruire un modello coerente con il fine da perseguire.

Come affermava Wittgenstein — ben prima di Evans — “I limiti del mio linguaggio significano i limiti del mio mondo”. Allo stesso modo, i limiti dell’Ubiquitous Language definiscono i confini — i bound — del nostro Bounded Context.

Il linguaggio non è soltanto uno strumento descrittivo, ma anche costruttivo: plasma il modo in cui i team interpretano, modellano e affrontano il dominio o una sua parte.

Ontologia

Nel contesto del Domain-Driven Design, un modello può essere interpretato come una rappresentazione ontologica parziale, progettata per descrivere aspetti significativi di una realtà complessa.

L’ontologia è quella disciplina filosofica che si occupa di analizzare ciò che esiste e di definire come possiamo classificare e comprendere gli elementi che ci circondano. In altre parole, offre un sistema per organizzare la realtà, identificando categorie di oggetti e relazioni tra essi.

Questo approccio è perfettamente in linea con ciò che realizziamo applicando i pattern Bounded Context e Context Mapping nei progetti basati su DDD: costruire una “mappa” coerente e condivisa della realtà del dominio, evidenziando entità, limiti e connessioni rilevanti.

Teleologia

Un aspetto fondamentale nella modellazione di un problema è comprendere il suo scopo o il motivo per cui esiste. Come recita la “seconda Legge dell’Architettura del Software”, il perché è più importante del come.

La teleologia, branca della filosofia, si occupa proprio dello studio delle finalità o degli scopi di un processo o di un’entità. Essa indaga il perché un determinato evento o fenomeno si verifica, cercando di comprendere il fine o l’obiettivo che esso intende raggiungere.

Diventa così evidente perché, durante l’analisi del dominio su cui si andrà a lavorare, sia essenziale coinvolgere tutti i soggetti interessati dal progetto, assicurando una visione completa e condivisa degli obiettivi.

Fenomenologia

Nell’analisi del dominio relativa al prodotto che intendiamo sviluppare, è fondamentale coinvolgere non solo chi ha ideato la soluzione, ma anche chi dovrà utilizzarla, commercializzarla, realizzarla e finanziarla. È necessario valutare e convalidare tutti questi aspetti.

In questo compito ci supporta la fenomenologia, lo studio dell’esperienza umana e del modo in cui percepiamo e interpretiamo la realtà. Piuttosto che focalizzarsi su ciò che esiste indipendentemente da noi, la fenomenologia indaga come viviamo, sperimentiamo e comprendiamo il mondo che ci circonda, ossia come vediamo e percepiamo le cose.

Si tratta di un’analisi approfondita dei fenomeni così come si manifestano alla nostra coscienza. Per questo motivo, è cruciale raccogliere il contributo di tutti gli attori coinvolti, poiché ciascuno possiede una propria percezione della realtà; il compito del software architect è tenerne conto e integrarle nel processo di modellazione.

Bello, sì… ma io volevo fare il Software Architect

Tutto molto interessante, ma perché è davvero importante modellare correttamente? Analizziamo l’impatto di questo approccio nella pratica quotidiana di un software architect, partendo da una riflessione sui concetti di coupling e cohesion.

La forza di integrazione di due componenti.

Due componenti risultano tanto più integrati tra loro — ossia con un accoppiamento più forte — quanto maggiore è la conoscenza che si scambiano. Al contrario, saranno più indipendenti quanto minore è la conoscenza reciproca.

In pratica, nel primo caso, una modifica a un componente richiederà quasi certamente una modifica anche all’altro; nel secondo caso, invece, potrebbe non essere necessario intervenire su entrambi per apportare cambiamenti a uno solo.

È quindi sempre negativo un alto livello di accoppiamento? La risposta tipica di un consulente è: “Dipende”.

Riassumiamo le possibili combinazioni in una tabella, per comprendere meglio quando è preferibile puntare sulla coesione e quando invece è opportuno favorire il disaccoppiamento.

Accoppiamento, disaccoppiamento, alta o bassa coesione.

All’interno di un determinato Bounded Context, cioè all’interno del modello, mi aspetto una forte coesione tra i componenti che lo costituiscono, con uno scambio intenso di informazioni e conoscenza. Non temo questo forte accoppiamento: se devo modificare il comportamento di un Bounded Context, sono consapevole che sarà necessario intervenire anche sui suoi componenti interni, poiché il contesto è stato progettato in modo corretto e rimane isolato dagli altri.

Al contrario, se due componenti appartengono a due Bounded Context distinti, tra di essi deve esserci un accoppiamento basso o nullo, perché non desidero dover modificare entrambi in caso di cambiamento su uno solo.

Bounded Context e Microservice

È importante chiarire che il pattern del Bounded Context non coincide con i microservizi, anche se spesso vengono confusi o usati come sinonimi.
I confini del Bounded Context sono definiti in base allo scopo del modello. Qui si cerca un’alta coesione tra i componenti. Al contrario, i confini di un microservizio sono stabiliti dalla dimensione dell’artifact da distribuire. Per questo motivo, si punta a un accoppiamento molto basso tra i microservizi.

Le motivazioni che guidano Bounded Context e microservizi sono differenti. La salute della nostra codebase dipende dalla chiarezza dei confini semantici tra i Bounded Context, indipendentemente dal fatto che si tratti di un monolite modulare o di microservizi. La separazione fisica va fatta solo se davvero necessaria. Spesso partire subito con i microservizi genera complessità accidentale e inutile. Meglio mantenere la semplicità e imparare progressivamente durante l’esplorazione del dominio.

Il rischio più grande per un software architect è prendere decisioni importanti quando la conoscenza del dominio è ancora limitata, tipicamente all’inizio del progetto. Prima di decidere, è fondamentale ridurre il debito tecnico. Parafrasando Socrate: “Ciò che devo imparare, lo imparo facendo, consapevole che l’unica cosa che so è di non sapere”.

Anche Dan North, nel suo celebre articolo “Introducing Deliberate Discovery“, ribadisce questo concetto. All’inizio di ogni progetto, dobbiamo avere l’umiltà di riconoscere almeno il secondo livello di ignoranza, cioè “So di non sapere qualcosa”.

Spesso siamo costretti a proporre soluzioni in tempi stretti, ma una buona architettura richiede tempo e conoscenza. In caso contrario, il rischio di errore, già elevato di suo, diventa quasi certo. Come si dice: se pensi che una buona architettura sia costosa… prova una mediocre e poi fai i conti.

Conclusione

Il ruolo del Software Architect va oltre la definizione di strutture e pattern tecnici: richiede un equilibrio costante tra logica e adattabilità, tra rigore e flessibilità.
Non si tratta solo di progettare soluzioni ottimali o scrivere codice. Significa creare un linguaggio comune che colleghi tecnologia e business, team di sviluppo e stakeholder.

Un buon Software Architect non cerca risposte assolute. Offre invece modelli capaci di evolversi con il contesto. Ogni scelta architetturale deve essere consapevole, con la piena accettazione che il cambiamento è inevitabile.

La vera sfida è saper apprendere, adattarsi e guidare il team attraverso la complessità del dominio.

In definitiva, il valore di un Software Architect si misura non solo nella qualità dell’architettura, ma nella sua capacità di rendere il team più forte, coeso e pronto ad affrontare le sfide future.

Articolo scritto da