极客时间已完结课程限时免费阅读

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

05 | 聚合和聚合根:怎样设计聚合?-极客时间

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

讲述:欧创新

时长13:21大小10.68M

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

聚合

在 DDD 中,实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。
那聚合在其中起什么作用呢?
举个例子。社会是由一个个的个体组成的,象征着我们每一个人。随着社会的发展,慢慢出现了社团、机构、部门等组织,我们开始从个人变成了组织的一员,大家可以协同一致的工作,朝着一个最大的目标前进,发挥出更大的力量。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是“高内聚、低耦合”的。
聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。聚合内实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。

聚合根

聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。
传统数据模型中的每一个实体都是对等的,如果任由实体进行无控制地调用和数据修改,很可能会导致实体之间数据逻辑的不一致。而如果采用锁的方式则会增加软件的复杂度,也会降低系统的性能。
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

怎样设计聚合?

DDD 领域建模通常采用事件风暴,它通常采用用例分析、场景分析和用户旅程分析等方法,通过头脑风暴列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理领域对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。
下面我们以保险的投保业务场景为例,看一下聚合的构建过程主要都包括哪些步骤。
第 1 步:采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单、标的、客户、被保人等等。
第 2 步:从众多实体中选出适合作为对象管理者的根实体,也就是聚合根。判断一个实体是否是聚合根,你可以结合以下场景分析:是否有独立的生命周期?是否有全局唯一 ID?是否可以创建或修改其它对象?是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体。
第 3 步:根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。在图中我们构建了客户和投保这两个聚合。
第 4 步:在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。这里我需要说明一下:投保人和被保人的数据,是通过关联客户 ID 从客户聚合中获取的,在投保聚合里它们是投保单的值对象,这些值对象的数据是客户的冗余数据,即使未来客户聚合的数据发生了变更,也不会影响投保单的值对象数据。从图中我们还可以看出实体之间的引用关系,比如在投保聚合里投保单聚合根引用了报价单实体,报价单实体则引用了报价规则子实体。
第 5 步:多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。
这就是一个聚合诞生的完整过程了。

聚合的一些设计原则

我们不妨先看一下《实现领域驱动设计》一书中对聚合设计原则的描述,原文是有点不太好理解的,我来给你解释一下。
1. 在一致性边界内建模真正的不变条件。聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
2. 设计小聚合。如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
3. 通过唯一标识引用其它聚合。聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
4. 在边界之外使用最终一致性。聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦(相关内容我会在领域事件部分详解)。
5. 通过应用层实现跨聚合的服务调用。为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。
上面的这些原则是 DDD 的一些通用的设计原则,还是那句话:“适合自己的才是最好的。”在系统设计过程时,你一定要考虑项目的具体情况,如果面临使用的便利性、高性能要求、技术能力缺失和全局事务管理等影响因素,这些原则也并不是不能突破的,总之一切以解决实际问题为出发点。

总结

[第 04 讲] 和 [第 05 讲] 的内容,其实是有强关联的。我们不妨在这里总结下聚合、聚合根、实体和值对象它们之间的联系和区别。
聚合的特点:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。
一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了。
聚合根的特点:聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。
实体的特点:有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
值对象的特点:无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。

思考题

请你结合公司的某个业务场景,试试能分析出哪些聚合?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进阶。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 38

提建议

上一篇
04 | 实体和值对象:从领域模型的基础单元看系统设计
下一篇
06 | 领域事件:解耦微服务的关键
 写留言

精选留言(155)

  • 南山
    置顶
    2019-10-23
    老师,麻烦有空帮忙看一下 场景:电销 根据任务类型的属性创建具体的定期执行任务,调度器把到点的任务放到执行器里去执行。执行完了等待下一次执行,对任务生成的明细可以填写沟通记录(第三方服务)但是本服务要提供此字段查询 过程中记录统计日志。每个任务类型在查询他的任务时查询和列表展示字段集合都不一样 任务执行过程:从数据源获取数据 -> 根据任务配置的过滤规则过滤 -> 生成任务明细(有过期时间) -> 1.自动分配给销售/2.销售主动去领单,过程中生成任务执行日志,统计日志 领域对象:任务类型、任务、任务明细、日志、过滤规则、字段、销售、 聚合A:聚合根-任务,任务明细-实体,值对象:日志、任务类型、销售 聚合B:聚合根-过滤规则, 聚合C:聚合根-字段,值对象,展示字段属性定义集合、查询字段属性定义集合 问题: 1.一个聚合中,允不允许只有一个实体和一些属性值? 2.是否合理?不合理该怎么设计呢? 3.任务是根据任务类型创建出来的,聚合A是不是不合理?或者实体的初始化可不可以依赖它的值对象呢? 4.任务类型决定它可以进行哪些过滤,实际创建任务时才会真正选择使用哪些过滤规则,过滤规则算什么呢?能作为一个独立的聚合吗? 5.字段也是同样的,并且字段是没有一个生命周期的,这种情况下是不是作为值对象更合理? 但是聚合A和B都用到了字段,值对象能在多个聚合公用吗?
    展开

    作者回复: 我先理解一下你说的业务场景哈。不知道理解的对不对?不对的地方请你指出。由于不好展示事件风暴的过程,我就口述吧。 你描述的业务场景主要包括这两个部分吧。 流程一:创建任务 1、根据任务规则获取任务基础数据,生成任务。(产生任务已创建事件,领域对象包括:任务生成的基础数据、数据过滤规则、任务、任务类型) 2、自动将任务分配给销售。(销售的数据应该来源于其他系统,这个过程实际上是一个给任务赋值的过程,领域对象包括:销售) 3、销售领取任务。(给任务分配销售) 这个过程领域对象包括:基础数据、数据过滤规则、任务、任务类型、销售。 命令有:创建任务,给任务分配销售。 领域事件有:任务已创建。 流程二、任务执行 1、销售查询并获取任务,执行任务。(不清楚你说的字段在这个过程是什么含义,是查询时勾选类型吗) 2、任务执行完成后,记录执行结果,产生任务执行日志。(产生任务执行日志已创建事件,领域对象有:任务、任务日志) 3、统计日志。这一块不清楚你的业务逻辑和流程。 因为有统计日志,是不是就会去查询任务的执行日志,如果是这样的话,任务日志就需要设计为实体,跟任务关联,这里任务是聚合根。 这个阶段的领域对象有:任务和任务执行日志。 命令有:查询任务,生成任务执行日志。 领域事件有:执行任务日志已创建。 结合这两个流程,我们整体来分析一下。 领域对象包括:基础数据、数据过滤规则、任务、销售、任务类型、任务执行日志。 由于销售人员数据来源于第三方,以值的形式存储在任务中,因此我们可以将它设计为任务的值对象。 任务执行日志依附于任务,但是由于它后续要做查询和统计分析,因此将它设计为被任务引用的实体。 任务类型是任务的值对象 其它的基础数据、数据过滤规则这两个领域对象很独立,你可以理解他们是独立的实体,或者说一个实体就是一个聚合。 但是这样设计在代码目录设计时会显得比较单薄,一个聚合会有一个仓储和聚合自己的代码目录结构。因此我们可以将这两个实体可以直接放在任务的聚合里,但是他们的生命周期不受任务这个聚合根管理。 这样的话,我们就可以只建立一个任务聚合。这个聚合的聚合根是任务。它引用的实体包括任务执行日志,任务的值对象有:销售、任务类型。还有两个独立实体:基础数据和数据过滤规则。 说明一下: 在不少的数据统计和计算场景中,有很多实体之间相互独立,只参与计算和统计分析,但是这类场景中业务内聚性又很高,你找不出管理这些实体的聚合根。我称这种业务模型是非典型领域模型。虽然有些方面(比如聚合根)不符合DDD的一些原则,但是我们也可以按照DDD方法来完成设计。

    共 6 条评论
    125
  • 胖虎
    2019-10-31
    老师,能用电商的例子说一下聚合根的使用场景嘛

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

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

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

    共 12 条评论
    23
  • stg609
    2019-10-29
    首先,几乎每个留言都会评论!必须给个大大的赞! 其次,我有3个疑问, 1. 聚合中可能会有实体,那允不允许直接把实体类型作为属性的类型或返回值暴露给外界? 2. 如果允许,那外界就可以直接获取其中的实体,然后调用者可以直接使用实体中的一些相关方法?这样似乎就违反了聚合很的设计? 3. 如果不允许,那外界如果需要得到某个实体的数据,要怎么操作?封装成 DTO 吗? 比如: Aggregate a 中包含一个b属性,b是一个实体。b 中包含操作该实体的方法 M。那外界调用a.b 就会直接获取到 b 这个实体,然后可以直接调用 M 方法。
    展开

    作者回复: 非常感谢! 首先你说的外界,要明确一下,这个外界是微服务内,不同的层之间?还是指微服务之间,也就是微服务外部调用。 如果是在微服务内,不同层之间的话,由于运行在同一个微服务内,实体是可以被其它高层服务获取的,这时实体以DO对象的形式,存在于应用层和领域层,你可以使用DO对象的方法。但是不建议将实体的方法编排放在应用层,而是在领域层封装成领域服务后暴露给应用层。 如果这个外界是微服务之间的话,你需要将实体DO数据转换为DTO对象后,才能被外界使用,这时,外界是不能调用实体的方法的,因此可以隐藏实体核心业务逻辑的实现方式。

    21
  • 李二木
    2019-10-23
    什么是充血模型

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

    共 5 条评论
    13
  • 美美
    2019-11-06
    老师,想请教一下 我们的场景是:商家后台上,商家可以进行门店管理,收银pos机相关的设置,营业相关的设置 背景:商家后台已经存在,且商家基础系统、门店基础信息、pos相关信息,数据模型已经存在,各调用方是直接访问数据库的 目标:将商家、门店的基础信息,经营设置信息,抽象出来形成商家域 统一对外提供能力 想通过DDD的思想来进行建模,感觉无从下手,麻烦老师提供点思路
    展开

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

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

    作者回复: 类比的不错。

    12
  • 张迪
    2019-10-29
    跨多个实体的业务逻辑通过领域服务来实现。 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。 怎么感觉 聚合根做的事情和领域服务一样?

    作者回复: 两个出发点不一样,聚合根主要是从实体关联的角度,关注数据一致性。领域服务主要组合的是实体业务行为。

    9
  • Geek_7c4953
    2020-08-14
    老师,对于关联两个聚合根的一种关系,应该划分到哪个聚合? 比如: 用户聚合根和课程聚合根。 用户收藏了一门课程,收藏这个操作关联了用户和课程,同时生成了一条收藏记录。 那么收藏记录是值对象还是实体,应该归于哪个聚合根? 如果作为实体,它似乎不需要ID去标识,因为一个用户ID,一个课程ID就可以标识这样一条记录。 如果作为值对象,又感觉不合适,因为我的感觉(仅仅是感觉)多值的值对象不应该能无限增长,但收藏这个操作是不可能限制数量的。
    展开

    作者回复: 我感觉收藏应该也是一个独立聚合,收藏会有聚合根,它会引用用户ID和课程ID这两个值对象。这样可以对用户和课程独立管理和维护,保证用户聚合和课程聚合的领域逻辑稳定。用户登录时,获取用户ID,然后将用户ID值对象作为查询条件从收藏聚合获取收藏记录,再通过收藏记录关联的课程ID从课程聚合获取课程的详细数据。

    共 3 条评论
    6
  • 蜗牛慢慢爬
    2019-10-23
    听了这么多节课,总结一下还是太抽象了

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

    共 2 条评论
    6
  • geek_time
    2020-06-23
    欧老师的课程,都是高级工程师和架构师级别的人购买,受众少,讲这么好,销量却不高,可惜了。

    作者回复: 感谢哈,帮忙多推荐哦。

    5
  • 静心
    2020-08-26
    关于聚合和聚合根还是没完全理解,请教老师: 既然聚合和聚合根都是实体且具有全局唯一ID,那么聚合和聚合根应该在代码层放置在domain的entity目录下,且应该对应一个class。 按照聚合与聚合根的规则,那么聚合与聚合内实体的关系应该是一种组合关系。 但现实中还会有一种实体间的关系,这种集合与集合中的实体不是组合关系,而是一种列表和列表中元素的关系,如,文章列表与文章的关系,这种关系下,文章列表是没有唯一ID的。那么,想请教老师,对于这种关系,DDD中是如何表述的?文章列表是作为一个实体类存在好呢?还是把列表做成一个文章实体的方法好呢?
    展开

    作者回复: 在DDD的领域模型中聚合是一个逻辑边界概念,聚合本身没有业务逻辑实现相关的代码。聚合的业务逻辑是由聚合内的聚合根、实体、值对象和领域服务等来实现的。聚合没有ID,它不是实体,所以它没有class,就跟企业里面的部门一样,它只是一个组织名称上的概念,在部门内部的人员才是实体,部门内的若干人员一起构成聚合。每个聚合内有一个聚合根,多个实体、值对象和领域服务等领域对象,它们共同完成聚合的业务逻辑。 不清楚您说的文章列表所在的场景是什么样的?是查询后的文章清单结果吗?如果是查询列表结果的话,这个不是领域模型的概念。 在DDD中领域模型可以理解为在某一上下文边界内,由若干业务实体、行为或者由若干业务实体或行为组合而成,完成某个单一职责业务能力的对象组合。我们可以在领域模型内定义对象的依赖和组合关系。

    共 2 条评论
    5
  • winzheng
    2020-03-26
    老师,投保人和被保人为什么被识别为值对象?他们是客户,具备实体的特征。

    作者回复: 它们的新增和修改是在客户的聚合里面。在投保单聚合里面它们就是值对象,它们的数据来源于客户聚合,不允许在投保单聚合单独修改,只可整体替换。

    共 5 条评论
    4
  • 祥敏
    2019-11-01
    您好,第四讲和第五讲都在讲述微服务内部的拆分与设计,是关系紧密的两讲,反复听了有三四遍略有收获和疑问。 从业务顺延的角度拆分,聚合、聚合根、实体、值对象这四个概念很好的模拟了业务的世界,就像面向对象所讲的一切皆对象,对象与对象之间的关系(聚合)。 疑惑有两点:持久化和实体之间关系的灵活性。 持久化(以关系型数据库为例):聚合内部强一致性,聚合之间最终一致性。聚合是由多个遵循一定规则的实体组成,实体的描述由值对象组成,这样的问题在于聚合、实体、值对象和数据库之间的对应关系不够直接,同时事务管理也比非DDD的方式复杂,如果严格遵循DDD的原则去做,可能会在对象->数据库这个关节会有较多的问题要解决。 实体之间的关系:跨聚合之间实体不能直接发生关系,这个是否会不够灵活,实践中是否会引发一些问题?
    展开

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

    共 3 条评论
    4
  • 马里奥的马里奥
    2020-11-19
    文中提到“聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。”,但是在画的图中,投保单聚合根依赖报价单实体,那不就违反了只能通过聚合根访问实体吗?我想知道投保单是通过什么方式来访问报价单实体的。

    作者回复: 在投保单聚合内,报价单属于被投保单聚合根引用的实体对象,如果其它聚合需要查看报价单实体的数据,则需要通过投保单ID查询到投保聚合根后,再找到报价单实体,然后访问报价单实体的数据。 而如果将报价设计为独立聚合的话,那么报价单就是报价聚合的聚合根。这样在投保单聚合将报价单设计为值对象可能会更合适一些,投保单聚合中的报价单值对象来源于报价单聚合。

    共 2 条评论
    3
  • 勤快的懒洋洋
    2020-03-07
    聚合里直接调用仓储吗,还是由应用服务层调用,为什么

    作者回复: 仓储接口可以由实体方法调用(一般是聚合根),也可以由领域服务或应用服务调用。仓储一般是用于新增和修改数据,新增和修改数据通常有聚合根来统一管理,可以放在聚合根的方法里面,也可以用聚合内的领域服务来统一管理,通常对于实体较多的复杂的聚合,写入数据库前的DO到PO的转换或者PO到DO的实体数据初始化由工厂服务来统一管理。复杂查询一般不放在聚合内部,可以通过应用服务直接调用仓储查询接口来实现。

    共 3 条评论
    3
  • Jie
    2020-01-17
    2天里一口气把01-05都看了,不仅仅是文字本身,留言回复都大大补充了专利内容,必须给这么认真的老师点个赞

    作者回复: 谢谢!

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

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

    共 2 条评论
    3
  • 周大仙
    2021-12-10
    老师,我已经将基础篇的5讲都已经看过了,我在第五章总结一下所有问题,一并发问,麻烦老师有空看看。 场景:用户 与 地址 代码:按ddd原则设置 ----==== 华丽的分割线 ====---- public class person { public Interger id; public String name; ... public Address address; // 其它方法 } public class Address { public String province; public String city; ... } ----==== 华丽的分割线 ====---- 问题1:引述“第05篇的聚合节点”,...,值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是 个体化的对象,它们的行为表现出来的是个体的能力。 以上述代码为例,对实体的状态和特征进行描述,结合代码如何理解这句话。 问题2:若完全按照DDD的说话,数据表的设计应设计成单表,且在特殊场景中,如“第4篇”的“案例2”,以序列化对象的形 式存储。若我现在的需求是查出某个省或市等的人员有多少,是否应按范式对数据库进行设计,若是,在程序输出 person实体给其他程序使用时,是否应由代码查询 person 与 address的持续数据,最后组成ddd所说的person对 象并包含address值对象会好点,若是,那我们是否应一开始就应该按照数据库范式设计,以避免将来业务需要变 更,需要改动数据库表的结构和代码才能实现新需要。 问题3:引述“第01篇”的“战略设计”和“战术设计”以及“三步来划定领域模型和微服务的边界法”, 如果我没理解错的话,三步法与“战略设计”、“战术设计”不能对应该上,甚至在实践中可以忽略“战略”和“战术”的 概念。因为三步法中的,第一步是输出领域实体等领域对象,第二步是输出聚合对像,第三步是根据语义将多个聚 合组成领域模型。综合所述,三步法是一种自下而上的实践方法,而“战略”应该是一种更高维度的自上而下的方 法,“第01篇”中未给出自上而下的实践方法。不知道我有没有理解对呢? 问题4:引述“第02篇”桃树分解领域过程,再结合第01篇”的“战略设计”概念,我觉得分解桃树是一个自上而的分解领域过 程,符合“战略设计”的概念,但在实际应用中即难以应用,因为“桃树”是一个成型的产品,几乎不需要维护,所以 能够自上而下去分解领导,但现实中我们的应用是不稳定的经常变动,以上述"persion"和"address"为例,一开始 我们可以单表的形式,去编写实体代码,只需要符合,从用户中查出他们的地址信息。后来需求改变,需要查出某 个省或市的所有用户,这时候我们就得再编写一个Address带唯一id的实体,并且影响到Person,因此我觉得自上 而下地划分领域难以应用在实际开发中,并且在设计时应该要考虑到以后应用的变化,尽量使用范例设计数据表, 用程序处理值对象,不知道我这样的理解对不对? 问题5:假设,以“persion”和“address”为例,在某些特定的场景下,我是否可以把它分成person领域和address领域, person包含address值对象,address包含person值对象,老师请问一下我的理解对吗? 还有就是再极端一点的情况,假设person是在db1,而address在db2,然后person是基于spring cloud开发的, 是一个独立的服务,同样address也是,那这时候老师是建立在各个微服务中采用多数据源的方法查person和 address的持续数据呢?还是通过feign跨服务查询自己实体所需要的值对象呢? 在此,先谢谢老师的解答,学生不胜感激
    展开
    共 1 条评论
    2
  • SkyeDance
    2021-02-23
    聚合的设计步骤和设计原则特别有指导价值,在设计聚合时,不是将有关系的实体和值对象作简单的组合,最好能够找到实体和值对象运行的业务规则;换种说话,在判断聚合设计是否合理时,看看能否在聚合中找到这样一套业务业务规则。作者在评论中给了一个很简单的例子:「关于订单和商品的不变性,其实里面是有一个重要的规则,也就是订单总金额一定会是所有商品数量乘以价格后的总和,商品是依附于订单存在的。」 在实际操作过程中,有特别多的人关注聚合根中的值对象到底要不要冗余多余的属性信息,比如保单中的投保人和被保人,是只指存储客户编号呢,还是存储一个客户对象,如果是前一种,如何根据投保人名称来查询保单呢?其实这两种方式都可以,解决查询问题的方法也很多,只要你不只是盯着数据库来思考。比如,只存储了客户编号如何解决查询问题?只要在前端用下拉控件选择客户来代替输入客户名称即可;如果保存了客户名称如何在客户信息更新后同步到其他对象中呢?既可以使用定时任务刷新,也可以使用消息广播的方式来同步更新客户信息。
    展开

    作者回复: 是的。 在值对象设计时,可以适当的采用数据冗余的方式。数据冗余后,在当前聚合就可以获取部分值对象的关键数据,这样在需要查询值对象数据时,就不必进行跨微服务调用了,既可以减少微服务之间的调用频率,也可以实现故障隔离。只有当需要获取明细数据时,才通过聚合根ID去其它微服务获取。

    2