Domain-Driven Design (DDD) nei microservizi

Domain-Driven Design (DDD) nei microservizi

Quando le organizzazioni passano da un’architettura monolitica ai microservizi, si trovano ad affrontare una domanda critica e ad alto rischio: Come tracciamo i confini dei nostri servizi?

In teoria, i microservizi dovrebbero essere unità sciolte e disaccoppiate che possono essere sviluppate, distribuite e scalate in modo indipendente. In pratica, tuttavia, molti team finiscono per costruire un monolite distribuito, un sistema in cui i servizi sono così strettamente associati che un singolo cambiamento aziendale richiede la modifica e l’implementazione di più servizi contemporaneamente, aggravando la latenza di rete e gli ostacoli di distribuzione.

Per evitare questo errore, gli architetti software si rivolgono al Domain-Driven Design (DDD). Introdotta per la prima volta da Eric Evans nel 2003, DDD è una metodologia di sviluppo software che allinea le strutture del codice ai complessi domini aziendali che rappresentano.

In questo articolo esploreremo il modo in cui DDD fornisce i modelli strategici e tattici per la progettazione di microservizi puliti, disaccoppiati e altamente manutenibili.


1. Il progetto strategico: tracciare i confini del servizio

Il DDD è diviso in due fasi principali: Progettazione strategica e Progettazione tattica. La progettazione strategica riguarda la modellazione dei contesti aziendali e la definizione dei confini dei servizi. Fornisce la visione “macro” dell’architettura.

Mappatura dei contesti limitati DDD al diagramma dell'architettura dei microservizi

A. Linguaggio ubiquo

All’interno di un’azienda, reparti diversi usano le stesse parole per significare cose completamente diverse. Ad esempio:

  • Per il team di vendita, un “Utente” è un lead o un potenziale cliente.
  • Per il team di sicurezza, un “Utente” è un insieme di credenziali di accesso.
  • Per il team di spedizione, un “Utente” è l’indirizzo fisico del destinatario.

Cercare di costruire un modello di database singolo e unificato per un “Utente” che soddisfi tutti si traduce in una base di codice enorme e intricata. DDD risolve questo problema stabilendo un linguaggio onnipresente, un vocabolario condiviso definito e utilizzato sia dagli esperti del settore aziendale che dagli sviluppatori all’interno di un confine specifico.

B. Contesti delimitati

Un Contesto Delimitato è il confine esplicito entro il quale si applica un modello di dominio. All’interno del confine, tutti i termini della Lingua Ubiquita hanno un significato unico e inequivocabile.

Invece di un singolo modello “Utente”, definiamo modelli separati all’interno dei rispettivi Contesti Delimitati:

  • Nel Contesto dell’ordine, abbiamo un modello Order contenente le informazioni di contatto del cliente.
  • Nel Contesto di identità, abbiamo un modello Credentials.
  • Nel Contesto di spedizione, abbiamo un modello DeliveryAddress.

La regola d’oro dei microservizi: Un contesto delimitato si associa direttamente a un microservizio.

Suddividendo il sistema in contesti delimitati, garantiamo che i microservizi siano liberamente accoppiati e rappresentino capacità aziendali distinte.

C. Mappatura del contesto: come comunicano i servizi

I contesti delimitati non esistono isolatamente; devono interagire. Una mappa del contesto definisce le relazioni e i meccanismi di traduzione tra i contesti. I modelli chiave includono:

  • Kernel condiviso: due contesti condividono un piccolo sottoinsieme del modello di dominio e del database (solitamente sconsigliato nei microservizi a causa dell’accoppiamento).
  • Cliente-Fornitore: Un contesto (fornitore) deve fornire dati a un altro (cliente). Il fornitore deve coordinare i programmi di rilascio con il cliente.
  • Anti-Corruption Layer (ACL): un livello di traduzione che traduce i dati in ingresso da un sistema esterno nel modello di dominio interno del cliente, impedendo ai modelli esterni di inquinare l’architettura interna.

2. Il progetto tattico: strutturare la base di codice dei microservizi

Una volta tracciati i confini del servizio utilizzando la progettazione strategica, il Tactical Design fornisce una serie di modelli di progettazione per strutturare il codice all’interno di un singolo microservizio.

A. Entità e oggetti valore

All’interno di un microservizio, i modelli sono suddivisi in due categorie:

  1. Entità: oggetti che hanno un’identità univoca che persiste nel tempo, anche se i loro attributi cambiano. Gli esempi includono Order o Product.
  2. Oggetti valore: oggetti che non hanno un’identità univoca e sono definiti interamente dai loro attributi. Sono immutabili. Gli esempi includono ShippingAddress o ProductDimensions. Se due oggetti valore hanno gli stessi attributi, sono considerati uguali.

B. Aggregati e radici di aggregati

Un Aggregato è un cluster di entità e oggetti valore associati che vengono trattati come una singola unità per le modifiche dei dati. Ogni aggregato ha una Radice aggregata, che è l’unico punto di ingresso attraverso il quale gli oggetti esterni possono interagire con l’aggregato. La radice garantisce che tutte le varianti aziendali (regole) vengano applicate.

Ad esempio, nel diagramma sopra:

  • Nel Servizio ordini, Order è la radice aggregata. Contiene OrderItem (Entità) e ShippingAddress (Oggetto valore). I servizi esterni non possono modificare direttamente OrderItem; devono chiamare un metodo sulla root Order (ad esempio, order.AddItem()), che verifica che l’ordine non sia già stato spedito.

C. Eventi del dominio

Un Evento di dominio è qualcosa che si è verificato nel dominio che interessa agli esperti aziendali. Viene utilizzato per comunicare le modifiche tra contesti delimitati in modo asincrono. Quando un aggregato esegue un comando, pubblica un evento di dominio (ad esempio, OrderCreated). Altri servizi ascoltano questo evento e aggiornano il proprio stato di conseguenza, garantendo la coerenza finale senza dipendenze API sincrone.


3. Un esempio concreto: servizio di ordine e servizio di inventario

Esaminiamo un’implementazione semplificata del DDD tattico in Go for the Order Service, che mostra come la radice aggregata coordina le regole aziendali.

package domain

import (
	"errors"
	"time"
)

// Value Object (Immutable, identity-less)
type ShippingAddress struct {
	Street  string
	City    string
	ZipCode string
}

// Entity (Has identity, mutable)
type OrderItem struct {
	ProductID string
	Quantity  int
	Price     float64
}

// Aggregate Root (Enforces transactional boundaries)
type Order struct {
	ID        string
	Items     []OrderItem
	Address   ShippingAddress
	Status    string
	CreatedAt time.Time
}

// NewOrder creates a new Order Aggregate Root
func NewOrder(id string, address ShippingAddress) *Order {
	return &Order{
		ID:        id,
		Items:     []OrderItem{},
		Address:   address,
		Status:    "PENDING",
		CreatedAt: time.Now(),
	}
}

// AddItem enforces business rules before mutating state
func (o *Order) AddItem(productID string, qty int, price float64) error {
	if o.Status != "PENDING" {
		return errors.New("cannot add items to a finalized or cancelled order")
	}
	if qty <= 0 {
		return errors.New("quantity must be greater than zero")
	}
	
	o.Items = append(o.Items, OrderItem{
		ProductID: productID,
		Quantity:  qty,
		Price:     price,
	})
	return nil
}

Quando l’ordine viene salvato con successo, il servizio pubblica un evento su un broker di messaggi:

{
  "event_id": "evt_98231",
  "event_type": "OrderCreated",
  "timestamp": "2026-06-26T00:15:00Z",
  "payload": {
    "order_id": "ord_5521",
    "items": [
      { "product_id": "prod_88", "quantity": 2 }
    ]
  }
}

Il Servizio di inventario ascolta questo evento, aggiorna la sua entità StockLevel locale e completa il flusso in modo asincrono.


4. DDD strategico e tattico nei microservizi

Fase/Aspetto DDD strategico DDD tattico
Ambito Architettura del sistema globale (macro) All’interno di un unico codebase di servizio (micro)
Obiettivo primario Disegna confini di servizio puliti e disaccoppiati Modella una logica aziendale ricca e applica gli invarianti
Concetti chiave Contesti delimitati, linguaggio ubiquo, mappa contestuale Entità, oggetti valore, aggregati, eventi di dominio
Pubblico Architetti, Product Manager, Sviluppatori Sviluppatori di software, revisori di codici
Impatto sui microservizi Determina il numero e la portata dei servizi Determina le transazioni del database e la struttura delle directory

Conclusione: inizia prima con il DDD strategico

L’applicazione della progettazione basata sui domini ai microservizi è un modo efficace per garantire che la tua architettura corrisponda alla struttura aziendale. Tuttavia, i team spesso commettono l’errore di concentrarsi troppo su modelli tattici (come scrivere interfacce di repository e oggetti di valore) ignorando la progettazione strategica.

Se non si definiscono correttamente i confini, nessuna quantità di codice tattico pulito potrà evitare che il sistema si trasformi in un monolite distribuito. Inizia sempre con la mappatura strategica: definisci il tuo linguaggio ubiquo, raggruppa la logica in contesti delimitati, mappa i flussi di comunicazione e lascia che i confini dei tuoi microservizi emergano naturalmente da tali definizioni.


Scopri ulteriori informazioni sull’architettura software, sui modelli di progettazione e sugli approfondimenti tecnici sul blog di Ghaznix →