Utilizzando l’approccio Code First, potremmo pensare di applicare automaticamente DDD (Domain-Driven Design). In realtà non è così. Analizziamo la questione con un semplice esempio. Definiamo due classi, “Fattura”:
public class RigoFattura
{
public string CodiceArticolo { get; set; }
public string Descrizione { get; set; }
public float Quantita { get; set; }
public float Totale { get; set; }
}
Per creare la base dati sottostante, vogliamo utilizzare Entity Framework Code First. Sfruttando il Modelling by convention ed eseguendo l’override del metodo OnModelCreating, potremmo scrivere del codice simile al seguente:
public class Db : DbContext
{
public DbSet<Fattura> Fatture { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<RigoFattura>();
modelBuilder.Entity<Fattura>()
.ToTable("Fattura");
modelBuilder.Entity<Fattura>()
.HasKey<int>(f => f.Numero);
}
}
Eseguendo del codice simile al seguente:
Database.SetInitializer<Db>(new DropCreateDatabaseIfModelChanges<Db>());
using (Db db = new Db())
{
var fatture = from f in db.Fatture select f;
Fattura fattura = Fattura.CreaFattura();
fattura.AddRigoFattura(new RigoFattura() { CodiceArticolo = "COD.1", Descrizione = "DESCRIZIONE COD. ART. 1", Quantita = 1, Totale = 10 });
fattura.AddRigoFattura(new RigoFattura() { CodiceArticolo = "COD.2", Descrizione = "DESCRIZIONE COD. ART. 2", Quantita = 2, Totale = 20 });
fattura.AddRigoFattura(new RigoFattura() { CodiceArticolo = "COD.3", Descrizione = "DESCRIZIONE COD. ART. 3", Quantita = 3, Totale = 30 });
db.Fatture.Add(fattura);
db.SaveChanges();
Console.WriteLine("Righe in fattura:{0}", fattura.RigheFattura.Count());
Console.WriteLine("Fatture: {0}", fatture.Count());
}
a runtime Entity Framework produrrebbe un diagramma di database simile a quello mostrato nella figura sottostante:

Una sola tabella! Come mai ? Facciamo qualche osservazione:
La classe “Fattura”, espone le proprietà “Numero” e “DataFattura” correttamente mappate con le rispettive colonne della tabella fisica Fattura, ma ci siamo persi qualche pezzo.
Per come abbiamo definito il Domain Model, un’istanza di “Fattura” dovrebbe contenere (nel modello semplificato non sono state aggiunte regole di validazione) almeno un’istanza di “RigoFattura”. Verso il mondo esterno, l’insieme delle righe che appartengono alla fattura è esposta come IEnumerable<RigoFattura> . Per aggiungere una riga di fattura, deve essere utilizzato il metodo “AddRigoFattura” (questo perché vorremo fare una serie di operazioni, validazione ecc... prima di aggiungere la riga alla collezione).
Per ottenere un’istanza di “Fattura” è necessario utilizzare l’opportuno metodo statico della classe “Fattura” che dovrebbe impostare correttamente il “Numero” e la “Data della Fattura”, non arbitrariamente modificabili per ovvi motivi.
Generalmente, a livello di base dati, vorremmo che le tabelle Fattura e RigheFattura siano collegate da una Foreign Key (dove RigheFattura può avere una propria colonna che definisce la chiave primaria o un insieme di colonne che definiscono una chiave composta), ma se analizziamo il modello non è presente nulla di tutto ciò.
Nella creazione del modello abbiamo definito la classe “RigoFattura” come Complex Type, aggiungendo volutamente un errore di configurazione, perché se la tabella fisica fosse stata creata correttamente, ci troveremmo in una situazione in cui la tabella Fattura conterrebbe la definizione anche delle colonne di RigoFattura, il che comporterebbe ad avere più righe della stessa fattura (una per ogni rigo) nella stessa tabella (!).
Potremmo pensare di mappare la classe “RigoFattura” come tabella a parte, ma questo implicherebbe specificare una chiave primaria per la stessa. In ottica DDD, “Fattura” è un’entità, mentre “RigoFattura” un Object Value, per cui, perché dovrei aggiungere una chiave primaria a “RigoFattura” ? In ogni caso , EF non riconosce IEnumerable<RigoFattura>, ma allora cosa succede ? Che abbiamo sbagliato a pensare che l’Object Model (Domain Model in questo caso) possa essere utilizzato come Data Model. Riassumendo:
Approccio Code First:
- Definiamo l’Object Model come Data Model
- Ci preoccupiamo di come gli oggetti saranno mappati sul database (tramite l’OnModelCreating)
- Consideriamo nella costruzione dell’Object Model dei vincoli e confini della base di dati sottostante
Approccio Domain Model:
- Non ci preoccupiamo assolutamente della base di dati sottostante, non ci preoccupiamo di vincoli e confini, ci astraiamo completamente (ci focalizziamo solo sulla logica di dominio, logica invariante del processo che andiamo a modellare tramite le classi)
- Scenari complessi
- Non duplicazione delle funzionalità comuni
- Le classi del dominio non si preoccupano di come saranno persistiti i dati o di come saranno popolate, è responsabilità di altri (Repository, DI)
Quello che il post vuole sottolineare è che utilizzare l’approccio Code First di Entity Framework (almeno così come è ora) non vuol dire fare DDD, perché non lo permette. Questo non vuol dire che Code First sia un male.
Grazie ad Andrea per le sue osservazioni e precisazioni in merito all’argomento.