Quando dovrei scegliere i microservizi invece di un monolite?
Scegli i microservizi quando hai un bisogno concreto e attuale di deployment e scaling indipendenti di parti distinte del sistema, team abbastanza grandi da possedere servizi in autonomia e la maturità operativa per gestire un sistema distribuito. La guida dominante è partire da un monolite ben strutturato, idealmente un monolite modulare con confini interni chiari, ed estrarre servizi solo quando una pressione reale lo impone. Suddividere troppo presto trasforma semplici chiamate in-process in chiamate di rete con le loro modalità di fallimento, latenza e consistenza eventuale, e impone l'intera tassa operativa di osservabilità, CI/CD e on-call su molti servizi prima di avere i volumi che la giustifichino.
Come decido dove tracciare i confini dei servizi?
Traccia i confini lungo le capacità di business e i bounded context del Domain-Driven Design, non lungo gli strati tecnici. Un buon confine possiede una fetta coesa del dominio, possiede i propri dati e cambia per un insieme di ragioni di business, così che la maggior parte delle modifiche tocchi un solo servizio. Fai attenzione alle catene di chiamate sincrone chiacchierone e ai frequenti bisogni di dati condivisi attraverso un confine, segnali che la cucitura è nel posto sbagliato. Di solito è più economico tenere i moduli insieme in un servizio finché il confine giusto non diventa chiaro, piuttosto che suddividere presto e dover poi unire o ritagliare di nuovo i servizi.
I servizi devono comunicare in modo sincrono o asincrono?
Usa la richiesta-risposta sincrona (REST o gRPC) quando il chiamante ha davvero bisogno di una risposta immediata per procedere, e mantieni corte tali catene per evitare di accoppiare disponibilità e latenza. Preferisci il messaging o gli eventi asincroni per i flussi tra servizi che tollerano la consistenza eventuale, perché disaccoppiano i servizi nel tempo, assorbono i picchi di carico e impediscono che l'interruzione di un servizio blocchi un altro. Un pattern sano comune è la lettura sincrona al bordo con la propagazione asincrona ed event-driven dei cambiamenti di stato dietro le quinte. Qualunque cosa tu scelga, progetta per il fallimento: timeout e retry per le chiamate sincrone, consumatori idempotenti e dead letter queue per quelle asincrone.
Come posso eseguire una transazione che attraversa più servizi?
In generale non puoi e non dovresti usare una transazione ACID distribuita tra servizi, perché il two-phase commit si blocca in caso di fallimento e scala male, e perché ogni servizio possiede il proprio database. Usa invece il pattern saga: modella la transazione di business come una sequenza di transazioni locali, ciascuna in un servizio, e definisci un'azione compensativa per annullare ogni passo completato se un passo successivo fallisce. Le saga possono essere coreografate tramite eventi o orchestrate da un coordinatore centrale, e barattano l'atomicità stretta con consistenza eventuale più compensazione esplicita. Rendi idempotente ogni passo così che i retry siano sicuri.
Cos'è il problema della doppia scrittura e come lo evito?
Il problema della doppia scrittura si verifica quando un servizio deve aggiornare il proprio database e pubblicare un evento per lo stesso cambiamento, e un crash tra le due operazioni li lascia incoerenti: il dato è salvato ma nessun evento viene inviato, o viceversa. Non puoi rendere atomici una scrittura su database e una pubblicazione sul broker tra due sistemi diversi. La soluzione standard è la transactional outbox: scrivi l'evento in una tabella outbox nella stessa transazione locale del cambiamento di business, poi fai sì che un relay separato (spesso tramite change data capture) legga l'outbox e pubblichi sul broker. Questo garantisce che l'evento sia pubblicato se e solo se il cambiamento di stato è stato committato.
Come impedisco che un servizio in errore faccia cadere l'intero sistema?
Progetta per l'isolamento dei guasti. Metti un timeout su ogni chiamata remota così che una dipendenza lenta non possa bloccare i tuoi thread, e usa un circuit breaker per fallire rapidamente quando una dipendenza è chiaramente in difficoltà, dandole spazio per recuperare. Isola le risorse con il pattern bulkhead così che una dipendenza saturata non possa esaurire tutti i tuoi pool di thread o di connessioni. Aggiungi retry limitati con backoff esponenziale e jitter solo per gli errori transitori, e fornisci fallback o risposte degradate dove possibile. Insieme, questi pattern contengono i guasti localmente invece di lasciarli propagare a cascata.
Come dovrei gestire i dati di cui hanno bisogno più servizi?
Rispetta la regola del database-per-service: non lasciare che altri servizi leggano direttamente il tuo database. Per condividere i dati, esponili tramite la tua API per i bisogni sincroni, oppure pubblica eventi di dominio che i servizi interessati consumano per costruire i propri read model o cache locali. Questa duplicazione è intenzionale ed è il prezzo dell'accoppiamento lasco e della distribuibilità indipendente. Mantieni le copie duplicate eventualmente consistenti tramite eventi e tratta il servizio proprietario come l'unica fonte di verità.
Ho bisogno di Kubernetes per eseguire i microservizi?
No. I microservizi sono uno stile architetturale, non una piattaforma specifica, e puoi eseguirli su semplici macchine virtuali, su un servizio container gestito o su una platform-as-a-service. Kubernetes è popolare perché automatizza scheduling, scaling, self-healing, aggiornamenti progressivi e service discovery, che diventano preziosi al crescere del numero di servizi. Per pochi servizi, Kubernetes può rappresentare più complessità operativa di quanto valga, e una piattaforma più semplice può servire meglio finché non ti serve davvero l'orchestrazione.
Cos'è un API gateway e ne ho sempre bisogno?
Un API gateway è un punto di ingresso unico davanti ai tuoi servizi che gestisce preoccupazioni trasversali come routing, autenticazione, rate limiting, terminazione TLS e talvolta aggregazione delle risposte. Semplifica i client e mantiene ogni servizio concentrato sulla logica di business, ma è anche un componente che devi rendere altamente disponibile e che devi evitare di trasformare in un collo di bottiglia intelligente e gonfio. Non ne hai sempre bisogno, ma è comune una volta che hai diversi servizi esposti esternamente, e un Backend-for-Frontend per esperienza client è una variante utile quando i bisogni dei client differiscono molto.
Come si fa il testing in un'architettura a microservizi?
Segui la piramide dei test adattata alla distribuzione: molti test unitari veloci, uno strato sano di test di integrazione che esercitano un servizio contro infrastruttura reale (con strumenti come Testcontainers) e un piccolo numero di test end-to-end. Poiché i test end-to-end completi tra molti servizi sono lenti e fragili, appoggiati molto al testing dei contratti consumer-driven (ad esempio con Pact) per verificare che i provider soddisfino ancora ciò che i loro consumatori si aspettano, intercettando in CI le modifiche di API che rompono la compatibilità senza un ambiente completo. Testa esplicitamente i percorsi di fallimento, inclusi timeout, retry e consegna del broker, perché è lì che i sistemi distribuiti si rompono.
Come faccio il debug di una richiesta che attraversa molti servizi?
Usa il tracing distribuito. Genera un correlation o trace ID quando una richiesta entra nel sistema e propagalo attraverso ogni chiamata a valle e ogni riga di log, così da poter ricostruire l'intero percorso e i tempi di una singola richiesta logica. Strumenta con OpenTelemetry ed esporta le tracce verso un backend come Jaeger, e assicurati che i tuoi log strutturati includano gli ID di trace e span. Combinato con metriche e log aggregati, questo fornisce l'osservabilità necessaria per individuare quale hop ha introdotto la latenza o l'errore.
Come verziono l'API di un microservizio senza rompere i consumatori?
Evolvi le API in modo retrocompatibile all'interno di una major version: aggiungi nuovi campi opzionali e nuovi endpoint invece di rimuovere o rinominare quelli esistenti o di irrigidire la validazione. Quando una modifica davvero breaking è inevitabile, introduci una nuova major version e fai girare entrambe in parallelo abbastanza a lungo da permettere ai consumatori di migrare, poi deprecala e rimuovi la vecchia secondo un calendario pubblicato. Usa i contratti consumer-driven per verificare in CI di non aver rotto alcun consumatore noto. Anche la lettura tollerante lato consumatore, in cui i consumatori ignorano i campi che non comprendono, riduce le rotture accidentali.
Qual è la differenza tra orchestration e choreography?
Entrambe coordinano un flusso multi-servizio ma in modi opposti. L'orchestration usa un coordinatore centrale che chiama esplicitamente ogni passo e decide cosa succede dopo, il che rende l'intero flusso visibile e facile da modificare in un solo punto ma accoppia i passi all'orchestratore. La choreography fa sì che ogni servizio reagisca a eventi ed emetta i propri, senza un cervello centrale, il che massimizza il disaccoppiamento e l'autonomia ma rende il flusso end-to-end più difficile da vedere e comprendere. Molti sistemi mescolano le due: choreography per reazioni debolmente accoppiate e orchestration per saga complesse che necessitano di un controllo chiaro.
Come metto in sicurezza la comunicazione tra servizi?
Tratta la rete interna come non fidata e adotta principi zero-trust: autentica e cifra il traffico servizio-servizio, comunemente con mutual TLS così che ciascun lato verifichi il certificato dell'altro. Autorizza le chiamate in base all'identità del servizio chiamante o dell'utente finale, propagando l'identità tramite token firmati come i JWT invece di fidarti della posizione di rete. Un service mesh può fornire mTLS, identità e policy in modo trasparente senza modificare il codice applicativo. Tieni i segreti fuori dal codice e dalle immagini, conservali in un secret manager e ruotali.
Quali sono gli anti-pattern comuni dei microservizi da evitare?
Il più dannoso è la decomposizione prematura: suddividere in molti servizi prima che esistano i confini o la maturità operativa. Strettamente collegato è il monolite distribuito, in cui i servizi sono nominalmente separati ma così strettamente accoppiati (database condiviso, deploy sincroni in lock-step, catene chiacchierone) da ottenere il dolore dei sistemi distribuiti senza i benefici dell'autonomia. Altri errori frequenti sono un database condiviso tra servizi, mettere la logica di business in un bus middleware intelligente, ignorare la gestione dei fallimenti così che i guasti si propaghino a cascata, e costruire servizi così a grana fine che l'overhead operativo supera il loro valore. La cura è partire a grana grossa, tracciare i confini attorno alle capacità di business e suddividere solo sotto pressione reale.
Quanto piccolo dovrebbe essere un microservizio?
Non esiste una regola sul numero di righe o sulla singola capacità che funzioni universalmente; l'euristica utile è che un servizio dovrebbe essere distribuibile in modo indipendente, possedere i propri dati, incapsulare una capacità di business coesa ed essere possedibile da un solo team. Se un servizio è così piccolo da non poter fare nulla di utile senza molte chiamate sincrone ai vicini, è troppo piccolo e hai creato chiacchiericcio e accoppiamento. Preferisci confini che minimizzano le chiamate tra servizi e lasciano evolvere ciascun servizio col proprio ritmo, rispetto a confini che si limitano a massimizzare il numero di servizi.