用行为和ORM丰富的域模型(Rich domain model with behaviours an

2019-07-30 03:29发布

看着NDC12呈现从吉米博加德(“手工艺邪恶的领域模型”后http://ndcoslo.oktaset.com/Agenda ),我是游荡如何坚持那种域模型。
这是从演示示例类:

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

所以有包含在该域模型的一些规则:
- 会员必须有姓和名
- 报价数量不能改变外
- 会员负责创建新的报价,计算其价值和分配

如果如果试图将此映射到像实体框架或NHibernate的一些ORM,它不会工作。 那么,什么是对这种模型与ORM映射到数据库最好的方法?
例如,如何从数据库加载AssignedOffers如果没有二传手?

唯一的事情,它对于我来说是使用命令/查询体系结构意义:查询始终与DTO做的结果,而不是域实体,并命令在域模型来完成。 此外,事件采购是非常适合的领域模型的行为。 但这种CQS架构不适合,也许每一个项目,特别是棕地。 或不?

我知道这里类似的问题,但找不到具体的例子和解决方案。

Answer 1:

其实,这是一个很好的问题,这是我已经考虑。 这是潜在的困难,以创建一个完全封装(即无属性setter)正确的域对象,并使用ORM直接建立域对象。

在我的经验,有解决这个问题的3种方式:

  • 正如卢卡已经提到,NHibernate的支持映射到私人领域,而不是财产setter方法。
  • 如果使用EF(我不认为支持以上),你可以使用Memento模式来恢复状态,以你的域对象。 如您使用实体框架来填充“纪念品”的对象,你的域实体接受设置自己的私人领域。
  • 正如你所指出的那样,使用CQRS与事件采购解决了这个问题。 这是我的首选各具特色的完美封装领域对象,也有事件采购的全部增值收益的方法。


Answer 2:

旧线。 但是,有一个更近的职位 (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);


Answer 3:

对于AssignedOffers:如果你看一下代码,你会看到AssignedOffers从现场返回的值。 NHibernate的可以填充该字段是这样的:地图(X => x.AssignedOffers).Access.Field()。

使用CQS同意。



Answer 4:

在做DDD第一件事情,你忽略了持续性的担忧。 所以这是一个持续关注ORM是tighlty连接到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实体所需的数据,这一切。

有关事件采购,我认为你必须看它是否适合你的域名,我看不出有任何问题,使用事件采购仅用于存储域聚合根而其余的(主要是基础设施)可以存储在普通的方式(关系表)。 我想CQRS让你在这个问题上很大的灵活性。



文章来源: Rich domain model with behaviours and ORM