Salta al contenuto principale

Buone pratiche

Inizia con un monolite modulare; estrai servizi solo su evidenza

Un monolite modulare offre confini interni netti senza il costo di rete, consistenza e operatività della distribuzione, e mantiene economico il refactoring dei confini mentre stai ancora imparando il dominio. Estrai un servizio solo quando una forza concreta lo richiede - scalabilità indipendente, cadenza di deployment indipendente o autonomia del team.

  • Fai: mantieni moduli con interfacce esplicite e nessuno stato interno condiviso, così che un modulo possa poi essere promosso a servizio.
  • Non fare: suddividere in microservizi al primo giorno "per essere scalabili" quando un team e un carico solo sarebbero molto più semplici come monolite.

Specifica gli attributi di qualità come scenari concreti e verificabili

L'architettura esiste per consegnare proprietà non funzionali, e non puoi progettare né verificare un obiettivo vago. Cattura ciascuna come uno scenario con uno stimolo, un ambiente, una risposta attesa e una misura della risposta misurabile.

  • Fai: scrivi "sotto carico di picco, una richiesta di checkout ritorna entro 300 ms al p99".
  • Non fare: scrivere "il sistema dovrebbe essere veloce e scalabile" lasciandolo non testato e senza un responsabile.

Fai puntare le dipendenze verso l'interno, verso il dominio

Applica la regola delle dipendenze condivisa da architettura esagonale, onion e clean: le regole di business non devono dipendere da framework, UI o persistenza. Definisci le interfacce di cui il dominio ha bisogno e implementale in adattatori esterni.

  • Fai: dichiara una porta OrderRepository nel dominio e un adattatore JPA nell'infrastruttura.
  • Non fare: annotare un'entità di dominio con annotazioni di persistenza, saldando la logica di business a uno specifico ORM.

Traccia i confini attorno alle capability di business, non ai livelli tecnici

I bounded context allineati alle capability di business producono alta coesione e accoppiamento lasco; i confini tracciati attorno ai livelli tecnici (un "servizio database", un "servizio logica") massimizzano il traffico chiacchierone tra i confini. Un confine è giusto quando la maggior parte dei cambiamenti resta su un solo lato.

  • Fai: possiedi "Ordini" e "Fatturazione" come contesti separati con i propri dati.
  • Non fare: creare un "servizio entità" condiviso che ogni altro servizio deve chiamare per fare qualsiasi cosa.

Registra le decisioni significative come ADR

La conoscenza architetturale evapora quando le persone che la detengono se ne vanno. Un Architecture Decision Record cattura il contesto, le opzioni, la scelta e le conseguenze come documento immutabile e versionato accanto al codice.

  • Fai: aggiungi un ADR quando scegli un modello di consistenza, uno stile di messaggistica o un confine.
  • Non fare: affidarti a un diagramma o a un thread su una chat per spiegare perché è stata presa una decisione portante.

Imponi le regole architetturali con le fitness function

I diagrammi divergono dalla realtà perché nulla impedisce a uno sviluppatore di violarli. Codifica le regole - direzione di stratificazione, dipendenze ammesse, budget di latenza - come controlli automatici che fanno fallire la build.

  • Fai: aggiungi un test ArchUnit che asserisce che domain non importa mai web o persistence.
  • Non fare: affidarti alla sola convenzione in code review per preservare la direzione delle dipendenze negli anni.

Preferisci eventi asincroni per "notificare" e chiamate sincrone per "chiedere"

Le chiamate sincrone accoppiano la disponibilità e creano catene di latenza; gli eventi disaccoppiano nel tempo e migliorano la resilienza ma aggiungono consistenza eventuale. Abbina il meccanismo all'intento.

  • Fai: emetti un evento OrderPlaced che Fatturazione e Spedizione consumano in modo indipendente.
  • Non fare: far chiamare in modo sincrono a Ordini i servizi Fatturazione, Spedizione e Notifiche in un'unica richiesta, così che il guasto di uno qualunque faccia fallire l'ordine.

Progetta ogni interazione remota perché possa fallire

In un sistema distribuito la rete si partizionerà, le dipendenze rallenteranno e i messaggi verranno riconsegnati. Tratta il guasto come il caso normale, non come un'eccezione.

  • Fai: metti un timeout, un retry-con-backoff e un circuit breaker su ogni chiamata remota, e rendi l'operazione idempotente affinché i retry siano sicuri.
  • Non fare: effettuare una chiamata sincrona illimitata presumendo che la dipendenza sia sempre veloce e disponibile.

Usa le saga, non il commit distribuito a due fasi, tra i servizi

Il commit a due fasi si blocca in caso di guasto del coordinatore e non scala tra servizi autonomi. Modella le transazioni di business cross-servizio come saga: una catena di transazioni locali con azioni compensative.

  • Fai: implementa una saga dell'ordine dove un pagamento fallito attiva un passo compensativo "rilascia prenotazione".
  • Non fare: avvolgere chiamate a tre servizi in un'unica transazione distribuita aspettandoti l'atomicità.

Mantieni un solo confine di consistenza per aggregato

La consistenza forte è economica dentro un singolo aggregato o transazione di database e costosa tra di essi. Tieni gli invarianti che devono valere istantaneamente dentro un aggregato, e accetta la consistenza eventuale tra aggregati e servizi.

  • Fai: imponi "il totale di un ordine è uguale alla somma delle sue righe" dentro l'aggregato Ordine.
  • Non fare: spargere un singolo invariante tra due servizi cercando di mantenerlo fortemente consistente con chiamate sincrone chiacchierone.

Tieni l'API gateway sottile e privo di logica di business

Un gateway è il posto giusto per routing, autenticazione, rate limiting e TLS, ma la logica di business al suo interno diventa un collo di bottiglia condiviso attraverso cui ogni team deve coordinarsi. Spingi il comportamento nel servizio proprietario.

  • Fai: termina il TLS, autentica e applica il rate limiting al gateway, poi instrada.
  • Non fare: implementare le regole di pricing degli ordini nel gateway perché è "in mezzo".

Integra l'osservabilità fin dall'inizio

Non puoi gestire né evolvere ciò che non puoi vedere, e l'osservabilità è molto più difficile da aggiungere a posteriori. Emetti log strutturati, metriche legate ai tuoi SLI e tracce distribuite con un correlation ID propagato.

  • Fai: definisci gli SLO, strumenta gli SLI corrispondenti e traccia le richieste end-to-end tra i servizi.
  • Non fare: rilasciare un sistema distribuito la cui unica diagnostica sono log non strutturati su singoli host.

Sostituisci i sistemi legacy in modo incrementale con lo Strangler Fig

Le riscritture "big bang" sono ad alto rischio e falliscono spesso. Instrada le funzionalità verso il nuovo sistema, funzionalità per funzionalità, dietro una facciata finché il vecchio sistema può essere ritirato.

  • Fai: intercetta il traffico al bordo e migra una capability alla volta, mantenendo entrambi in esecuzione.
  • Non fare: congelare il sistema legacy, ricostruire tutto in parallelo e tentare un'unica migrazione.

Rendi l'architettura evolutiva, decidendo a monte le cose costose

Decidi solo ciò che è costoso da cambiare in seguito - modello di consistenza, confini principali, stile di integrazione - e rinvia le decisioni economiche all'ultimo momento responsabile. Proteggi i pochi vincoli rigidi con fitness function e lascia che il resto emerga tramite refactoring.

  • Fai: fissa presto il modello di consistenza e i confini dei moduli, e lascia evolvere la struttura dettagliata.
  • Non fare: tentare un big design up front completo basato su requisiti che non comprendi ancora del tutto.