Person是一个聚合根吗?

Hao*_*Hao 3 c# linq nhibernate domain-driven-design aggregate

由于所有实体都标记了谁创建/修改了记录,我们是否可以将Person实体视为所有实体的聚合根?

也就是说,引用Person的所有实体都将成为Person的集合,例如

public class Person
{
    public virtual int PersonId { get; set; }
    public virtual string Lastname { get; set; }
    public virtual IList<OrderHeader> CreatedOrders { get; set; }
    public virtual IList<OrderHeader> ModifiedOrders { get; set; }

    // Other entities that have a reference on Person will be mapped as a collection under 
    // the Person entity
}


public class OrderHeader
{
    public virtual int OrderId { get; set; }        
    public virtual DateTime OrderDate { get; set; }
    public virtual Customer Customer { get; set; }
    public virtual string CommentsOnThisOrder { get; set; }

    // stamp audit-level concerns
    public virtual Person CreatedBy { get; set; }
    public virtual DateTime DateCreated { get; set; }

    public virtual Person ModifiedBy { get; set; }
    public virtual DateTime DateModified { get; set; }

    public virtual IList<OrderItem> OrderItems { get; set; }
}

public class OrderItem 
{
    public virtual OrderHeader OrderHeader { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal Price { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这基本上会使所有实体成为Person的集合,这违反了DDD聚合根规则.

在我对DDD聚合的有限理解中,OrderHeader不能成为Person的集合,因为我们不应该通过Person保存Order聚合.保存Order聚合(对象图)的唯一*entry*点必须从OrderHeader完成,而不是从Person完成.

现在我的真正目标就是为什么它看起来很脏,我仍然希望Order成为Person的集合:

如果OrderHeader没有作为集合映射到Person,那么有一个ORM*cough*NHibernate*cough*不能从Person到OrderHeader进行LEFT JOIN(.DefaultIfEmpty).从Person到OrderHeader实现LEFT JOIN的唯一方法是将OrderHeader作为集合映射到Person.

我是否应该允许基础设施问题(例如,通过使OrderHeader成为Person的集合,通过Linq的.DefaultIfEmpty 促进从Person到OrderHeader的LEFT JOIN)打破关于何时只有实体必须成为聚合根的规则?

如果我们不将OrderHeader作为集合映射到Person,那么如果我们需要在NHibernate上从Person到OrderHeader进行LEFT JOIN(扁平化结果,不需要分层,因此需要使用LEFT JOIN),那么剩下的唯一选择就是使用QueryOver.与Linq相比,QueryOver编写起来非常繁琐.

如果我们从Person到OrderHeader手动执行LEFT JOIN(即,通过Linq的join和.DefaultIfEmpty),则无法在NHibernate的Linq上使用.DefaultIfEmpty(LEFT JOIN功能).如果我们在NHibernate上进行手动LEFT JOIN,则.DefaultIfEmpty会抛出异常, NHibernate的Linq上的LEFT JOIN必须通过集合和.DefaultIfEmpty来完成.

如果偶尔(按需要)完成,那么打破聚合根的规则是一个务实的选择吗?例如,我应该将OrderHeader作为集合映射到Person,以便通过Linq方便从Person到OrderHeader的LEFT JOIN?

编辑

北风数据库示例.当我们需要向所有客户报告他们的订单,包括那些没有订单的客户(例如,PARIS)

 CustomerID     OrderID
 OTTIK      |   10407
 OTTIK      |   10684
 OTTIK      |   10554
 PARIS      |        
 PERIC      |   10502
 PERIC      |   10474
 PERIC      |   10995
 PERIC      |   10354
 PERIC      |   11073
 PERIC      |   10322
Run Code Online (Sandbox Code Playgroud)

,我们需要做一个LEFT JOIN:

select c.CustomerID, o.OrderID
from customers c
left join orders o on c.CustomerID = o.CustomerID
order by c.CustomerID
Run Code Online (Sandbox Code Playgroud)

这可以通过Linq的join和.DefaultIfEmpty()来完成.但是NHibernate的Linq无法对手动Linq连接的结果做.DefaultIfEmpty,它会引发异常.NHibernate的DefaultIfEmpty只能应用于集合.但我觉得将集合映射到不是聚合根的集合违反了DDD聚合根规则.此外,所有实体(如Person(客户是另一个示例))都可能包含ALL实体的集合,因为每个表都有一个CreatedByPerson引用.

@DanielSchilling:

我也很惊讶(以一种好的方式),从OrderItem引用OrderHeader违反了DDD.我们是否有某种白皮书或Martin Fowler的文章阐述了这一点?我认为从子实体引用父实体不被视为基础设施问题,因此被视为DDD.

Dan*_*ing 5

关于DDD决定的想法......

听起来你想要能够做到这一点:

var people = session.Query<Person>()
    .FetchMany(x => x.CreatedOrders)
    .Where(x => x.Id == personId);
Run Code Online (Sandbox Code Playgroud)

你应该只是翻转查询,就像这样......

var orders = session.Query<OrderHeader>()
    .Fetch(x => x.CreatedBy)
    .Where(x => x.CreatedBy.Id == personId);
Run Code Online (Sandbox Code Playgroud)

除了DDD纯度之外,还有更多的理由可以person.CreatedOrders避开您的模型.如果你有一个人每天每天发出10个订单,一年中的每一天,年复一年怎么办?第一个查询几乎肯定会加载比实际需要更多的数据.第二个查询,即根本不需要person.CreatedOrders集合的查询,可以让您更好地控制要获取的订单数和要获取的数量.

var orders = session.Query<OrderHeader>()
    .Fetch(x => x.CreatedBy)
    .Where(x => x.CreatedBy.Id == personId)
    .Where(x => x.OrderDate >= DateTime.Now.AddYears(-1))
    .Skip(100)
    .Take(50);
Run Code Online (Sandbox Code Playgroud)

一般来说,如果你发现你违反了DDD原则,那通常是你做错事的一个很好的指标,你通常不必太过于寻找其他支持证据.

但是,每隔一段时间,你就必须稍微改变规则,我认为这是可以接受的.例如,我通常只是尝试映射每个关系的一侧 - 这是我真正需要的DDD视角.当引用其他聚合根时,这通常意味着只对many-to-one侧面进行建模,并且对于聚合根中的引用 - 从根到其子节点,这通常意味着只是建模one-to-many并离开many-to-one模型.这意味着要离开OrderItem.OrderHeader模型.

如果您需要了解特定产品的销售量,但仅适用于已提交的订单,该怎么办?最简单的方法就是这样:

var quantitySold = session.Query<OrderItem>()
    .Where(x => x.Product.Id == productId && x.OrderHeader.Submitted)
    .Sum(x => x.Quantity);
Run Code Online (Sandbox Code Playgroud)

所以在这种情况下,我们需要同时模拟one-to-many orderHeader.OrderItemsmany-to-one orderItem.OrderHeader.这是因为这个SUM最有效地直接访问相关的OrderItems,而不是OrderHeader像DDD所指示的那样首先进行访问.我很好.

左外连接在未映射的关系上

忽略问题的DDD部分,可以通过组合两个单独查询的结果来模拟这个LEFT OUTER JOIN - 一个用于获取订单...

var orders = session.Query<OrderHeader>()
    .Fetch(x => x.CreatedBy);
Run Code Online (Sandbox Code Playgroud)

...和另一个让没有订单的人:

var peopleWithNoOrders = session.Query<Person>()
    .Where(p => !session.Query<OrderHeader>().Any(o => o.CreatedBy == p));
Run Code Online (Sandbox Code Playgroud)