2012-06-27 29 views
17

在觀看來自Jimmy Bogard(http://ndcoslo.oktaset.com/Agenda)的NDC12演示文稿「Crafting Wicked Domain Models」後,我徘徊於如何堅持這種領域模型。
這是從演示示例類:帶有行爲和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; 
    } 
} 

所以有包含在該域模型的一些規則:
- 會員必須有姓和名
- 報價數量不能改變外
- 會員負責創建新優惠,計算其價值和分配

如果嘗試將此映射到像Entity Framework或NHibernate這樣的一些ORM,它將無法工作。 那麼,使用ORM將這種模型映射到數據庫的最佳方法是什麼?
例如,如果沒有setter,我該如何從DB加載AssignedOffers?

對我來說唯一有意義的事情就是使用命令/查詢體系結構:查詢總是以DTO作爲結果完成,而不是域實體,並且命令在域模型上完成。此外,事件採購非常適合領域模型上的行爲。但這種CQS架構並不適合每個項目,特別是棕地。或不?

我知道這裏有類似的問題,但找不到具體的例子和解決方案。

+0

我剛看了同一個視頻,我想知道同樣的事情。你如何看待在構造函數中傳遞一個poco,並且在Member類中擁有隻讀屬性以返回該poco的一個克隆?這樣你就可以獲取數據進出域對象,以便保存或傳遞它。 – stralsi

+0

對象快照?它可能會工作,但也需要一些黑客來使它與ORM工具一起工作。我個人看不出任何簡單的方法,它會帶來很多抽象和概括,你將不得不在整個應用程序開發中進行戰鬥。事件採購是走向IMO的唯一途徑 –

+0

我其實只是看了這段視頻,想着同樣的事情;這是否意味着你需要一套DTO/POCO對象來存放ORM保存的數據/持久層,然後使用像AutoMapper這樣的映射器映射到一個域對象?像這樣的事情發生在存儲庫中嗎?看起來像EF Code First這樣的ORM希望得到一個帶有getter和setter的POCO。 – Abe

回答

1

對於AssignedOffers:如果您查看代碼,您會看到AssignedOffers從字段返回值。 NHibernate可以像這樣填充該字段:Map(x => x.AssignedOffers).Access.Field()。

同意使用CQS。

+0

Cool,thanx for NH info! –

+0

但是構造函數呢? –

+0

我沒有在您的示例中看到私有/受保護或內部構造函數。所以NHibernate將使用默認的參數。我只會使用這種類型的構造函數來確保對象被代碼填充,而不是orm。 – Luka

0

在做DDD的第一件事情時,您會忽略持久性問題。 ORM與RDBMS緊密相關,因此它是一個持久性問題。

ORM模型持久性結構不是域。基本上,存儲庫必須將接收到的聚集根「轉換」爲一個或多個持久性實體。有界上下文很重要,因爲根據你想要完成的工作,總根也會變化。

比方說,您希望將會員保存在分配的新優惠的上下文中。然後你就會有這樣的事情(當然這只是一種可能的情形)

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

下一頁回購只會爲了改變NH實體所需的數據,這一切。

關於EVent Sourcing,我認爲您必須查看它是否適合您的域名,並且我沒有看到使用事件源僅用於存儲域集合根的任何問題,而其餘(主要是基礎設施)可以存儲在普通的方式(關係表)。我認爲CQRS在這個問題上給你很大的靈活性。

+0

thanx for answer,但是應該如何加載成員庫提供的存儲庫? –

+0

加載它們用於什麼目的? :) – MikeSW

+0

成員暴露IEnumerable ,所以用於服務類或類似的東西。 –

11

這實際上是一個非常好的問題,也是我所設想的。創建完全封裝的適當域對象(即沒有屬性設置器)並使用ORM直接構建域對象是很困難的。

在我的經驗,有3種方式解決這個問題:

  • 正如已經被提巴尼亞盧卡,NHibernate的支持映射到私人領域,而不是財產setter方法。
  • 如果使用EF(我不認爲支持上述),您可以使用memento pattern將狀態恢復到您的域對象。例如您使用實體框架來填充您的域實體接受設置其私有字段的'紀念'對象。
  • 正如您已經指出的那樣,使用帶事件源的CQRS消除了這個問題。這是我製作完美封裝的域對象的首選方法,也具有事件採購的所有附加好處。
2

舊線程。但是Vaughn Vernon提出的more recent post(2014年末)解決了這種情況,特別是參考了實體框架。鑑於我以某種方式努力尋找此類信息,也許將它發佈在此處可能會有所幫助。

基本上職倡導爲Product域(集合)對象來包裝什麼涉及「數據包」的東西ProductState EF POCO數據對象。當然,域對象仍然會通過特定於域的方法/訪問器來添加其所有豐富的域行爲,但當它必須獲取/設置其屬性時,它會使用內部數據對象。

複製片段直接從後:

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

庫將使用內部構造,以從DB-堅持版本實例(負載)的實體實例。

的一位,我可以添加自己,是可能Product域對象應該是弄髒多一個訪問只是通過EF持續存在的目的:在同是作爲new Product(productState)允許從加載域實體數據庫,相反的方式應該允許通過類似的東西:

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