Tag: software-architecture, modular-monolith, backend, ddd, clean-architecture, refactoring
Livello: intermedio
TL;DR: Architettura a deployment singolo con separazione logica a moduli indipendenti e confini stretti. Alternativa pragmatica ai microservizi per ridurre complessità operativa.
Singolo deployable unit con partizionamento interno in moduli di dominio ben definiti.
Comunicazione tra moduli consentita solo tramite API pubbliche o contratti espliciti.
Vietato l'accesso diretto ai database o alle tabelle di altri moduli.
Ogni modulo incapsula la propria logica di business e le strutture dati.
Transazioni cross-modulo gestite preferibilmente via domain event asincroni in-process.
Isolamento del codice applicabile tramite package privati, moduli build o tool statici.
Permette di ritardare o evitare la distribuzione di rete dei microservizi.
Mantiene semplicità di debug, testing locale e osservabilità su un unico processo.
Facilita l'estrazione futura di singoli moduli in servizi autonomi se necessario.
Dipendenze cross-modulo esplicitamente direzionali: divieto assoluto di dipendenze cicliche.
Q: Quando preferisci un monolite modulare rispetto ai microservizi?R: Quando il dominio è in evoluzione o il team è ridotto: evita latenze di rete, overhead operativo e complessità distribuita mantenendo il codice disaccoppiato.
Q: Come garantisci che i moduli non violino i confini nel tempo?R: Impongo regole di architettura automatizzate in CI, limito la visibilità dei tipi tramite moduli di build e vieto query DB cross-modulo.
Q: Come gestisci la comunicazione tra moduli diversi?R: Utilizzo interfacce di facciata sincrone o un mediator di eventi in-process, trattando i dati scambiati come contratti immutabili (DTO).
Condividere modelli di entità ORM tra moduli diversi anziché passare contratti DTO.
Consentire foreign key dirette tra tabelle di moduli distinti.
Introdurre dipendenze cicliche tra package di domini differenti.
Fallire nell'automatizzare i controlli di integrità architetturale nelle pipeline CI.
Accoppiare i moduli a una singola transazione database globale e pesante.
// Modulo Orders pubblica interfaccia contrattuale isolata
export interface OrderPlacedEvent {
readonly orderId: string;
readonly customerId: string;
readonly totalAmount: number;
}
// Modulo Notifications ascolta senza dipendere dal dominio interno Orders
export class SendNotificationOnOrderPlaced {
async handle(event: OrderPlacedEvent): Promise<void> {
await sendEmail(event.customerId, `Ordine ${event.orderId} confermato`);
}
}
[ ] Definizione dei confini di contesto (Bounded Context) secondo principi DDD
[ ] Configurazione di tool di arch unit per bloccare violazioni di import in CI
[ ] Strategie di partizionamento schema database per modulo (schema separati)
[x] Vantaggi operativi rispetto a una topologia di rete a microservizi
Domain-Driven Design: Tackling Complexity in the Heart of Software (Eric Evans)
Monolith to Microservices (Sam Newman)