DDD, microservizi e architetture evolutive: problem space vs. solution space
Nel mio precedente articolo ho parlato dell’importanza di alcune architetture e best practice da poter adottare.
Problem space vs. solution space: che distinzione c’è? E come influenza la progettazione software?
Le informazioni qui riportate sono riprese da questo mio secondo articolo che fa parte di una serie, pubblicata nella rivista web MokaByte, dedicata all’esplorazione del DDD e delle architetture software evolutive.
Capire i problemi e/o trovare le soluzioni
Tra le tante frasi attribuite ad Einstein, ce n’è una che mi ha sempre colpito:
Se avessi un’ora per risolvere un problema, spenderei 55 minuti a capire il problema e 5 a trovare una soluzione.
In altre parole, il problem space è l’insieme delle sfide che un software affronta; il solution space, le risposte possibili a ciascuna di esse.
Cosa si intende con Problem space
Gli esseri umani tendono naturalmente a risolvere problemi. Lo affermano Newell e Simon nel libro “Human Problem Solving“, e lo vediamo ogni giorno. Nel lavoro di team, mentre sviluppiamo software, cerchiamo soluzioni rapide. Vogliamo uscire in fretta dal problem space, come se restarci fosse scomodo. Eppure è lì che risiedono i bisogni dei nostri clienti. È lì che possiamo capire davvero quali problemi il software deve risolvere.
Il problem space è la base su cui costruire ogni soluzione. Senza comprenderlo, la soluzione rischia di essere fragile o addirittura sbagliata. In termini matematici, il problem space è un sottoinsieme del solution space: comprendere il primo è essenziale per navigare nel secondo.
Come sviluppatori, è normale voler agire subito. Ma agire troppo presto può portarci lontano dal vero obiettivo. La fretta ci impedisce di esplorare il problema a fondo. E il risultato? Requisiti incompleti e soluzioni parziali. Così iniziano i fallimenti nei progetti software: da un problema non compreso abbastanza, da un comfort cercato troppo presto.
Restiamo nel problem space
Progettare software significa risolvere problemi di business e, come diceva la mia professoressa di matematica, un problema va prima capito, poi risolto.
Offrire una soluzione sbagliata all’inizio ci fa partire svantaggiati e sappiamo che le applicazioni crescono: a volte piano, altre molto rapidamente. Più il tempo passa, più una piccola scelta iniziale può deviare il progetto. Correggere la rotta diventa difficile, se non impossibile.
Come dice Eric Evans, il team cresce nella comprensione del dominio insieme ai Business Expert. Le soluzioni migliorano man mano che il problema diventa chiaro. Ma se partiamo nella direzione sbagliata, anche con maggiore consapevolezza sarà dura riallinearci. Le prime decisioni sono fondamentali. Come evitarlo? Restando nel problem space. Il più a lungo possibile. Einstein diceva che per risolvere un problema serve prima capirlo. Evans lo conferma: il primo modello di dominio sarà sempre sbagliato. Pensiamo per iterazioni. Analisi, ipotesi, feedback. Solo così la soluzione emergerà davvero dal problema.
Il modello esplorativo proposto da Eric Evans.
Il blocco delle soluzioni precoci
Secondo Eric Evans, tentare di risolvere tutto subito porta a quello che chiamiamo big front design: restiamo bloccati nella nostra ignoranza iniziale. Prendiamo decisioni cruciali proprio quando sappiamo meno del problema. È come costruire fondamenta senza conoscere il terreno su cui poggiano.
Dan North ci invita alla cautela: non forziamo una soluzione definitiva finché non conosciamo davvero il dominio. Nel suo articolo “Introducing Deliberate Discovery“, suggerisce di accettare la nostra ignoranza. Solo così possiamo scoprire il problema man mano. Questa “mancanza di consapevolezza” (non sappiamo di non sapere) è il secondo dei cinque livelli di ignoranza nell’ambito dello sviluppo software trattati da Philip Armour nel suo scritto “Five Orders of Ignorance. Viewing software development as knowledge acquisition and ignorance reduction“.
Ed è qui che nasce il rischio. Agire troppo presto, senza sapere abbastanza, ci fa sbagliare strada prima ancora di partire.
Qualche indicazione per non trovare soluzioni frettolose
Se l’ignoranza è l’opposto della conoscenza, come restare nel problem space abbastanza a lungo da colmare la prima e far crescere la seconda?
La tentazione di proporre soluzioni è forte. Ma possiamo imparare a rallentare. Ecco qualche indicazione utile per farlo.
- Mantenere il controllo della discussione. Non serve parlare di più. Serve evitare che la conversazione chiuda troppo presto. Basta un gesto, uno sguardo, per cambiare direzione. Il cliente dà per scontato molto. Tocca a noi far emergere ciò che resta implicito, riportandolo in superficie con domande mirate.
- Acoltare le diverse soluzioni. All’inizio c’è imbarazzo. Poi, pian piano, emergono idee diverse, anche in contrasto. Tutte vanno accolte nello spazio delle soluzioni. Creare un clima aperto aiuta. Ogni punto di vista è un tassello del problema che stiamo cercando di comprendere.
- Tenere nascoste le soluzioni principali. Se moderi l’esplorazione, lascia che le soluzioni emergano da sole. Non forzarle. Non metterle subito in evidenza. Con strumenti come l’EventStorming, guida senza svelare. Lascia che i partecipanti costruiscano, esplorino, scoprano. Se noti soluzioni simili, portale all’attenzione. Chiediti: sono la stessa cosa detta in modi diversi o strade da esplorare separatamente?
Conoscere la situazione del cliente
Restare nello spazio del problema è sicuramente scomodo per entrambe le parti, cliente e fornitore. Il primo si sente un po’ sotto pressione per le innumerevoli domande che gli continuano ad arrivare, il secondo — vale a dire, spesso, noi — non vede l’ora di proporre una soluzione. Prioritario è individuare i confini del problema e iniziare a proporre una soluzione, sulla quale ci si potrà ritrovare per avere feedback e continuare, dopo aver eventualmente aggiustato il tiro. Il nostro dominio sarà sempre in continua evoluzione, se non altro per tutto ciò che appartiene al livello del unknow unknows, ossia le cose che ancora non sappiamo di non sapere, ma che scopriremo a mano a mano che diventeremo sempre più esperti del dominio su cui stiamo lavorando. A questo punto dobbiamo capire come si affronta, dal punto di vista architetturale, una soluzione simile.
Evolutionary architecture
L’architettura, come il software che realizziamo, deve poter evolvere nel tempo. Non possiamo più pensare al software come a un edificio statico. La vecchia metafora dell’architettura edilizia non regge più. Il software non si costruisce una volta sola, per poi lasciarlo lì, immutabile.
Oggi serve flessibilità. Non possiamo rischiare che l’applicazione diventi obsoleta solo perché l’architettura iniziale non regge il passo del cambiamento. Scalabilità, resilienza, elasticità: sono requisiti che arrivano col tempo. E l’architettura deve saperli accogliere, crescere con loro, senza diventare un vincolo.
Pro e contro dei microservizi e dei monoliti
Scegliere i microservizi fin dall’inizio può sembrare lungimirante. Ci aiuta a rispondere meglio ai cambiamenti, ma introduce subito molta complessità.
Con i microservizi arrivano problemi noti:
- decomposizione del database
- workflow distribuiti
- transazioni distribuite
- automazione dei processi
- distribuzione tramite container e orchestratori
Questi aspetti richiedono tempo, strumenti e competenze. E, soprattutto, budget. Non sempre un progetto in fase iniziale può permetterselo.
Il buon vecchio monolite, invece, parte facile.
- Si implementa facilmente
- Si testa con semplicità
- Si distribuisce senza troppi problemi
- Si realizza velocemente
Ogni beneficio ha però un prezzo. I limiti del monolite sono proprio quelli che i microservizi mirano a superare.
E allora? Scegliere un’architettura è un’azione strategica molto importante. Serve capire bene il contesto e decidere come far evolvere il sistema nel tempo.
Architettura modulare
Negli ultimi anni si sta facendo strada un approccio intermedio: l’architettura modulare, a metà strada tra monolite e microservizi. L’obiettivo? Realizzare una soluzione pronta per la produzione, non un semplice Proof of Concept (PoC). La chiave è individuare moduli ben separati dentro il sistema.
Per farlo, possiamo usare gli strategic pattern del Domain-Driven Design: Bounded Context, Context Mapping e Ubiquitous Language. Questi pattern sono spesso associati ai microservizi. Ma qui restano dentro un unico codice, organizzato in moduli, non in servizi separati. Ogni modulo rappresenta un’area funzionale distinta, senza bisogno di separare tutto subito in servizi remoti. È un buon compromesso. Se in seguito serve spostare i confini, farlo in un monolite modulare è più semplice. Non dobbiamo smontare un’intera rete di microservizi. L’architettura modulare ci permette di evolvere gradualmente, mantenendo ordine e flessibilità fin dalle prime fasi di sviluppo.
Conclusioni
Restare nel problem space, lo spazio dei problemi, può sembrare scomodo, ma ci aiuta ad affrontare le soluzioni con più consapevolezza e decisioni migliori.
Abbiamo esplorato vantaggi e limiti di monoliti e microservizi, introducendo l’architettura modulare come possibile via intermedia e più flessibile.
Nel prossimo articolo vedremo cosa intendiamo per “modulo” e quali vantaggi può portare la scelta di una architetturale modulare.