2012-06-27 28 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);