2012-06-27 4 views
17

Nachdem ich die NDC12-Präsentation "Crafting Wicked Domain Models" von Jimmy Bogard (http://ndcoslo.oktaset.com/Agenda) gesehen hatte, war ich auf der Suche nach einem solchen Domänenmodell.
Dies ist ein Beispielklasse von der Präsentation:Rich-Domain-Modell mit Verhaltensweisen und 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; 
    } 
} 

so gibt es einige Regeln in diesem Bereich Modell enthalten:
- Mitglied ersten und letzten haben müssen Name
- Anzahl der Angebote kann nicht außerhalb
geändert werden - Mitglied ist verantwortlich für die Erstellung eines neuen Angebots, Berechnung seines Werts und seiner Zuweisung

Wenn versuchen, dies zu einigen ORM wie Entity Framework oder NHibernate zuordnen, wird es nicht funktionieren. Also, was ist der beste Ansatz für die Zuordnung dieser Art von Modell zur Datenbank mit ORM?
Zum Beispiel, wie kann ich AssignedOffers von DB laden, wenn es keinen Setter gibt?

Das einzige, was für mich Sinn macht, ist die Verwendung der Befehls-/Abfragearchitektur: Abfragen werden immer mit DTO als Ergebnis durchgeführt, nicht als Domänenentitäten, und Befehle werden in Domänenmodellen ausgeführt. Event-Sourcing eignet sich auch perfekt für Verhaltensweisen im Domänenmodell. Aber diese Art von CQS-Architektur ist vielleicht nicht für jedes Projekt geeignet, besonders für Brownfields. Oder nicht?

Ich kenne ähnliche Fragen hier, konnte aber kein konkretes Beispiel und keine Lösung finden.

+0

Ich habe gerade das gleiche Video angesehen, und ich habe mich gefragt, die gleiche Sache. Was denkst du darüber, ein poco im Konstruktor zu übergeben und auch eine readonly-Eigenschaft für die Member-Klasse, um einen Klon dieses poco zurückzugeben? Auf diese Weise können Sie Daten innerhalb und außerhalb des Domänenobjekts abrufen, um sie zu erhalten oder weiterzugeben. – stralsi

+0

So etwas wie Objekt-Snapshot? Es würde wahrscheinlich funktionieren, würde aber auch etwas Hacking erfordern, um es mit dem ORM-Tool arbeiten zu lassen. Ich persönlich sehe keinen einfachen Weg, und es würde viele Abstraktionen und Verallgemeinerungen mit sich bringen, die man während der gesamten App-Entwicklung bekämpfen müsste. Event Sourcing ist die einzige Möglichkeit, um IMO zu gehen. –

+0

Ich habe gerade dieses Video angeschaut und dachte über dasselbe nach; Bedeutet das, dass Sie eine Menge von DTO/POCO-Objekten für die Daten/Persistenzschicht benötigen, die Ihr ORM hydratisiert, und dann einen Mapper wie AutoMapper verwenden, um ein Domänenobjekt zuzuordnen? Kommt so etwas im Repository vor? Es scheint, als ob ein ORM wie EF Code First einen POCO mit Gettern und Setter erwartet. – Abe

Antwort

1

Für AssignedOffers: Wenn Sie sich den Code ansehen, sehen Sie, dass AssignedOffers den Wert aus einem Feld zurückgibt. NHibernate kann dieses Feld wie folgt auffüllen: Map (x => x.AssignedOffers) .Access.Field().

Stimmen Sie der Verwendung von CQS zu.

+0

Cool, danke für NH info! –

+0

Aber was ist mit Konstruktor? –

+0

Ich sehe keinen privaten/geschützten oder internen Konstruktor in Ihrer Stichprobe. Daher wird NHibernate den Standardparameter verwenden, der parameterlos ist. Ich würde diese Art von Konstruktor nur verwenden, um sicherzustellen, dass das Objekt in Code, nicht von orm, gefüllt wird. – Luka

0

Wenn Sie DDD zuerst tun, ignorieren Sie die Persistenz Bedenken. Das ORM ist eng an ein RDBMS gekoppelt, daher ist es eine beharrliche Angelegenheit.

Ein ORM-Modelle Persistenzstruktur NICHT die Domäne. Grundsätzlich muss das Repository den empfangenen Gesamtstamm in eine oder mehrere Persistenzeinheiten umwandeln. Der gebundene Kontext ist sehr wichtig, da sich die Gesamtsumme der Aggregate entsprechend dem ändert, was Sie ebenfalls erreichen möchten.

Angenommen, Sie möchten das Mitglied im Kontext eines neuen zugewiesenen Angebots speichern. Dann werden Sie etwas davon haben (natürlich ist dies nur ein mögliches Szenario)

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);  
} 

Als nächstes wird die Repo erhalten nur die erforderlich ist, um Daten, die die NH Einheiten zu ändern, und das ist alles. Über EVent Sourcing denke ich, dass Sie sehen müssen, ob es zu Ihrer Domain passt und ich sehe kein Problem darin, Event Sourcing nur zum Speichern von Domain - Aggregat - Roots zu verwenden, während der Rest (hauptsächlich Infrastruktur) in der. Gespeichert werden kann gewöhnlicher Weg (relationale Tabellen). Ich denke, CQRS gibt Ihnen eine große Flexibilität in dieser Angelegenheit.

+0

Danke für die Antwort, aber wie sollte Repository laden Mitglieder bietet? –

+0

Laden Sie sie für welchen Zweck? :) – MikeSW

+0

Mitglied legt IEnumerable , also für die Verwendung in Service-Klasse oder so ähnlich. –

11

Das ist eigentlich eine sehr gute Frage und etwas, das ich mir vorgestellt habe. Es ist möglicherweise schwierig, richtige Domänenobjekte zu erstellen, die vollständig gekapselt sind (d. H. Keine Eigenschaften-Setter) und ein ORM verwenden, um die Domänenobjekte direkt zu erstellen.

Nach meiner Erfahrung gibt es drei Möglichkeiten, um dieses Problem zu lösen:

  • Wie bereits von Luka erwähnen, unterstützt NHibernate Mapping auf private Felder, anstatt Eigentum Setter.
  • Wenn Sie EF verwenden (was meiner Meinung nach nicht die oben genannten unterstützt), können Sie memento pattern verwenden, um den Status Ihrer Domänenobjekte wiederherzustellen. z.B. Sie verwenden das Entity-Framework, um "Memento" -Objekte zu füllen, die Ihre Domain-Entitäten akzeptieren, um ihre privaten Felder festzulegen.
  • Wie Sie bereits erwähnt haben, wird durch die Verwendung von CQRS mit Event Sourcing dieses Problem behoben. Dies ist meine bevorzugte Methode, perfekt verkapselte Domänenobjekte zu erstellen, die alle zusätzlichen Vorteile von Event Sourcing bieten.
2

Alter Thread. Aber es gibt eine more recent post (Ende 2014) von Vaughn Vernon, die genau dieses Szenario anspricht, mit besonderem Bezug auf Entity Framework. Angesichts der Tatsache, dass ich irgendwie Schwierigkeiten hatte, solche Informationen zu finden, kann es vielleicht hilfreich sein, sie auch hier zu veröffentlichen.

Grundsätzlich sind die Post setzt sich für die Product Domäne (Aggregat) Objekt der ProductState EF POCO-Datenobjekt wickeln, was betrifft die „Datentasche“ Seite der Dinge. Natürlich würde das Domänenobjekt immer noch sein gesamtes reiches Domänenverhalten durch domänenspezifische Methoden/Zugriffsmethoden hinzufügen, würde aber auf das innere Datenobjekt zurückgreifen, wenn es seine Eigenschaften abrufen/setzen muss.

Kopieren Schnipsel direkt aus Beitrag:

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 internen Konstruktor verwenden, um würde (Last) eine Einheit Instanz von seiner DB-beharrte Version zu instanziiert.

Das ein Bit mich hinzufügen kann, ist, dass wahrscheinlich Product Domain-Objekt mit einem weiteren Accessor dirtied sollte nur zum Zweck der Persistenz durch EF: in derselben war wie new Product(productState) ermöglicht eine Domäne Entität aus geladen werden Datenbank, der umgekehrte Weg sollte durch so etwas wie erlaubt werden:

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);