DDD实战课
欧创新
人保高级架构师
立即订阅
4865 人已学习
课程目录
已完结 23 讲
0/2登录后,你可以任选2讲全文学习。
开篇词 (1讲)
开篇词 | 学好了DDD,你能做什么?
免费
基础篇 (5讲)
01 | 领域驱动设计:微服务设计为什么要选择DDD?
02 | 领域、子域、核心域、通用域和支撑域:傻傻分不清?
03 | 限界上下文:定义领域边界的利器
04 | 实体和值对象:从领域模型的基础单元看系统设计
05 | 聚合和聚合根:怎样设计聚合?
进阶篇 (6讲)
06 | 领域事件:解耦微服务的关键
07 | DDD分层架构:有效降低层与层之间的依赖
08 | 微服务架构模型:几种常见模型的对比和分析
09 | 中台:数字转型后到底应该共享什么?
10 | DDD、中台和微服务:它们是如何协作的?
答疑:有关3个典型问题的讲解
实战篇 (10讲)
11 | DDD实践:如何用DDD重构中台业务模型?
12 | 领域建模:如何用事件风暴构建领域模型?
13 | 代码模型(上):如何使用DDD设计微服务代码模型?
14 | 代码模型(下):如何保证领域模型与代码模型的一致性?
15 | 边界:微服务的各种边界在架构演进中的作用?
16 | 视图:如何实现服务和数据在微服务各层的协作?
17 | 从后端到前端:微服务后,前端如何设计?
18 | 知识点串讲:基于DDD的微服务设计实例
19 | 总结(一):微服务设计和拆分要坚持哪些原则?
20 | 总结(二):分布式架构关键设计10问
结束语 (1讲)
结束语 | 所谓高手,就是跨过坑和大海!
DDD实战课
登录|注册

05 | 聚合和聚合根:怎样设计聚合?

欧创新 2019-10-23
你好,我是欧创新。今天我们来学习聚合(Aggregate)和聚合根(AggregateRoot)。
我们先回顾下上一讲,在事件风暴中,我们会根据一些业务操作和行为找出实体(Entity)或值对象(ValueObject),进而将业务关联紧密的实体和值对象进行组合,构成聚合,再根据业务语义将多个聚合划定到同一个限界上下文(Bounded Context)中,并在限界上下文内完成领域建模。
那你知道为什么要在限界上下文和实体之间增加聚合和聚合根这两个概念吗?它们的作用是什么?怎么设计聚合?这就是我们这一讲重点要关注的问题。

聚合

在 DDD 中,实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。
那聚合在其中起什么作用呢?
举个例子。社会是由一个个的个体组成的,象征着我们每一个人。随着社会的发展,慢慢出现了社团、机构、部门等组织,我们开始从个人变成了组织的一员,大家可以协同一致的工作,朝着一个最大的目标前进,发挥出更大的力量。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《DDD实战课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(44)

  • 陈华应 置顶
    老师,麻烦有空帮忙看一下
    场景:电销
    根据任务类型的属性创建具体的定期执行任务,调度器把到点的任务放到执行器里去执行。执行完了等待下一次执行,对任务生成的明细可以填写沟通记录(第三方服务)但是本服务要提供此字段查询
    过程中记录统计日志。每个任务类型在查询他的任务时查询和列表展示字段集合都不一样
    任务执行过程:从数据源获取数据 -> 根据任务配置的过滤规则过滤 -> 生成任务明细(有过期时间) -> 1.自动分配给销售/2.销售主动去领单,过程中生成任务执行日志,统计日志

    领域对象:任务类型、任务、任务明细、日志、过滤规则、字段、销售、
    聚合A:聚合根-任务,任务明细-实体,值对象:日志、任务类型、销售
    聚合B:聚合根-过滤规则,
    聚合C:聚合根-字段,值对象,展示字段属性定义集合、查询字段属性定义集合

    问题:
      1.一个聚合中,允不允许只有一个实体和一些属性值?
      2.是否合理?不合理该怎么设计呢?
      3.任务是根据任务类型创建出来的,聚合A是不是不合理?或者实体的初始化可不可以依赖它的值对象呢?
      4.任务类型决定它可以进行哪些过滤,实际创建任务时才会真正选择使用哪些过滤规则,过滤规则算什么呢?能作为一个独立的聚合吗?
      5.字段也是同样的,并且字段是没有一个生命周期的,这种情况下是不是作为值对象更合理? 但是聚合A和B都用到了字段,值对象能在多个聚合公用吗?

    作者回复: 我先理解一下你说的业务场景哈。不知道理解的对不对?不对的地方请你指出。由于不好展示事件风暴的过程,我就口述吧。
    你描述的业务场景主要包括这两个部分吧。
    流程一:创建任务
    1、根据任务规则获取任务基础数据,生成任务。(产生任务已创建事件,领域对象包括:任务生成的基础数据、数据过滤规则、任务、任务类型)
    2、自动将任务分配给销售。(销售的数据应该来源于其他系统,这个过程实际上是一个给任务赋值的过程,领域对象包括:销售)
    3、销售领取任务。(给任务分配销售)
    这个过程领域对象包括:基础数据、数据过滤规则、任务、任务类型、销售。
    命令有:创建任务,给任务分配销售。
    领域事件有:任务已创建。

    流程二、任务执行
    1、销售查询并获取任务,执行任务。(不清楚你说的字段在这个过程是什么含义,是查询时勾选类型吗)
    2、任务执行完成后,记录执行结果,产生任务执行日志。(产生任务执行日志已创建事件,领域对象有:任务、任务日志)
    3、统计日志。这一块不清楚你的业务逻辑和流程。
    因为有统计日志,是不是就会去查询任务的执行日志,如果是这样的话,任务日志就需要设计为实体,跟任务关联,这里任务是聚合根。
    这个阶段的领域对象有:任务和任务执行日志。
    命令有:查询任务,生成任务执行日志。
    领域事件有:执行任务日志已创建。

    结合这两个流程,我们整体来分析一下。
    领域对象包括:基础数据、数据过滤规则、任务、销售、任务类型、任务执行日志。
    由于销售人员数据来源于第三方,以值的形式存储在任务中,因此我们可以将它设计为任务的值对象。
    任务执行日志依附于任务,但是由于它后续要做查询和统计分析,因此将它设计为被任务引用的实体。
    任务类型是任务的值对象
    其它的基础数据、数据过滤规则这两个领域对象很独立,你可以理解他们是独立的实体,或者说一个实体就是一个聚合。
    但是这样设计在代码目录设计时会显得比较单薄,一个聚合会有一个仓储和聚合自己的代码目录结构。因此我们可以将这两个实体可以直接放在任务的聚合里,但是他们的生命周期不受任务这个聚合根管理。
    这样的话,我们就可以只建立一个任务聚合。这个聚合的聚合根是任务。它引用的实体包括任务执行日志,任务的值对象有:销售、任务类型。还有两个独立实体:基础数据和数据过滤规则。
    说明一下:
    在不少的数据统计和计算场景中,有很多实体之间相互独立,只参与计算和统计分析,但是这类场景中业务内聚性又很高,你找不出管理这些实体的聚合根。我称这种业务模型是非典型领域模型。虽然有些方面(比如聚合根)不符合DDD的一些原则,但是我们也可以按照DDD方法来完成设计。

    2019-10-23
    4
    22
  • 蜗牛慢慢爬
    听了这么多节课,总结一下还是太抽象了

    作者回复: 基础篇主要讲解DDD的基础概念和设计理念,所以相对抽象一些。后面会有中台业务建模和微服务设计案例,比较好理解。敬请期待。

    2019-10-23
    4
  • 渊虹
    老师,有个问题不明白,麻烦解惑。投保聚合和客户聚合中,投保人和被保人跨聚合引用到客户的id,需求是查询以客户s为被保人的保单。就需要跨过聚合根,直接访问被保人这个值对象。这个是不是和只能通过聚合根访问聚合内其他对象的理论不一致

    作者回复: 被保人这个值对象以属性嵌入的方式嵌入保单聚合根中,查询客户保单时你不需要到客户聚合去查询客户信息了,直接根据客户信息在投保聚合查保单就可以了,当然这个客户信息不只是ID。
    说明一下:这个跨聚合引用是在生成保单的时候,通过客户聚合根查询获取的客户信息,从客户聚合获取客户信息后,客户的信息就作为值对象的值嵌入到了保单实体中。

    2019-10-24
    1
    3
  • 祥敏
    您好,第四讲和第五讲都在讲述微服务内部的拆分与设计,是关系紧密的两讲,反复听了有三四遍略有收获和疑问。

    从业务顺延的角度拆分,聚合、聚合根、实体、值对象这四个概念很好的模拟了业务的世界,就像面向对象所讲的一切皆对象,对象与对象之间的关系(聚合)。

    疑惑有两点:持久化和实体之间关系的灵活性。

    持久化(以关系型数据库为例):聚合内部强一致性,聚合之间最终一致性。聚合是由多个遵循一定规则的实体组成,实体的描述由值对象组成,这样的问题在于聚合、实体、值对象和数据库之间的对应关系不够直接,同时事务管理也比非DDD的方式复杂,如果严格遵循DDD的原则去做,可能会在对象->数据库这个关节会有较多的问题要解决。

    实体之间的关系:跨聚合之间实体不能直接发生关系,这个是否会不够灵活,实践中是否会引发一些问题?

    作者回复: 如果数据一致性没什么问题,那就选择自己最合适的方式去做吧。但是一定要记住,边界要清晰,聚合之间的服务和数据不要耦合,耦合度太高,以后领域模型和微服务演进就很难。

    2019-11-01
    2
  • 胖虎
    老师,能用电商的例子说一下聚合根的使用场景嘛

    作者回复: 电商里面比较典型的几个聚合根,比如:库存、商品、订单等等。
    以订单为例,订单在聚合里是聚合根,与订单关联的有订单明细和收货地址。订单明细包括商品ID,商品名称,价格以及数量等信息,由于订单明细是多个,它是一个集合,它被设计为实体,被订单引用。而订单只有一个收货地址,这个收货地址的值来源于你个人中心维护的收货地址,收货地址只能被整体替换,所以它被设计为值对象。

    2019-10-31
    2
    2
  • 密码123456
    聚合。用界限上下文把细粒度的实体圈起来当做一个组织,选出组织的董事长。一个组织和另外一个组织交流的时候,只需要通过一个董事长,能够了解该组织的全部非隐私信息

    作者回复: 类比的不错。

    2019-10-23
    2
  • 美美
    老师,想请教一下
    我们的场景是:商家后台上,商家可以进行门店管理,收银pos机相关的设置,营业相关的设置
    背景:商家后台已经存在,且商家基础系统、门店基础信息、pos相关信息,数据模型已经存在,各调用方是直接访问数据库的
    目标:将商家、门店的基础信息,经营设置信息,抽象出来形成商家域 统一对外提供能力

    想通过DDD的思想来进行建模,感觉无从下手,麻烦老师提供点思路

    作者回复: 后面会有建模方法的介绍。
    你可以试着从用户旅程入手,根据流程看看会发生哪些领域事件,再找找是哪些命令触发这些领域事件的。梳理的差不多的时候,你就找找这些命令都是哪些实体的行为,这样就可以找出好多实体。根据实体可以找出聚合根,再根据聚合根找出关联的实体和值对象。这样聚合就找到了,然后对聚合划分限界上下文。这样就可以设计微服务了。

    2019-11-06
    1
  • zj
    一个聚合可能对应一堆实体及值对象,一个聚合对应一个仓储也即数据持久化单元。那如何实现聚合到持久化单元呢

    作者回复: 它是通过仓储实现的。
    由于领域模型中的DO实体与数据模型中的PO不一定一一对应,所以聚合里的实体在持久化时,会有一个DO到PO的转换过程。仓储持久化的组织者是聚合根。具体的实现你可以用MyBatis等组件,目前来讲现在的几个主流持久化组件,在仓储实现持久化方面使用起来还不是特别的顺手。
    在某些不强调数据一致性的聚合中,个人感觉你可以突破聚合根,按照你习惯的方式去完成数据持久化。

    2019-11-01
    1
  • 杨杰
    “一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了“
              目前我们在微服务内部,对聚合的要求降低了。也就是说一个微服务内部的数据结构是随便互相访问的,实体也是贫血的模型。主要是考虑:服务层已经分为微服务层,聚合服务层(BFF层)了,微服务之间通过消息来解耦;如果微服务内部再分层,引入领域事件,感觉有点儿太复杂了。不知道这样合适不合适?

    作者回复: 因为微服务的架构演进,会有功能和代码的拆分和重构的过程。而一般来讲聚合的内聚性很高,聚合内的功能相对稳定,我们可以聚合功能和代码为单位在不同的微服务之间进行功能和代码的重构。
    如果聚合之间代码和业务边界不清晰,聚合之间数据和服务可以随便访问,就会因为耦合度过高,代码很难剥离,最后微服务架构演进时又要在走一遍从单体拆分微服务的过程。后面的章节我会专门讲微服务架构的演进。

    2019-10-23
    1
  • 守候、
    老师,就基础篇。我的理解是基于事件风暴(头脑风暴)定义出实体与值对象,并能识别出根对象,进而得到聚合,并清晰领域边界、松耦合。从而达到服务化确定服务的边界!但是这其中事件风暴如何组织进行是否有策略或者说工具技术。如果团队成员就事件风暴无法达成共识,如何进一步推进?

    作者回复: 你好,事件风暴会有专门的一节介绍。

    2019-10-23
    1
  • 三木子
    什么是充血模型

    作者回复: 说到充血模型,就离不开贫血模型。
    先说一下贫血模型吧,贫血模型是指使用的领域对象中只有setter和getter方法,所有的业务逻辑都不包含在领域对象中而是放在业务逻辑层。
    而充血模型将大多数业务逻辑放在领域实体中实现,实体本身包含了属性和它的业务行为,它在领域模型中就是一个具有业务行为和逻辑的基本业务单元。

    2019-10-23
    2
    1
  • Geek_88604f
    聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。这两者有什么区别呢,老师?

    作者回复: 主要还是为了解耦,持久化时是以聚合为单位进行持久化。

    2019-12-01
  • Geek_88604f
    聚合A的对象a需要访问聚合B的对象b,需要经过聚合B的聚合根,这个很容易理解。疑问的地方是对象a为什么没有经过自己的聚合根呢?这个不符合常识,举个例子,a和b分别是团队A和B的成员,a要和b配合完成某个事务,a要和b的主管协商,同时a还要和自己的主管协商,不然自己的主管不认可怎么办,白干了!

    作者回复: 生活中是这样哈。聚合根的主要目标是为了保证聚合内的业务规则和数据一致性。其实你也可以通过聚合A的聚合根来使用聚合B的数据的。

    2019-11-29
  • Geek_aa8017
    老师,一个聚合可以有实体和领域服务,应用层调用聚合是调用聚合根还是领域服务呢

    作者回复: 看你的领域服务放在在聚合根类内还是单独设计一个领域服务类,领域服务类内实现所有的领域服务。两者都可以。第一种方式调聚合根内的方法,第二种方式调领域服务类内的方法。他们都可以实现领域服务。但我建议你用第二种方法,这样边界和定位相对比较清晰。

    2019-11-21
  • 八百
    1、如果保费与报价规则一对多(保费的计算由多个规则计算得到,不是仅一个规则),现在有2种类型规则:vip打七折、已消费年保费大于1千元减200, 但是如果判断vip和已消费的年保费依赖外部系统的rpc接口,那么外部系统的调用能放在投保单这个聚合的规则实体中吗,因为我好像看到外部系统的调用,应该放在四层结构的应用层中
    2、如果采用应用层调用外部系统的接口,那么是否是vip,和已消费年保费,是当做参数 传递到 领域层的投保单聚合中吗?如果是这样的,如果规则类型很多,需要传递的参数是否太多了

    作者回复: 1、外部调用通过应用服务。
    2、放应用层和放领域层所需要的参数的应该差异不大吧。前一个领域服务处理后,参数传给第二个领域服务而已。

    2019-11-18
    1
  • Benjamn
    这篇文章从光听,到看文字,起码看了5,6遍(很多同学反映比较抽象,我也感觉是这样的)。希望欧老师以后可以直接从案例入手,通过详细解析一个案例,从而逐步带出对“领域”、“实体”、“值对象”、“聚合”还有“聚合根”的解释和含义,这样对于我们初学者来说 可能会更加容易理解:)

    作者回复: 谢谢提醒。后面章节在讲案例的时候,会有具体的分析过程。

    2019-11-12
  • xj_zh
    欧老师,你好,有两个问题想问一下。

    1. 添加用户

    从客户端传过来用户基础信息,这些内容是没有id标识的,是属于值对象范畴吗?

    2. 更新用户信息

    更新的时候会从客户端传过来用户id标识,和要更新的用户信息,那么这个对象属于实体对象吗?

    应该怎么区分这两者之间的关系。谢谢!

    作者回复: 这个用户只是传输过程中的DTO的数据。不是领域模型中DO对象。是实体还是值对象要看DO的属性和特征。

    2019-11-08
  • 甜甜圈
    老师,聚合内的领域服务,是直接可以被应用层调用还是需要通过聚合根类定义的方法来调用?

    作者回复: 通过应用服务来调用。主要是为了避免泄露领域逻辑。

    2019-11-08
  • 骆驼1089
    老师,这篇文章里面的关于 实体 和 值对象的 标识,是标注错了吗?

    作者回复: 应该没错。你是指哪个地方有问题呢?

    2019-11-07
    1
  • golang
    “应避免跨聚合的领域服务调用和跨聚合的数据库表关联”与“通过唯一标识引用其它聚合”是不是有冲突呢?我认为在表中应该存储领域之间的关联ID

    作者回复: 为什么要尽量避免跨聚合的领域服务调用和数据库表关联呢?我是这么考虑的,保持聚合之间的松耦合,如果出现领域服务或者数据表之间的关联,聚合之间就会产生耦合。如果以后聚合需要拆分到其它微服务,这个过程就很复杂了。通过唯一标识引用,你看可以通过应用服务来实现。在微服务演进过程中,聚合的重组,只需要在应用层做解耦就可以了。

    2019-11-06
收起评论
44
返回
顶部