由于所有实体都标记了谁创建/修改了记录,我们是否可以将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; }
}
这基本上会使所有实体成为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。
我是否应该允许基础设施问题(例如facilitating通过Linq的.DefaultIfEmpty从Person到OrderHeader的LEFT JOIN,使OrderHeader成为Person的集合)打破关于何时只有实体必须成为聚合根的规则?
如果我们不将OrderHeader作为集合映射到Person,那么如果我们需要在NHibernate上从Person到OrderHeader进行LEFT JOIN(扁平化结果,不需要分层,因此需要使用LEFT JOIN),这是唯一的选择左边是使用QueryOver。与Linq相比,QueryOver编写起来非常繁琐。
如果我们从Person到OrderHeader手动执行LEFT JOIN(即,通过Linq的join和.DefaultIfEmpty),则不能在NHibernate的Linq上使用.DefaultIfEmpty(LEFT JOIN功能)。如果我们执行手动LEFT JOIN,则.DefaultIfEmpty会抛出异常在NHibernate上,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
,我们需要进行LEFT JOIN:
select c.CustomerID, o.OrderID
from customers c
left join orders o on c.CustomerID = o.CustomerID
order by c.CustomerID
可以使用Linq的join和.DefaultIfEmpty()来完成。但是NHibernate的Linq无法对手动Linq连接的结果做.DefaultIfEmpty,它会引发异常。 NHibernate的DefaultIfEmpty只能应用于集合。但我觉得将集合映射到不是聚合根的集合违反了DDD聚合根规则。此外,所有实体(如Person(客户是另一个示例))都可能包含 ALL 实体的集合,因为每个表都有一个CreatedByPerson引用。
@DanielSchilling:
我也很惊讶(以一种好的方式),从OrderItem引用OrderHeader违反了DDD。我们是否有某种白皮书或Martin Fowler的文章阐述了这一点?我认为从子实体引用父实体不被视为基础设施问题,因此被视为DDD。
答案 0 :(得分:5)
听起来你希望能够做到这一点:
var people = session.Query<Person>()
.FetchMany(x => x.CreatedOrders)
.Where(x => x.Id == personId);
你应该只是翻转查询,就像这样......
var orders = session.Query<OrderHeader>()
.Fetch(x => x.CreatedBy)
.Where(x => x.CreatedBy.Id == personId);
除了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);
通常情况下,如果您发现自己违反了DDD原则,通常情况下这是一个很好的指标,表明您做错了什么,而且您通常不必太过于寻找其他支持证据。
但是,每隔一段时间,你就必须稍微改变规则,我认为这是可以接受的。例如,我通常只是尝试映射每个关系的一侧 - 这是我真正需要的DDD视角。在引用其他聚合根时,这通常意味着只对many-to-one
侧进行建模,对于聚合根中的引用 - 从根到其子进行建模,这通常意味着只对one-to-many
进行建模并离开{{ 1}}出模型。这意味着将many-to-one
排除在模型之外。
如果您需要了解特定产品的销售量,但仅针对已提交的订单,该怎么办?最简单的方法就是这样:
OrderItem.OrderHeader
因此,在这种情况下,我们需要对var quantitySold = session.Query<OrderItem>()
.Where(x => x.Product.Id == productId && x.OrderHeader.Submitted)
.Sum(x => x.Quantity);
one-to-many
以及orderHeader.OrderItems
many-to-one
进行建模。这是因为此SUM最有效地直接访问相关的OrderItems,而不是像DDD所指示的那样首先通过orderItem.OrderHeader
。我很好。
忽略问题的DDD部分,可以通过组合两个单独查询的结果来模拟这个LEFT OUTER JOIN - 一个用于获取订单......
OrderHeader
...和另一个让没有订单的人:
var orders = session.Query<OrderHeader>()
.Fetch(x => x.CreatedBy);
答案 1 :(得分:1)
您的Person
可能是汇总,而Order
可能是汇总。一个聚合根实例不应引用另一个实例。尽量不要考虑ORM或数据库。
我可以指出这些答案(它们可能是相关的):
How to avoid holding reference to non-root-aggregate in another non-root-aggregate?