2012-06-27 15 views
17

Dopo aver visto la presentazione di NDC12 "Creazione di modelli di domini malvagi" da Jimmy Bogard (http://ndcoslo.oktaset.com/Agenda), stavo vagando su come mantenere quel tipo di modello di dominio.
Questa è classe di esempio dalla presentazione:Modello di dominio ricco con comportamenti e ORM

public class Member 
{ 
    List<Offer> _offers; 

    public Member(string firstName, string lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
     _offers = new List<Offer>(); 
    } 

    public string FirstName { get; set; } 

    public string LastName { get; set; } 

    public IEnumerable<Offer> AssignedOffers { 
     get { return _offers; } 
    } 

    public int NumberOfOffers { get; private set; } 

    public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc) 
    { 
     var value = valueCalc.CalculateValue(this, offerType); 
     var expiration = offerType.CalculateExpiration(); 
     var offer = new Offer(this, offerType, expiration, value); 
     _offers.Add(offer); 
     NumberOfOffers++; 
     return offer; 
    } 
} 

quindi ci sono alcune norme contenute nel presente modello di dominio:
- Membro deve avere nome e cognome
- Numero di offerte non può essere modificato al di fuori
- Il membro è responsabile della creazione di una nuova offerta, calcolandone il valore e l'assegnazione

Se prova a mappare questo ad un ORM come Entity Framework o NHibernate, non funzionerà. Quindi, qual è l'approccio migliore per mappare questo tipo di modello al database con ORM?
Ad esempio, come caricare AssignedOffers dal DB se non è impostato alcun setter?

L'unica cosa che per me ha senso è l'utilizzo dell'architettura di comando/query: le query vengono sempre eseguite con DTO come risultato, non con le entità di dominio e i comandi vengono eseguiti sui modelli di dominio. Inoltre, l'event sourcing è perfetto per comportamenti sul modello di dominio. Ma questo tipo di architettura CQS non è forse adatto a tutti i progetti, specialmente brownfield. O no?

Sono a conoscenza di domande simili qui, ma non sono riuscito a trovare esempi e soluzioni concrete.

+0

Ho appena visto lo stesso video, e mi stavo chiedendo la stessa cosa. Cosa ne pensi di passare un poco nel costruttore e avere anche una proprietà readonly sulla classe Member per restituire un clone di quello poco? In questo modo è possibile ottenere dati dentro e fuori dall'oggetto dominio per mantenerlo o passarlo. – stralsi

+0

Qualcosa come l'istantanea dell'oggetto? Probabilmente funzionerebbe, ma richiederebbe anche qualche hacking per farlo funzionare con lo strumento ORM. Personalmente non vedo alcun modo semplice, e porterebbe molte astrazioni e generalizzazioni che dovresti combattere durante l'applicazione. L'individuazione degli eventi è l'unico modo per andare IMO –

+0

In realtà ho appena guardato questo video e stavo pensando la stessa cosa; vuol dire che hai bisogno di un set di oggetti DTO/POCO per il livello dati/persistenza che il tuo ORM idrata e poi usi un mappatore come AutoMapper per mappare su un oggetto dominio? Succede qualcosa del genere nel repository? Sembra che un ORM come il Codice EF in primo luogo si aspetta un POCO con getter e setter. – Abe

risposta

1

Per AssignedOffers: se si guarda il codice, si vedrà che AssignedOffers restituisce il valore da un campo. NHibernate può popolare quel campo in questo modo: Mappa (x => x.AssignedOffers) .Access.Field().

Accettare l'utilizzo di CQS.

+0

Cool, grazie per informazioni NH! –

+0

Ma che dire del costruttore? –

+0

Non vedo un costruttore privato/protetto o interno nel campione. Quindi NHibernate utilizzerà quello predefinito che non ha parametri. Vorrei usare questo tipo di costruttore che hai fatto solo per assicurarti che l'oggetto venga popolato nel codice, non da orm. – Luka

0

Quando si esegue DDD per prima cosa, si ignorano i problemi di persistenza. L'ORM è strettamente accoppiato a un RDBMS quindi è un problema di persistenza.

Una struttura di persistenza dei modelli ORM NON il dominio. Fondamentalmente il repository deve "convertire" la radice di aggregazione ricevuta in una o più entità di persistenza. Il Contesto Limitato conta molto poiché il Radice Aggregato cambia in base a ciò che stai cercando di ottenere.

Supponiamo di voler salvare il membro nel contesto di una nuova offerta assegnata. Allora avrai qualcosa di simile (ovviamente questo è solo uno scenario possibile)

public interface IAssignOffer 
{ 
    int OwnerId {get;} 
    Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc); 
    IEnumerable<Offer> NewOffers {get; } 
} 

public class Member:IAssignOffer 
{ 
    /* implementation */ 
} 

public interface IDomainRepository 
{ 
    void Save(IAssignOffer member);  
} 

Avanti repo riceverà solo i dati necessari al fine di modificare le entità NH e questo è tutto.

Informazioni su Sourcing di eventi, penso che devi verificare se si adatta al tuo dominio e non vedo alcun problema con l'utilizzo di Event Sourcing solo per la memorizzazione dei domini di aggregazione del dominio mentre il resto (principalmente l'infrastruttura) può essere archiviato nel modo ordinario (tabelle relazionali). Penso che CQRS ti dia una grande flessibilità in questa materia.

+0

grazie per la risposta, ma come dovrebbe essere il repository che carica i membri? –

+0

Caricali per quale scopo? :) – MikeSW

+0

Membro espone IEnumerable , quindi per l'utilizzo in classe di servizio o qualcosa del genere. –

11

Questa è davvero una buona domanda e qualcosa che ho contemplato. È potenzialmente difficile creare oggetti di dominio appropriati che siano completamente incapsulati (cioè senza setter di proprietà) e utilizzare un ORM per creare direttamente gli oggetti del dominio.

Nella mia esperienza ci sono 3 modi di risolvere questo problema:

  • Come già indicato da Luka, NHibernate supporta il mapping di campi privati, piuttosto che setter di proprietà.
  • Se si utilizza EF (che non credo supporti quanto sopra), è possibile utilizzare memento pattern per ripristinare lo stato sugli oggetti del dominio. per esempio. usi la struttura delle entità per popolare gli oggetti "memento" che le entità di dominio accettano per impostare i loro campi privati.
  • Come si è sottolineato, l'utilizzo di CQRS con l'individuazione degli eventi elimina questo problema. Questo è il mio metodo preferito per creare oggetti di dominio perfettamente incapsulati, che hanno anche tutti i vantaggi aggiuntivi del sourcing di eventi.
2

Vecchio thread. Ma c'è un more recent post (fine 2014) di Vaughn Vernon che affronta proprio questo scenario, con particolare riferimento a Entity Framework. Dato che in qualche modo ho faticato a trovare tali informazioni, forse può essere utile postarle anche qui.

Fondamentalmente i sostenitori post per l'oggetto Product dominio (aggregato) per avvolgere l'oggetto di dati ProductState EF POCO per quanto riguarda il "sacco di dati" lato delle cose. Ovviamente l'oggetto dominio aggiungerebbe ancora tutto il suo comportamento di dominio ricco attraverso metodi/accessor specifici del dominio, ma ricorrerebbe all'oggetto dati interno quando deve ottenere/impostare le sue proprietà.

copia frammento direttamente dal post:

public class Product 
{ 
    public Product(
    TenantId tenantId, 
    ProductId productId, 
    ProductOwnerId productOwnerId, 
    string name, 
    string description) 
    { 
    State = new ProductState(); 
    State.ProductKey = tenantId.Id + ":" + productId.Id; 
    State.ProductOwnerId = productOwnerId; 
    State.Name = name; 
    State.Description = description; 
    State.BacklogItems = new List<ProductBacklogItem>(); 
    } 

    internal Product(ProductState state) 
    { 
    State = state; 
    } 

    //... 

    private readonly ProductState State; 
} 

public class ProductState 
{ 
    [Key] 
    public string ProductKey { get; set; } 

    public ProductOwnerId ProductOwnerId { get; set; } 

    public string Name { get; set; } 

    public string Description { get; set; } 

    public List<ProductBacklogItemState> BacklogItems { get; set; } 
    ... 
} 

Repository userebbe costruttore interno per istanziare (carico) un'istanza entità dalla sua versione DB-persisteva.

Quello po 'posso aggiungere io, è che probabilmente Product oggetto di dominio dovrebbe essere sporcati con un altro di accesso solo per lo scopo della persistenza attraverso EF: nella stessa era come new Product(productState) consente a un soggetto di dominio per essere caricato da database, il modo opposto dovrebbe essere consentito attraverso qualcosa come:

public class Product 
{ 
    // ... 
    internal ProductState State 
    { 
    get 
    { 
     // return this.State as is, if you trust the caller (repository), 
     // or deep clone it and return it 
    } 
    } 
} 

// inside repository.Add(Product product): 

dbContext.Add(product.State); 
Problemi correlati