posts - 315, comments - 268, trackbacks - 15

My Links

News

View Pietro Libro's profile on LinkedIn

DomusDotNet
   DomusDotNet

Pietro Libro

Tag Cloud

Article Categories

Archives

Post Categories

Blogs amici

Links

EF Code First e DDD

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 Fattura
    {
        private int _numero = 1;
        private DateTime _dataFattura;

        private IList<RigoFattura> _righeFatture = null;

        public int Numero
        {
            get { return _numero; }
            internal set { _numero = value; }
        }

        public DateTime DataFattura
        {
            get { return _dataFattura; }
            internal set { _dataFattura = value; }
        }

        private Fattura()
        {
        }

        public static Fattura CreaFattura()
        {
            Fattura fattura = new Fattura();

            fattura.Numero = 1;// Proprietà che dovrebbe essere correttamente impostata per ogni fattura;
            fattura.DataFattura = DateTime.Now;

            return fattura;
        }

        public void AddRigoFattura(RigoFattura rigoFattura)
        {
            if (rigoFattura == null) throw new System.ArgumentNullException("RigoFattura is null.");
            if (_righeFatture == null) _righeFatture = new List<RigoFattura>(3);

            _righeFatture.Add(rigoFattura);
        }

        public IEnumerable<RigoFattura> RigheFattura
        {
            get { return _righeFatture; }
        }
    }

E “RigoFattura”:

    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:

image

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.

Print | posted on lunedì 4 aprile 2011 10:42 | Filed Under [ Entity Framework 4.1 ]

Feedback

Gravatar

# Expression, LambdaExpression, Entity Framework (e DDD)

Expression, LambdaExpression, Entity Framework (e DDD)
10/11/2011 09:13 | Il blog di Pietro Libro
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET