Quando un'applicazione cresce, prima o poi compare una proposta apparentemente inevitabile: dividiamola in microservizi. A volte è la scelta corretta. Altre volte introduce distribuzione, rete e complessità operativa senza risolvere un problema reale.
1. Il componente deve essere distribuito in modo indipendente?
Il primo criterio non è la dimensione del codice, ma il bisogno di deployment indipendente. Se due moduli vengono sempre rilasciati insieme, dipendono dalle stesse modifiche e richiedono coordinamento tra gli stessi sviluppatori, separarli in due servizi aggiunge un confine di rete senza creare vera autonomia.
Un servizio indipendente dovrebbe poter essere modificato, testato e distribuito senza obbligare a rilasciare contemporaneamente il resto del sistema. Questo richiede contratti stabili, versionamento e compatibilità tra versioni.
2. Esiste un confine dei dati realmente autonomo?
La separazione del codice è relativamente semplice. La separazione dei dati è molto più difficile. Un microservizio che deve interrogare continuamente le tabelle di un altro servizio, condividere lo stesso schema SQL o partecipare alle stesse transazioni non è davvero indipendente.
Prima dell'estrazione conviene chiedersi chi possiede i dati, quali invarianti devono rimanere locali e quali informazioni possono essere scambiate tramite API o eventi. Se il confine dei dati non è chiaro, il problema architetturale non scompare spostando classi in un altro processo.
3. Ha esigenze di scalabilità differenti?
Uno dei vantaggi dei microservizi è poter scalare solo la parte che ne ha bisogno. Ma questa giustificazione è valida soltanto quando il carico è realmente diverso. Separare un modulo che usa le stesse risorse, ha lo stesso profilo di traffico e cresce insieme al resto dell'applicazione raramente produce un beneficio proporzionato.
Prima di introdurre nuovi servizi misurerei CPU, memoria, tempi di risposta, code, throughput e pressione sul database. La scalabilità dovrebbe essere una risposta a dati osservati, non un'ipotesi. Lo stesso principio vale per le performance applicative: prima di cambiare architettura conviene individuare il vero collo di bottiglia.
4. Un team può possederlo davvero in autonomia?
L'architettura organizzativa conta. Se ogni modifica al servizio richiede comunque l'approvazione o il lavoro di più team, il confine tecnico non crea indipendenza. Al contrario, quando un team può possedere funzionalità, dati, deployment e osservabilità end-to-end, il servizio può diventare un confine utile anche dal punto di vista operativo.
5. Siamo disposti a pagare la platform tax?
Un processo separato porta con sé costi che nel monolite non esistono o sono molto più semplici: service discovery, configurazione distribuita, gestione dei secret, timeout, retry, circuit breaker, tracing distribuito, correlazione dei log, health check, deployment multipli, gestione delle versioni API e diagnosi dei problemi di rete.
Questa è la platform tax. Non significa che i microservizi siano sbagliati; significa che il beneficio dell'indipendenza deve essere sufficientemente importante da giustificare quel costo permanente.
Il rischio del distributed monolith
Il risultato peggiore è spesso un sistema composto da molti servizi che devono essere distribuiti insieme, condividono lo stesso database e si chiamano in modo sincrono lungo catene profonde. Formalmente è distribuito; operativamente resta un monolite, ma con timeout, rete e failure mode aggiuntivi.
Un buon test è semplice: posso cambiare e distribuire questo servizio senza coordinare il rilascio degli altri? Se la risposta è quasi sempre no, l'indipendenza è probabilmente solo nominale.
Il monolite modulare come punto di partenza
Per molti sistemi .NET, un monolite modulare offre un compromesso efficace. I moduli hanno confini espliciti, dipendenze controllate e responsabilità chiare, ma restano nello stesso processo e possono condividere una pipeline di deployment più semplice.
Questo non impedisce un'evoluzione futura. Al contrario, un modulo ben isolato è spesso il candidato migliore da estrarre quando emerge una necessità concreta di deployment, scalabilità o ownership indipendente.
Estrarre gradualmente, non riscrivere tutto
Quando il confine è maturo, l'estrazione può essere incrementale: definire il contratto, ridurre le dipendenze dirette, separare l'accesso ai dati, introdurre un adapter e spostare il traffico progressivamente. È lo stesso principio che uso quando valuto come esporre funzionalità di un sistema legacy .NET tramite API senza riscriverlo: creare un confine controllato prima di sostituire il sistema esistente.
In un'applicazione legacy questo approccio riduce il rischio rispetto a una riscrittura completa. La modernizzazione può partire dall'isolamento dei moduli e dei contratti, come descritto anche nella guida su come modernizzare applicazioni .NET Framework senza fermare il business.
Quando eviterei i microservizi
Se il team è piccolo, il dominio è ancora in evoluzione, i moduli condividono molti dati e i deployment vengono comunque coordinati, distribuire il sistema troppo presto tende ad aumentare il costo di sviluppo e di esercizio. In questi casi preferirei rendere espliciti i confini all'interno del monolite e rimandare l'estrazione finché non emerge un bisogno misurabile.
I microservizi non sono quindi un punto di partenza obbligatorio: sono uno strumento da introdurre quando il vantaggio dell'indipendenza supera il costo della distribuzione.
FAQ
Un monolite modulare può diventare microservizi in seguito?
Sì. Se i moduli hanno responsabilità, dipendenze e accesso ai dati ben delimitati, l'estrazione futura è normalmente più semplice perché il confine logico esiste già prima di diventare un confine di rete.
Quando conviene davvero separare un microservizio?
Quando una parte del sistema ha un bisogno concreto di deployment, scalabilità, ownership o ciclo di evoluzione indipendente e possiede un confine dei dati sufficientemente chiaro.
Usare microservizi rende automaticamente un sistema più scalabile?
No. Permette di scalare componenti separatamente, ma introduce anche overhead operativo. Se tutto il sistema ha lo stesso profilo di carico o il database resta il vero collo di bottiglia, la separazione può non produrre il beneficio atteso.
Conclusione
La domanda utile non è “monolite o microservizi?” in astratto. È: quale problema concreto risolviamo introducendo questo confine distribuito?
Se la risposta riguarda autonomia reale di deployment, dati, scalabilità o team, l'estrazione può avere senso. Se invece l'obiettivo è soltanto rendere il sistema “più moderno”, un monolite modulare ben progettato può essere una base più semplice, economica e affidabile.
In sintesi
Separare il sistema ha senso solo quando il confine crea vera autonomia.
Deployment, dati, scalabilità e ownership devono poter evolvere in modo realmente indipendente. Altrimenti un monolite modulare ben progettato può offrire confini chiari con una complessità operativa molto inferiore.
Guide correlate
Stai valutando come far evolvere un'applicazione .NET esistente?
Posso supportare software house e team di sviluppo nell'analisi dell'architettura, nella modernizzazione di applicazioni legacy e nella definizione di un percorso evolutivo pragmatico, senza introdurre complessità distribuita dove non serve.