聚合

聚合(Aggregate)是领域驱动设计中非常重要的一个概念。简单地说,聚合是这样一组领域对象(包括实体和值对象),这组领域对象联合起来表述一个完整的领域概念。比如,根据Eric Evans《领域驱动设计》一书中的例子,一辆车包含四个轮子,轮子离开“车”就毫无意义,此时这个联合体就是聚合,而“车”就是聚合根(Aggregate Root)。

从实践中得知,并非领域模型中的每个实体都能够完整地表述一个明确的领域概念,就比如客户与送货地址的关系。假设在某个应用中,系统需要为每个客户维护多个送货地址,此时送货地址就是一个实体,而不是值对象。那么这样一来,领域模型中至少就有了“客户”和“送货地址”两个实体,而事实上,“送货地址”是针对“客户”的,离开“客户”,“送货地址”就变得毫无意义。于是,“送货地址”就和“客户”一起,完整地表达了“客户可以有多个送货地址,并能对它们进行维护”的思想。

在《实体框架之领域驱动实践(三) - 案例:一个简易的销售系统》一文中,我们简单地设计了一个领域模型,其中包含了一些必要的实体和值对象。现在,我用不同颜色的笔在这个领域模型上圈出了三个聚合:客户、订单以及产品分类,如下图所示:

【注意】:如果像上图所示,Category-Item组成一个聚合,那么此时聚合根就应该是Item,而不是Category,因为Category对Item从概念上并没有包含/被包含的关系,而更多情况下,Category是 Item的一种信息描述,即某个Item是可以归类到某个Category的。在这种情况下,我们不需要对Category进行维护,Category就以值对象的形式存在于领域模型中。如果是另一种应用场合,比如,我们的系统需要针对Category进行促销,那么我们需要维护Category的信息,由此Category和Item就分属两个不同的聚合,聚合根为各自本身。

首先是“客户-信用卡”聚合,这个聚合表示了一个客户可以拥有多张信用卡,类似于上面所讲的 “客户-送货地址”的概念;其次是“订单-订单行”的聚合,类似地,虽然订单行也是一个实体,因为在应用中需要对每个订单行进行区分,但是订单行离开订单就变得毫无意义,它是“订单”概念的一部分;最后是“产品分类-产品”的聚合。

每个聚合都有一个根实体(聚合根,Aggregate Root),这个根实体是聚合所表述的领域概念的主体,外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问。从技术角度考虑,聚合确定了实体生命周期的关注范围,即当某个实体被创建时,同时需要创建以其为根的整个聚合,而当持久化某个实体时,同样也需要持久化整个聚合。比如,在从外部持久化机制重建“客户”对象的同时,也需要将其所拥有的“信用卡”赋给“客户”实体(具体如何操作,根据需求而定)。不要去关注聚合内实体的生命周期问题,如果你真的这么做了,那么你就需要考虑下你的设计是否合理。

由此引出了“领域对象生命周期”的问题,这个问题我会在后面两节单独讨论,但目前至少知道:

  1. 领域对象从无到有的创建,不是针对某个实体的,而是针对某个聚合的
  2. 领域对象的持久化(通常所说的“保存”)、重建(通常所说的“查询”)和销毁(通常所说的“删除”)也不是针对某个实体的,而是针对某个聚合的

很可惜,微软的EntityFramework(实体框架,EF)目前并不支持“聚合”的概念,所有的实体都被一股脑地塞到 ObjectContext中:

为了实现聚合的概念,我们又一次地需要用到“部分类(partial class)”的功能。我们首先定义一个IAggregateRoot的接口,修改每个聚合根的实体类,使其实现IAggregateRoot接口,如下:

隐藏行号 复制代码 ? IAggregateRoot
  1. public interface IAggregateRoot
    
  2. {
    
  3. }
    

隐藏行号 复制代码 ? 聚合根
  1. [AggregateRoot("Orders")]
    
  2. partial class Order : IAggregateRoot
    
  3. {
    
  4.     public Single TotalDiscount
    
  5.     {
    
  6.         get
    
  7.         {
    
  8.             return this.Lines.Sum(p => p.Discount);
    
  9.         }
    
  10.     }
    
  11.     public Single TotalAmount
    
  12.     {
    
  13.         get
    
  14.         {
    
  15.             return this.Lines.Sum(p => p.LineAmount);
    
  16.         }
    
  17.     }
    
  18. }
    

到这里又有问题了,接口IAggregateRoot中什么都没有定义?!我在我的技术博客中,特别解释了C#中接口的三种用途,请参考这篇文章:《C#基础:多功能的接口》。在这里,我们将IAggregateRoot接口用作泛型约束。在看完后续的两篇介绍领域对象生命周期的文章后,你就能够更好地理解这个问题了。事实上,在领域驱动设计的社区中,不少人都是这样用的。

最后说明一下,由于实体框架使所有的实体类继承于EntityObject类,而从面向对象的角度,接口是没办法去继承于类的,因此,在这里我们的 IAggregateRoot接口好像跟实体没什么太大的关系,而事实上聚合根应该是一种实体。在很多领域驱动的项目中,设计人员专门设计了 IEntity接口,所有实现了该接口的类都被认定为实体类,于是,IAggregateRoot接口也就很自然地继承IEntity接口,以表示“聚合根是一种实体”的概念,代码大致如下:

隐藏行号 复制代码 ? IAggregateRoot
  1. public interface IEntity
    
  2. {
    
  3.     Guid Id { get; set; }
    
  4. }
    
  5. public interface IAggregateRoot : IEntity
    
  6. {
    
  7. }
    

总的来说,领域模型需要根据领域概念分成多个聚合,每个聚合都有一个实体作为“聚合根”,通俗地说,领域对象从无到有的创建,以及CRUD操作都应该作用在聚合根上,而不是单独的某个实体。当你的代码需要直接对聚合内部的实体进行CRUD操作时,就说明你的模型设计已经存在问题了。

-----【以下为原文网友评论及回复信息】-----
 

Re:实体框架之领域驱动实践(五)

[ 2010-1-11 9:22:00 | By: ruson(游客) ]

理论上是很好的,但实践中感觉有些局限性。
如上文所说Category和Item是一个聚合,Category是聚合根。那如果一个apsx页中要打开id为1的Item信息,是否还要把 Category的id也传过来。先从数据库中取出Category,再从Category中取Item呢。

以下为blog主人的回复:
你的问题很有价值!
Category可以是聚合根,也可以不是,应该根据实际情况进行考虑。如果我们不需要对Category进行维护,那么将Category和Item划归一个聚合,聚合根应该是Item而不是Category,也就是说,Category应该是Item的一部分,Category作为值对象存在。考虑另外一种应用场合,比如在订单上使用销售折扣,这个折扣可以应用在Item上,也允许应用在Category上(比如某个客户如果买了这个Category 下的Item,那么就按多少的折扣给他),那么此时就不得不去维护Category的信息,于是,Item和Category分属两个不同的聚合,聚合根为其本身。
这里也让我们了解到,实体和值对象没有明确的分界线,只能是设计人员在实践中根据自己的实际经验把握。
【另外,对于文章中给大家造成的不合理引导,我深表歉意,我会在文章中保留原本错误的部分,然后将勘误用橙色笔标注以备参考。也非常欢迎大家能够提出自己的问题与疑惑】

Re:实体框架之领域驱动实践(五)

[ 2010-1-16 9:17:00 | By: ruson(游客) ]

你好,按现实中的常规,应该先有分类才有分类下的子项,Item是属于Category下的一个集合,Category优先于Item而存在。
在Item必需有分类的情况下,Item离开了Category就显得无意义,所以我的想法是Category应该为聚合根。但会遇到上面我所提到的问题。以及如果只想对其中某一个Item进行CURD时还要先取出来整个聚合根的话对性能的影响有些疑问。
谢谢。

以下为blog主人的回复:
你好!这个问题确实让人难以理解,开始的时候,我承认我自己也没有经过深思熟虑就把结论写在这里,造成很多朋友的误解,再次深表歉意。
由于你现在需要对Item进行CRUD,更“领域”一点讲,你需要对Item进行持久化的操作,那么Item就一定是聚合根,那么它是哪个聚合的聚合根?就需要看应用本身的需求了。
同理,至于Category和Item是否属于同一个聚合,以及Category是否是聚合根,也要根据实际需求而定。DDD中所讨论的聚合应该是组合聚合(Composite Aggregation),而不是可共享聚合(Shareable Aggregation),因为在创建聚合的同时也需要创建聚合内部的成员。于是,从语义上讲,子部件无法脱离聚合而单独存在(就像汽车与方向盘、轮子的关系那样)。但事实上呢?现实生活中,物品就是物品,可以不给它们归类,也可以将它们分属于不同的类别,但归类也好,不归类也好,物品都是客观存在的。不因为你不给它归类,物品就消失了。
因此,在做建模的时候,我们可能需要更加注重实际应用与我们模型的距离,以便更加真实客观地反映问题本身。DDD是实践指导,不是理论,就像对待设计模式一样,我们所能做的只是借鉴,而不是照搬。
希望我的解答能够为你提供帮助。

Re:实体框架之领域驱动实践(五)

[ 2010-1-18 15:26:00 | By: ruson(游客) ]

谢谢博主的回复。
这段时间在尝试NHibernate + Spring.NET下的DDD实践。

以下为 blog主人的回复:
共同探讨,共同进步。
2008年的时候我尝试过Spring.NET+Castle ActiveRecord的DDD实践。当时我选用Castle ActiveRecord的原因是因为样品很简单,我不打算去维护NHibernate复杂的mapping XML。其实ActiveRecord是夹在Transaction Script和Domain Model之间的DDD的反模式,DDD社区中不少人指出这种做法不妥,但我觉得只要适合我的实际情况,也没什么大碍。
与Spring.NET一样,NHibernate也是一种解耦的手段。Spring.NET解耦了对象之间的依赖性,而NHibernate则解耦了对象模型与数据库模型之间的映射关系。两者目的相同:提高系统的延展性和扩充性。

转载于:https://www.cnblogs.com/daxnet/archive/2010/07/07/1772606.html

EntityFramework之领域驱动设计实践(五)相关推荐

  1. EntityFramework之领域驱动设计实践(十)(转)

    http://www.cnblogs.com/daxnet/archive/2010/07/19/1780764.html 规约(Specification)模式 本来针对规约模式的讨论,我并没有想将 ...

  2. 【转】EntityFramework之领域驱动设计实践(三)

    原文地址:http://www.cnblogs.com/daxnet/archive/2010/07/07/1772593.html 案例:一个简易的销售系统 从现在开始,我们将以一个简易的销售系统为 ...

  3. EntityFramework之领域驱动设计实践(八)

    仓储的实现:基本篇 我们先从技术角度考虑仓储的问题.实体框架(EntityFramework)中,操作数据库是非常简单的:在ObjectContext中使用LINQ to Entities即可完成操作 ...

  4. 【DDD】领域驱动设计实践 —— UI层实现

    前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来.本文是架构实现讲解的第一篇,主要 ...

  5. 苏宁金融会员领域驱动设计实践

    苏宁金融会员领域驱动设计实践 背景介绍 近年来,苏宁集团业务不断扩大,用户快速增长,线上线下融合不断深入,系统的复杂性越来越高,技术的广度和深度都在不断拓展. 在整个集团技术不断迭代演进的过程中,集团 ...

  6. 领域驱动设计实践(一)(转)

    分层构架 在分析领域驱动设计之前,我们需要先回顾以前的分层架构."层"是一种体系结构模式,也是被广大软件从业人员用的最为广泛而且最为灵活的模式之一.其中最为大家所熟知的就是三层架构 ...

  7. 领域驱动设计实践框架-COLA的解读

    引言 Cola作为当前比较优秀的领域驱动设计最佳实践框架越来越被更多的技术人所知晓.先抛出COLA 4.0:应用架构的最佳实践_张建飞(Frank)的博客-CSDN博客_cola架构 是关于COLA4 ...

  8. 薪水支付系统领域驱动设计实践

    (一)    通过领域驱动设计想解决什么问题 一个系统,按理说针对的行业是固定的,业务也比较接近,可是每次换个用户,总感觉需求变化一点点,系统却要改得天翻地覆.修改后的代码让不忍直视,心里暗暗嘀咕,神 ...

  9. 领域驱动设计实践:还是图书馆借书的例子

    去年开始博客园和Jdon有一场DDD的讨论,是关于如何给一个图书馆的应用系统建模.大概是在讨论几个经典的Use Case:办卡.持卡借书和还书. 讨论最开始由博客园的张逸大牛发起(链接在此),给出了一 ...

最新文章

  1. 关于 $'\r': 未找到命令的解决(bad interpreter )
  2. Python3 内置http.client,urllib.request及三方库requests发送请求对比
  3. 【FPGA教程案例2】基于vivado核的NCO正弦余弦发生器设计与实现
  4. Ubuntu15.0.4下Torch安装教程
  5. 微型计算机,单片机和单板机是,9、微型计算机、单片机和单板机是()
  6. CSS 修饰图片的5种方法
  7. Linux 下shell编程
  8. 我的docker随笔29:oracle数据库部署
  9. Matlab Tricks(十四)—— 某一行/列是否在一个矩阵中(ismember)
  10. Java进阶:SpringMVC中通过监听器将Spring上下文对象放置到servletContext中,方便其他地方使用
  11. RSA签名和验签Util
  12. 记事本怎么运行c语言代码,如何让记事本里的代码运行
  13. cad如何多选对象_CAD快速选择和选择类似对象怎么用
  14. c语言遍历算法的头文件,图优先遍历算法(C语言版).doc
  15. Caution: request is not finished yet
  16. 美服行星边际2服务器维护时间,行星边际2美服证书 快速起步攻略新手必读
  17. 手把手教你用Java实现一个“网易云音乐”
  18. 每个Java程序员都应该Follow的10个Twitter账号
  19. 根据具体日期计算是一年的第几周和当月的第几周
  20. postman点击一次连续发送多个请求

热门文章

  1. 自主可控国产服务器思考
  2. 浅谈Web App前端设计原则
  3. 三端双向可控硅(triac)
  4. linux看不到进程管理,关于Linux下进程的详解【进程查看与管理】
  5. 【2017百度之星程序设计大赛 - 初赛(B)】度度熊的交易计划
  6. 初识C语言——冒泡排序法
  7. VBA实战(09) - 工作簿(WorkBook)
  8. Vue:el-dialog可拖拽
  9. CAJ如何转化为PDF文件
  10. 安全测试简述/安全审计工具