【转】EntityFramework之领域驱动设计实践(五)
原文地址:http://www.cnblogs.com/daxnet/archive/2010/07/07/1772606.html
聚合
聚合(Aggregate)是领域驱动设计中非常重要的一个概念。简单地说,聚合是这样一组领域对象(包括实体和值对象),这组领域对象联合起来表 述一个完整的领域概念。比如,根据Eric Evans《领域驱动设计》一书中的例子,一辆车包含四个轮子,轮子离开“车”就毫无意义,此时这个联合体就是聚合,而“车”就是聚合根 (Aggregate Root)。
从实践中得知,并非领域模型中的每个实体都能够完整地表述一个明确的领域概念,就比如客户与送货地址的关系。假设在某个应用中,系统需要为每个客户 维护多个送货地址,此时送货地址就是一个实体,而不是值对象。那么这样一来,领域模型中至少就有了“客户”和“送货地址”两个实体,而事实上,“送货地 址”是针对“客户”的,离开“客户”,“送货地址”就变得毫无意义。于是,“送货地址”就和“客户”一起,完整地表达了“客户可以有多个送货地址,并能对 它们进行维护”的思想。
在《实体框架之领域驱动实践(三) - 案例:一个简易的销售系统》一文中,我们简单地设计了一个领域模型,其中包含了一些必要的实体和值对象。现在,我用不同颜色的笔在这个领域模型上圈出了三个聚合:客户、订单以及产品分类,如下图所示:
【注意】:如果像上图所示,Category-Item组成一个聚合,那么此时聚合根就应该是Item,而不是Category, 因为Category对Item从概念上并没有包含/被包含的关系,而更多情况下,Category是 Item的一种信息描述,即某个Item是可以归类到某个Category的。在这种情况下,我们不需要对Category进行维护,Category就 以值对象的形式存在于领域模型中。如果是另一种应用场合,比如,我们的系统需要针对Category进行促销,那么我们需要维护Category的信息, 由此Category和Item就分属两个不同的聚合,聚合根为各自本身。
首先是“客户-信用卡”聚合,这个聚合表示了一个客户可以拥有多张信用卡,类似于上面所讲的 “客户-送货地址”的概念;其次是“订单-订单行”的聚合,类似地,虽然订单行也是一个实体,因为在应用中需要对每个订单行进行区分,但是订单行离开订单 就变得毫无意义,它是“订单”概念的一部分;最后是“产品分类-产品”的聚合。
每个聚合都有一个根实体(聚合根,Aggregate Root),这个根实体是聚合所表述的领域概念的主体,外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问。从技术角度考虑,聚合 确定了实体生命周期的关注范围,即当某个实体被创建时,同时需要创建以其为根的整个聚合,而当持久化某个实体时,同样也需要持久化整个聚合。比如,在从外 部持久化机制重建“客户”对象的同时,也需要将其所拥有的“信用卡”赋给“客户”实体(具体如何操作,根据需求而定)。不要去关注聚合内实体的生命周期问 题,如果你真的这么做了,那么你就需要考虑下你的设计是否合理。
由此引出了“领域对象生命周期”的问题,这个问题我会在后面两节单独讨论,但目前至少知道:
- 领域对象从无到有的创建,不是针对某个实体的,而是针对某个聚合的
- 领域对象的持久化(通常所说的“保存”)、重建(通常所说的“查询”)和销毁(通常所说的“删除”)也不是针对某个实体的,而是针对某个聚合的
很可惜,微软的EntityFramework(实体框架,EF)目前并不支持“聚合”的概念,所有的实体都被一股脑地塞到 ObjectContext中:
为了实现聚合的概念,我们又一次地需要用到“部分类(partial class)”的功能。我们首先定义一个IAggregateRoot的接口,修改每个聚合根的实体类,使其实现IAggregateRoot接口,如下:
-
public interface IAggregateRoot
-
{
-
}
-
-----【以下为原文网友评论及回复信息】----- |
[ 2010-1-11 9:22:00 | By: ruson(游客) ] 理论上是很好的,但实践中感觉有些局限性。 以下为blog主人的回复: |
[ 2010-1-16 9:17:00 | By: ruson(游客) ] 你好,按现实中的常规,应该先有分类才有分类下的子项,Item是属于Category下的一个集合,Category优先于Item而存在。 以下为blog主人的回复: |
[ 2010-1-18 15:26:00 | By: ruson(游客) ] 谢谢博主的回复。 以下为 blog主人的回复: |