Il presente articolo è stato tradotto automaticamente.

Concetti sui dati

Programmazione per DDD (Domain-Driven Design): Suggerimenti per sviluppatori attenti ai dati, parte 2

Julie Lerman

Scaricare il codice di esempio

Julie LermanMese questo continuerà a celebrare il decimo anniversario del libro di Eric Evans, "Domain-Driven Design: Affrontare la complessità nel cuore del Software"(Addison-Wesley Professional, 2003).Io condividere ulteriori suggerimenti per gli sviluppatori di dati prima che sono interessati a beneficiare di alcuni dei modelli di codificazione di jealously Design (DDD).Articolo del mese scorso ha evidenziato:

  • Accantonando preoccupazioni di persistenza quando modellazione del dominio
  • Esponendo i metodi per guidare la tua entità e aggregati, ma non la proprietà set
  • Riconoscendo che alcuni sottosistemi sono perfetti per semplice creare, leggere, aggiornare ed eliminare interazione (CRUD) e non richiedono la modellazione del dominio
  • Il vantaggio di non tentare di condividere dati o tipi tra contesti delimitati

In questo articolo imparerai cosa si intende con i termini "anemico" e modelli di dominio "ricco", così come il concetto di oggetti di valore.Valore oggetti sembrano essere un argomento che rientra in uno dei due campi per gli sviluppatori.È così evidente che alcuni lettori non riescono a immaginare perché esso garantisce che qualsiasi discussione a tutti, o così sconcertante che gli altri non sono mai stati in grado di avvolgere la testa intorno ad esso.Sarò anche incoraggiarvi a considerare l'utilizzo di oggetti di valore al posto di oggetti correlati in determinati scenari.

Quel termine condiscendente: Modelli di dominio anemico

C'è una coppia di termini che sentirete spesso rispetto a come vengono definite le classi in DDD — modello di dominio anemico e dominio ricco.DDD, modello di dominio si riferisce a una classe.Un modello di dominio ricco è uno che si allinea con l'approccio DDD — una classe (o tipo) che è definito con i comportamenti, non solo con Getter e Setter.Al contrario, un modello di dominio anemico costituito semplicemente getter e setter (e forse alcuni metodi semplici), che va bene per molti scenari, ma non trae alcun beneficio da DDD.

Quando ho iniziato ad usare il primo codice di Entity Framework (EF), le classi che inteso il primo codice di lavorare con erano probabilmente 99% anemico.Figura 1 è un perfetto esempio, mostrando un tipo di cliente e il tipo di persona da cui eredita.Spesso vorrei includere una manciata di metodi, ma il tipo di base è stato semplicemente uno schema con Getter e Setter.

Figura 1 dominio anemico tipico modello classi Look come tabelle di Database

public class Customer : Person
{
  public Customer()
  {
    Orders = new List<Order>();
  }
  public ICollection<Order> Orders { get; set; }
  public string SalesPersonId { get; set; }
  public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string CompanyName { get; set; }
  public string EmailAddress { get; set; }
  public string Phone { get; set; }
}

Ho finalmente guardato queste classi e realizzato che stavo facendo a poco più di definire una tabella di database nella mia classe. Non è necessariamente una cosa negativa, a seconda di che cosa si intende fare con il tipo.

Confronta che al tipo di cliente più ricco elencati Figura 2, che è simile al tipo di cliente ho esplorato nel mio articolo precedente (msdn.microsoft.com/magazine/dn342868). Espone metodi che controllano l'accesso alle proprietà e altri tipi che fanno parte dell'aggregato. Poiché si è visto nella colonna precedente per meglio esprimere alcuni degli argomenti che sto discutendo di questo mese, ho ho ottimizzato il tipo di cliente.

Figura 2 tipo di cliente che è un modello di dominio ricco, non semplicemente la proprietà

public class Customer : Contact
{
  public Customer(string firstName, string lastName, string email)
  {
    FullName = new FullName(firstName, lastName);
    EmailAddress = email;
    Status = CustomerStatus.Silver;
  }
  internal Customer()
  {
  }
  public void UseBillingAddressForShippingAddress()
  {
    ShippingAddress = new Address(
      BillingAddress.Street1, BillingAddress.Street2,
      BillingAddress.City, BillingAddress.Region,
      BillingAddress.Country, BillingAddress.PostalCode);
  }
  public void CreateNewShippingAddress(string street1, string street2,
   string city, string region, string country, string postalCode)
  {
    BillingAddress = new Address(
      street1,street2,
      city,region,
      country,postalCode)
  }
  public void CreateBillingInformation(string street1,string street2,
   string city,string region,string country, string postalCode,
   string creditcardNumber, string bankName)
  {
    BillingAddress = new Address      (street1,street2, city,region,country,postalCode );
    CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
  }
  public void SetCustomerContactDetails
   (string email, string phone, string companyName)
  {
    EmailAddress = email;
    Phone = phone;
    CompanyName = companyName;
  }
  public string SalesPersonId { get; private set; }
  public CustomerStatus Status { get; private set; }
  public Address ShippingAddress { get; private set; }
  public Address BillingAddress { get; private set; }
  public CustomerCreditCard CreditCard { get; private set; }
}

In questo modello più ricco, piuttosto che semplicemente esponendo la proprietà per essere letti e scritti, la superficie pubblica del cliente è costituita da metodi espliciti. Rivedere la sezione Private setter e metodi pubblici del mese ultimo per maggiori dettagli. Il mio punto qui è per aiutarvi a comprendere meglio la differenza tra ciò che DDD si riferisce a come un anemico rispetto a un modello di dominio ricco.

Oggetti di valore possono confondere

Anche se sembrano semplici, oggetti di valore DDD sono un grave punto di confusione per molti, me compreso. Ho letto e sentito così tanti modi diversi di descrivere gli oggetti di valore da una varietà di prospettive. Per fortuna, ognuno delle diverse spiegazioni, piuttosto che in conflitto con gli altri, mi ha aiutato a costruire una comprensione più profonda degli oggetti di valore.

Al suo interno, un oggetto di valore è una classe che non ha una chiave di identità.

Entity Framework ha un concetto di "tipi complessi" che si avvicina abbastanza. Tipi complessi non hanno una chiave di identità — sono un modo per incapsulare un insieme di proprietà. EF capisce come gestire questi oggetti quando si tratta di persistenza dei dati. Nel database, essi vengono memorizzati come campi della tabella a cui è associata l'entità.

Ad esempio, invece di avere una proprietà FirstName e una proprietà LastName nella vostra classe persona, si potrebbe avere una proprietà FullName:

public FullName FullName { get; set; }

FullName può essere una classe che incapsula le proprietà FirstName e LastName, con un costruttore che ti obbliga a fornire sia di quelle proprietà.

Ma un oggetto di valore è più di un tipo complesso. In DDD, ci sono tre caratteristiche di un oggetto di valore:

  1. Non ha alcuna chiave di identità (ciò si allinea con un tipo complesso)
  2. Esso è immutabile
  3. Quando si verifica l'uguaglianza ad altre istanze dello stesso tipo, tutti i valori vengono confrontati

Immutabilità è interessante. Senza una chiave di identità, immutabilità di un tipo definisce la sua identità. È sempre possibile identificare un'istanza tramite la combinazione delle sue proprietà. Perché è immutabile, nessuna delle proprietà del tipo può cambiare, quindi è sicuro di utilizzare valori del tipo per identificare una particolare istanza. (Hai già visto come entità DDD proteggersi dalle modifiche casuali rendendo loro Setter privato, ma si potrebbe avere un metodo che permette di influenzare le proprietà). Non solo fa un oggetto di valore nascondere il setter, ma vi impedisce di modificare le proprietà. Se una proprietà è stato modificato, che significherebbe che il valore dell'oggetto è cambiato. Perché l'intero oggetto rappresenta il suo valore, non interagire individualmente con le sue proprietà. Se è necessario l'oggetto ad avere diversi valori, si crea una nuova istanza dell'oggetto per contenere un nuovo insieme di valori. Alla fine, non non c'è nessuna cosa come la modifica di una proprietà di un oggetto di valore — che effettivamente rende anche formulare una frase che include la frase "se è necessario modificare il valore di una proprietà" un ossimoro. Un parallelo utile è considerare una stringa, che è un altro tipo di immutabile (almeno in tutte le lingue che conosco). Quando si lavora con un'istanza di stringa, è non sostituire singoli caratteri della stringa. È sufficiente creare una nuova stringa.

Mio oggetto di valore FullName è mostrato Figura 3. Non ha alcuna proprietà chiave di identità. Si può vedere che l'unico modo per creare un'istanza di esso è di fornire entrambi i valori di proprietà, e non non c'è alcun modo per modificare una di tali proprietà. Così essa soddisfa il requisito di immutabilità. L'ultimo requisito, che fornisce un modo per confrontare l'uguaglianza a un'altra istanza di quel tipo, è nascosta nella classe vengono personalizzata (che io ho preso in prestito da Jimmy Bogard presso bit.ly/13SWd9h) da cui eredita, perché questo è un po ' di codice contorto. Sebbene questo vengono non tiene conto per la proprietà di insieme, soddisfa le mie esigenze perché non ho alcun raccolte all'interno di questo oggetto di valore.

Nella figura 3 l'oggetto valore FullName

public class FullName:ValueObject<FullName>
{
  public FullName(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
  }
  public FullName(FullName fullName)
    : this(fullName.FirstName, fullName.LastName)
  {    }
  internal FullName() { }
  public string FirstName { get; private set; }
  public string LastName { get; private set; }
 // More methods, properties for display formatting
}

Tenendo presente che è possibile una copia modificata — per esempio, simile a questo FullName, ma con un nome diverso — Vaughn Vernon, autore di "Attuare jealously Design" (Addison-Wesley Professional, 2013), suggerisce FullName potrebbe includere i metodi per creare nuove istanze dall'istanza esistente:

public FullName WithChangedFirstName(string firstName)
{
  return new FullName(firstName, this.LastName);
}
public FullName WithChangedLastName(string lastName)
{
  return new FullName(this.FirstName, lastName);
}

Quando posso aggiungere nel mio livello di persistenza (Entity Framework), questo vedrà come proprietà ComplexType EF di qualsiasi classe in cui è utilizzato. Quando uso FullName come proprietà del mio tipo di persona, EF verranno archiviate le proprietà di FullName nella tabella del database in cui è memorizzato il tipo di persona. Per impostazione predefinita, la proprietà sarà denominata FullName_FirstName e FullName_LastName nel database.

FullName è piuttosto semplice. Anche se stavi facendo design orientato al database, si potrebbe non voler memorizzare FirstName e LastName in una tabella separata.

Oggetti di valore o oggetti correlati?

Ma consideriamo ora un altro scenario — Immaginate un cliente con un ShippingAddress e un BillingAddress:

public Address ShippingAddress { get; private set; }
public Address BillingAddress { get; private set; }

Per impostazione predefinita (basata sui dati di cervello), creare indirizzo come un'entità, inclusa una proprietà AddressId. E, ancora, perché "Credo nei dati," presumo che indirizzo verrà memorizzato come una tabella separata nel database. OK, sto lavorando sodo per allenarsi a me stesso di smettere di considerare il database durante la modellazione mio dominio, così che è ininfluente. Tuttavia, ad un certo punto che io aggiungo nei miei dati strato utilizzando EF ed EF farà anche questa ipotesi. Ma EF non sarà in grado di elaborare le mappature correttamente. Presumo un 0.. 1: * rapporto tra indirizzo e cliente; in altre parole, che un indirizzo potrebbe avere un numero qualsiasi dei relativi clienti e un cliente avrà alcun indirizzo o in un indirizzo.

Mio DBA potrebbe non piacere questo risultato e, cosa più importante, che non scrivendo il mio codice di applicazione sulla base del presupposto stesso che sta facendo il Entity Framework — che ci sono molti clienti a zero o un indirizzo. Per questo motivo, EF possono influire sulla mia persistenza dei dati o il recupero in qualche modo inaspettato. Quindi dal punto di vista di qualcuno che ha lavorato molto con EF, il mio primo approccio è di fissare i mapping EF utilizzando l'API intuitiva. Ma chiedendo EF per risolvere questo problema di aggiungere proprietà di navigazione di indirizzo che punta al cliente che non voglio che nel mio modello costringerebbe. Come si può vedere, per risolvere questo problema con i mapping EF mi porterebbe una tana di coniglio.

Quando un passo indietro e concentrarsi sul dominio, non su EF o database, ha più senso fare semplicemente affrontare un oggetto di valore invece di un'entità. Sono necessari tre passaggi:

  1. Ho bisogno di rimuovere la proprietà chiave dal tipo di indirizzo (possibilmente chiamato Id o AddressId).
  2. Ho bisogno per essere sicuri che l'indirizzo è immutabile. Costruttore già mi permette di compilare tutti i campi. Devo togliere tutti i metodi che consentono qualsiasi proprietà di cambiare.
  3. Ho bisogno di garantire che l'indirizzo può essere controllato per uguaglianza basata sui valori delle relative proprietà e i campi. Posso farlo ereditando nuovamente da quella simpatica classe vengono ho preso in prestito da Jimmy Bogard (vedere Figura 4).

Figura 4 indirizzo come un oggetto di valore

public class Address:ValueObject<Address>
{
  public Address(string street1, string street2, 
    string city, string region,
    string country, string postalCode) {
    Street1 = street1;
    Street2 = street2;
    City = city;
    Region = region;
    Country = country;
    PostalCode = postalCode;  }
  internal Address()  {  }
  public string Street1 { get; private set; }
  public string Street2 { get; private set; }
  public string City { get; private set; }
  public string Region { get; private set; }
  public string Country { get; private set; }
  public string PostalCode { get; private set; }
  // ... 
}

Come utilizzare questa classe indirizzo dalla mia classe cliente non cambierà, tranne che se ho bisogno di modificare l'indirizzo, io devo creare una nuova istanza.

Ma ora non ho più bisogno preoccuparsi della gestione di tale rapporto. E fornisce anche un altro banco di prova per gli oggetti di valore, in quanto un cliente davvero è definito dalla sua spedizione e fatturazione informazioni perché se vendo qualcosa al cliente che, molto probabilmente avrò bisogno di spedire ed il reparto contabilità fornitori richiede che ho il suo indirizzo di fatturazione.

Questo non è per dire che ogni relazione di 1:0.. 1 o 1:1 può essere sostituito con oggetti di valore, ma è che una bella soluzione qui e il mio lavoro è diventati molto più semplice di quando mi sono fermato a dover risolvere un puzzle dopo l'altro che sono saltate fuori come ho provato a forzare il Entity Framework a mantenere questo rapporto per me.

Come l'esempio FullName, cambiando l'indirizzo per essere un oggetto di valore significa Entity Framework vedrà l'indirizzo come un ComplexType e memorizza tutti i dati all'interno delle tabelle del cliente. Anni di essere focalizzato sulla normalizzazione database mi ha fatto pensare automaticamente questa è stata una brutta cosa, ma mio database in grado di gestire facilmente e funziona per il mio dominio particolare.

Posso pensare di molti argomenti contro l'uso di questa tecnica, ma iniziano tutti con "what if". Nessuno di quelli che non appartengono al mio dominio non sono argomenti validi. Sprechiamo un sacco di tempo che codifica per scenari solo nel caso che non vengono mai a passare. Sto cercando di essere più premuroso sull'aggiunta di quei cerotti preemptive in mio soluzioni.

Non abbastanza ancora fatto

Mi sto davvero attraverso l'elenco dei concetti DDD che hanno infestato questa geek di dati. Come grok più di questi concetti, divento ispirato per imparare ancora di più. Una volta che sposto il mio pensiero un po', questi modelli fanno molto senso per me. Non ridurre il mio interesse in un bit di dati persistenza, ma separando il dominio dalla persistenza dei dati e infra­struttura si sente proprio a me dopo averli aggrovigliata insieme per tanti anni. Ancora, è importante tenere a mente che ci sono così tante attività di software che non necessitano di DDD. DDD può aiutare a risolvere problemi complessi, ma molto spesso è eccessivo per quelli semplici.

Nel prossimo articolo tratterò alcune altre DDD tecniche strategie che sembrava anche inizialmente in conflitto con il mio pensiero dati prima, come lasciar andare relazioni bidirezionali, considerando le invarianti e affrontare qualsiasi necessità percepita per attivare l'accesso ai dati da vostro aggregati.

Julie Lerman è un Microsoft MVP, mentore e consulente .NET che risiede nel Vermont. È possibile trovare le sue presentazioni relative all'accesso ai dati e altri argomenti su Microsoft .NET in occasioni di conferenze che si tengono in tutto il mondo. Blog di lei a /Blog ed è l'autore di "programmazione Entity Framework" (2010) così come un codice prima edizione (2011) e un DbContext edizione (2012), tutti da o ' Reilly Media. Seguirla su Twitter a Twitter.com /julielerman. e vedere i suoi corsi Pluralsight presso Juliel.me/PS-video.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Stephen Bohlen (Microsoft)
Stephen A. Bohlen è attualmente un Senior Technology Evangelist Microsoft Corporation e porta la sua variegata 20-plus-anni di esperienza come un ex praticante architetto, responsabile CAD, tecnologo IT, ingegnere del software, CTO e consulente per assistere selezionare Microsoft organizzazioni partner nell'adozione di prodotti per sviluppatori Microsoft e le tecnologie all'avanguardia e pre-release.