主页

索引

模块索引

搜索页面

DDD 实战课

备注

【书评】这本书是特别适合多看几遍的,而且也适合在实践中遇到问题时拿过来当工具书。更为精彩的是示例,文中示例挺好,但更关键的是评论中提出的问题,作者也给了很详细的解答,可以从各种维度来帮我理解文中的理论。

  • 2019-10-14

  • 欧创新,人保高级架构师,一名奋斗在软件架构一线十余年的技术人。

  • 目前热衷于采用领域驱动设计(DDD)实现中台业务建模,专注基于 DDD 的微服务设计和开发等。另外,我也正在深入探索传统企业中台数字化转型的技术和方法体系。

  • 对应书籍:《中台架构与实现:基于 DDD 和微服务》

目录

开篇词

  • 首要任务就是要吃透 DDD 的核心设计思想,搞清楚 DDD、微服务和中台之间的关系。中台本质是业务模型,微服务是业务模型的系统落地,DDD 是一种设计思想,它可以同时指导中台业务建模和微服务设计

  • 其次,就是通过战略设计,建立领域模型,划分微服务边界。这步是关键

  • 最后,通过战术设计,从领域模型转向微服务设计和落地。此时,边界清晰、可持续演进的微服务架构雏形就在你面前了。

基础篇

基础篇主要讲解 DDD 的核心知识体系,具体包括:领域、子域、核心域、通用域、支撑域、限界上下文、实体、值对象、聚合和聚合根等概念

https://img.zhaoweiguo.com/uPic/2022/09/kaiKRm.jpg

进阶篇

进阶篇主要讲解领域事件、DDD 分层架构、几种常见的微服务架构模型以及中台设计思想等内容。

实战篇

用一个典型的案例将 DDD 所有的知识点串联在一起,带你深入了解如何用 DDD 的设计思想来完成领域建模和微服务设计的全流程。

中台和平台有什么区别

备注

中台和平台相比,它更多体现的是一种理念的转变,它主要体现在这三个关键能力上:对前台业务的快速响应能力;企业级复用能力;从前台、中台到后台的设计、研发、页面操作、流程服务和数据的无缝联通、融合能力。

  • 中台首先体现的是一种企业级的能力,它提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、联通和融合问题,支持业务和商业模式创新。通过平台联通和数据融合为用户提供一致的体验,更敏捷地支撑前台一线业务。平台只是将部分通用的公共能力独立为共享平台。虽然可以通过 API 或者数据对外提供公共共享服务,解决系统重复建设的问题,但这类平台并没有和企业内的其它平台或应用,实现页面、业务流程和数据从前端到后端的全面融合,并且没有将核心业务服务链路作为一个整体方案考虑,各平台仍然是分离且独立的。

  • 关于区分平台与中台初步的认识,平台面向特定领域,偏技术,也有业务,避免重复建设,比如工作流引擎,移动平台,办公平台。平台之间是相对孤立的,在企业内会形成平台孤岛。而中台是企业级业务模型,面向的是业务,本质是业务,强调全局业务流,业务的互联互通,业务复用,相当于建立一个企业级的大系统,但不是单体应用,而是先从整体出发,再拆分成多个有内在关联的服务,由业务驱动各服务衔接。而技术上是通过微服务,分布式这种架构风格,来管理复杂度。微服务本来就复杂,需要成熟的平台,中间件,比如 dubbou 和 springcloud,来降低复杂性。

基础篇 (5 讲)

01 | 领域驱动设计: 微服务设计为什么要选择 DDD

DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

DDD 是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。

  • 战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

  • 战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

https://img.zhaoweiguo.com/uPic/2022/09/64vatW.jpg

02 | 领域.子域.核心域.通用域和支撑域⭐️

领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。

核心域.通用域和支撑域

  • 核心域:决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。

  • 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域。通用域则是你需要用到的通用系统,比如认证、权限等等,这类应用很容易买到,没有企业特点限制,不需要做太多的定制化

  • 支撑域:必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。

备注

🔥对于核心域的把握尤为关键,一般有两个原则:1)实打实赚钱的,能产生现金流的,足球主攻手得分王;2)能加快现金流转的,虽然不赚钱,但是起到助攻作用的,比如足球助攻手,至于通用域和支撑域的区分就不显得那么重要了,如果真要说点啥的话,通用域就是可以复用程度比较高的域,而支撑域就是默默付出的域。

不同的人对核心域的理解是不同的

  • 桃树生长在公园里,在园丁的眼里,他喜欢的是 “人面桃花相映红” 的阳春三月,这时花就是桃树的核心域。

  • 桃树生长在果园里,对果农来说,他则是希望在丰收的季节收获硕果累累的桃子,这时果实就是桃树的核心域。

  • 很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异

  • 商业模式的不同会导致核心域划分结果的不同。有的公司核心域可能在客户服务,有的可能在产品质量,有的可能在物流。

备注

核心域的建设排在首位,最好是有绝对的掌控能力和自主研发能力,如果资源实在有限的话,可以在支撑域或者通用域上想想办法,暂时采用外购的方式也未尝不可。

总结

核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。

备注

在领域不断划分的过程中,领域会被细分为不同的子域,这个过程实际上是将问题范围不断缩小的过程。对于领域问题来说,一个问题不断的划分,直到划分为我们熟悉的,能够快速处理的小问题。然后在对小问题处理再排列一个优先级。即:在领域细分到一定的范围后,我们就可以对这个子域进行事件风暴,为这个子域划分限界上下文,建立领域模型,然后可以基于领域模型进行微服务设计了。

03 | 限界上下文

  • 限界上下文(Bounded Context)

  • 通用语言定义上下文含义,限界上下文则定义领域边界,两者相辅相成,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。

通用语言

  • 定义:在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。

  • 价值:解决交流障碍这个问题,使领域专家和开发人员能够协同合作,从而确保业务需求的正确表达。

https://img.zhaoweiguo.com/uPic/2022/09/orXSwh.jpg

从事件风暴建立通用语言到领域对象设计和代码落地的完整过程。通用语言贯穿 DDD 的整个设计过程。作为项目团队沟通和协商形成的统一语言,基于它,你就能够开发出可读性更好的代码,将业务需求准确转化为代码设计。

  • 通用语言包含术语和用例场景,并且能够直接反映在代码中。

  • 通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象

  • 通用语言中的动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。

https://img.zhaoweiguo.com/uPic/2022/09/6z6dKX.jpg

设计过程中我们可以用一些表格,来**记录事件风暴和微服务设计过程中产生的领域对象及其属性**。比如,领域对象在 DDD 分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。在这个表格里面我们可以看到,DDD 分析过程中所有的领域对象以及它们的属性都被记录下来了,除了 DDD 的领域对象,我们还记录了在微服务设计过程中领域对象所对应的代码对象,并将它们一一映射。

限界上下文

  • 定义:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

商品在不同的阶段有不同的术语:

在销售阶段是商品
在运输阶段则变成了货物

限界上下文和微服务的关系

https://img.zhaoweiguo.com/uPic/2022/09/vhgPjW.jpg

领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。子域还可根据需要进一步拆分为子子域,比如,支付子域可继续拆分为收款和付款子子域。拆到一定程度后,有些子子域的领域边界就可能变成限界上下文的边界了。

备注

理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。

限界上下文和子域的关系

个人认为,子域的划分是一种比较粗的领域边界的划分,它不考虑子域内的领域对象、对象之间的关系和结构。子域的划分往往按照业务阶段或者功能模块边界进行粗分,其目的就是为了让你能够在一个相对较小的问题空间内,比较方便的用事件风暴来梳理业务场景。而限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。 聚合是最小的业务单元,也是可以独立作为微服务的。

在 DDD 中包括问题域和解决方案域两个不同的维度:

1. 问题域主要从业务视角来考虑,完成从领域到子域的分解
2. 解决方案域则主要从技术实现的角度,通过划分限界上下文和采用 DDD 战术设计完成微服务拆分和落地
“子域” 和 “限界上下文” 这两个概念分别从不同的视角,构建起了 DDD 处理业务复杂度的根基
  • 从实践角度,可以将业务领域的分解拆分为两个阶段:1.从领域到子域的粗粒度的分解和2.从子域到限界上下文的技术实现级的分解。有时候企业的业务领域非常庞大,不太方便用事件风暴对整个领域构建领域模型。所以在领域建模之前,我们先根据业务流程边界或者功能集合等要素,将庞大的领域分解成若干个大小合适的子域,然后根据子域属性划分为核心子域、通用子域和支撑子域。当领域分解到足够小后,我们就可以在这些子域内开展事件风暴,划分限界上下文完成领域建模。

  • 限界上下文本质上就是子域,只不过它会更多的考虑领域对象的语义边界和技术实现细节。限界上下文的划分体现的是一种更为详细的设计过程,这个过程划分了业务的上下文语义边界,完成了领域模型,明确了领域对象以及领域对象之间的依赖关系等。我们依据限界上下文和领域模型就可以完成微服务设计和落地了。

疑问解答

  • 如果一个对象同时存在于两个微服务中,你可以考虑一定的数据冗余。其中的一个微服务这个对象是主要的业务环节,它可以被设计为实体,而在另外一个微服务中这个数据是冗余数据,它可以设计为值对象或者关联实体。

04 _ 实体和值对象

  • 实体(Entity)或值对象(ValueObject)

实体

  1. 实体的业务形态:

    实体和值对象是组成领域模型的基础单元
    在 DDD 不同的设计过程中,实体的形态是不同的:
      a. 在战略设计时,实体是领域模型的一个重要对象。
      b. 领域模型中的实体是多个属性、操作或行为的载体。
      c. 在事件风暴中,根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合
    
  2. 实体的代码形态:

    在代码模型中,实体的表现形式是实体类,
      这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。
    在 DDD 里,这些实体类通常采用充血模型,
      与这个实体相关的所有业务逻辑都在实体类的方法中实现,
      跨多个实体的领域逻辑则在领域服务中实现。
    
  3. 实体的运行形态:

    实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。
      我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。
      但是,由于它们拥有相同的 ID,它们依然是同一个实体。
    比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,
      不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。
    
  4. 实体的数据库形态:

    与传统数据模型设计优先不同,DDD 是先构建领域模型,
      针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。
    
    大多数情况下实体与持久化对象是一对一
    但一个实体可能对应 0 个或者多个数据库持久化对象
      在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。
        比如,基于多个价格配置数据计算后生成的折扣实体。
      在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。
        比如,用户 user 与角色 role 两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。
        比如,为避免数据库的联表查询,提升系统性能,将客户信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,
          客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。
    

值对象

备注

【定义】通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。——《实现领域驱动设计》。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。简单来说,值对象本质上就是一个集。

  1. 值对象的业务形态:

    实体是看得到、摸得着的实实在在的业务对象,实体具有「业务属性」、「业务行为」和「业务逻辑」。
    而值对象只是若干个「属性」的集合,只有数据初始化操作和有限的不涉及修改数据的「行为」,基本不包含「业务逻辑」。
    值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
    
    在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。
    
  2. 值对象的代码形态:

    如果值对象是单一属性,则直接定义为实体类的属性;
    如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
    
  3. 值对象的运行形态:

    实体实例化后的 DO 对象的业务属性和业务行为非常丰富,
    值对象实例化的对象则相对简单和乏味。
      除了值对象数据初始化和整体替换的行为外,其它业务行为就很少了。
    

备注

值对象嵌入到实体的话,有这样两种不同的数据格式分别是属性嵌入的方式和序列化大对象的方式。

https://img.zhaoweiguo.com/uPic/2022/09/0hKeXC.jpg

以属性嵌入的方式形成的人员实体对象,地址值对象直接以属性值嵌入人员实体中。

https://img.zhaoweiguo.com/uPic/2022/09/xA85mM.jpg

以序列化大对象的方式形成的人员实体对象,地址值对象被序列化成大对象 Json 串后,嵌入人员实体中。

  1. 值对象的数据库形态:

    DDD 引入值对象是希望实现从 “数据建模为中心” 向 “领域建模为中心” 转变,
      减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。
    

备注

在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。另外,有 DDD 专家认为,要想发挥对象的威力,就需要优先做领域建模,弱化数据库的作用,只把数据库作为一个保存数据的仓库即可。即使违反数据库设计原则,也不用大惊小怪,只要业务能够顺利运行,就没什么关系。

  1. 值对象的优势和局限:

    优势:可以简化数据库设计,提升数据库性能。
    劣势:
      值对象采用序列化大对象的方法:
        简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。
        这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
      值对象采用属性嵌入的方法:
        提升了数据库的性能,
        但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
    

实体和值对象的关系

实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。值对象和实体在某些场景下可以互换,很多 DDD 专家在这些场景下,其实也很难判断到底将领域对象设计成实体还是值对象

备注

DDD 提倡从领域模型设计出发,而不是先设计数据模型。

很多值对象的数据可能来源于其他聚合,它们以数据冗余的方式完成不同领域中数据的流转和共享。在这些聚合中的值对象以实体或聚合根的形式存在,完成数据的集中维护和管理。而在自己的聚合中它则以值对象的形式存在,被聚合内的某一个实体引用。例如:在订单聚合中,订单实体有收货地址这个值对象。在生成订单实体时,会从个人中心的客户聚合中,获取地址实体数据组合成订单聚合的地址值对象。订单实体可以整体引用和修改地址值对象的数据,但不允许单独修改地址值对象的某一个属性数据,如 street。所有地址数据的新增和修改等维护操作,都只能在客户聚合中完成,这样就可以实现业务职责的高内聚,也就是说,如果你要修改某个业务行为,只需要修改一处就可以了。

总结

上面展示了实体和值对象在 DDD 不同设计阶段的形态,以及它们从战略设计向战术设计演进过程中的设计方法。这个过程是从业务模型向系统模型落地的过程,比较复杂,很考验你的设计能力,很多时候我们都要结合自己的业务场景,选择合适的方法来进行微服务设计。

05 _ 聚合和聚合根

  • 聚合(Aggregate)和聚合根(AggregateRoot)

聚合

实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。

备注

领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。

备注

聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。

聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是 “高内聚、低耦合” 的。

  • 聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。

  • 聚合内实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。

  • 跨多个实体的业务逻辑通过「领域服务」来实现:比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;

  • 跨多个聚合的业务逻辑通过「应用服务」来实现:有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。

聚合根

备注

聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。

https://img.zhaoweiguo.com/uPic/2022/09/VnhL19.jpg

DDD 领域建模通常采用事件风暴,它通常采用用例分析、场景分析和用户旅程分析等方法,通过头脑风暴列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理领域对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。

  • 第 1 步:采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单、标的、客户、被保人等等。

  • 第 2 步:从众多实体中选出适合作为对象管理者的根实体,也就是聚合根。判断一个实体是否是聚合根,你可以结合以下场景分析:是否有独立的生命周期?是否有全局唯一 ID?是否可以创建或修改其它对象?是否有专门的模块来管这个实体。

  • 第 3 步:根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。在图中我们构建了客户和投保这两个聚合。

  • 第 4 步:在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。

  • 第 5 步:多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。

聚合的一些设计原则

  1. 在一致性边界内建模真正的不变条件。聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。

  2. 设计小聚合。如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。

  3. 通过唯一标识引用其它聚合。聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。

  4. 在边界之外使用最终一致性。聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦。

  5. 通过应用层实现跨聚合的服务调用。为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。

总结

  • 聚合的特点:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。

  • 聚合根的特点:聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。

  • 实体的特点:有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。

  • 值对象的特点:无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。

02进阶篇 (6 讲)

06 _ 领域事件: 解耦微服务的关键

备注

【定义】在事件风暴(Event Storming)时,我们发现除了命令和操作等业务行为以外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,在 DDD 中这种事件被称为领域事件。

领域事件

领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。

举例来说:

1. 可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作;
2. 可以是定时批处理过程中发生的事件,比如批处理生成季缴保费通知单,触发发送缴费邮件通知操作
3. 可以是一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作

【如何识别领域事件】业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完…… 的时候,请通知……”“发生…… 时,则……” 等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。

备注

聚合的一个设计原则:在边界之外使用最终一致性。一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的最终一致性。所以领域事件要用最终一致性,而不是传统 SOA 的直接调用的方式。

备注

【作用】领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。

在微服务设计时不同领域事件的处理方式会不一样:

1. 微服务内的领域事件
    通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。
    这个过程可能会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。

2. 微服务之间的领域事件
    在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。

领域事件相关案例

https://img.zhaoweiguo.com/uPic/2022/09/IV8rMe.jpg

领域事件总体架构

https://img.zhaoweiguo.com/uPic/2022/09/MV4CPw.jpg

领域事件的执行需要一系列的组件和技术来支撑。领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。

  1. 事件构建和发布:

    事件实体: 依赖聚合根
      包括:
      a. 事件基本属性:
          至少包括:事件唯一标识、发生时间、事件类型和事件源
      b. 业务属性:
          用于记录事件发生那一刻的业务数据,这些数据会随事件传输到订阅方,以开展下一步的业务操作
    
    领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存
    
  2. 事件数据持久化:

    用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计
    当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。
    
    事件数据持久化有两种方案:
    a. 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
    b. 持久化到共享的事件数据库中。
        业务数据库和事件数据库不在一个数据库中
        它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性
    
  3. 事件总线 (EventBus):

    实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。
      事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。
    
    事件分发流程大致如下::
    a. 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;
    b. 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;
    c. 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。
    
  4. 消息中间件:

    跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅。
    
  5. 事件接收和处理:

    订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。
    

领域事件运行机制相关案例

https://img.zhaoweiguo.com/uPic/2022/09/srbtzH.jpg

这个领域事件发生在投保和收款微服务之间。发生的领域事件是:缴费通知单已生成。下一步的业务操作是:缴费。

总结

领域事件驱动是很成熟的技术,在很多分布式架构中得到了大量的使用。领域事件是 DDD 的一个重要概念,在设计时我们要重点关注领域事件,用领域事件来驱动业务的流转,尽量采用基于事件的最终一致,降低微服务之间直接访问的压力,实现微服务之间的解耦,维护领域模型的独立性和数据一致性。

07 _ DDD分层架构: 有效降低层与层之间的依赖

什么是 DDD 分层架构

DDD 的分层架构在不断发展。最早是传统的四层架构;后来四层架构有了进一步的优化,实现了各层对基础层的解耦;再后来领域层和应用层之间增加了上下文环境(Context)层,五层架构(DCI)就此形成了。

https://img.zhaoweiguo.com/uPic/2022/09/oBuLYW.jpg

在最早的传统四层架构中,基础层是被其它层依赖的,它位于最核心的位置,那按照分层架构的思想,它应该就是核心,但实际上领域层才是软件的核心,所以这种依赖是有问题的。后来我们采用了依赖倒置(Dependency inversion principle,DIP)的设计,优化了传统的四层架构,实现了各层对基础层的解耦。

  1. 用户接口层:

    负责向用户显示信息和解释用户指令。
    在应用层完成领域层服务组合和编排后,应用服务被用户接口层Facade服务封装,完成接口和数据适配后,以粗粒度的服务通过API网关面向前端应用发布。
    也是微服务之间服务调用的通道,微服务在应用层可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排。
    
    主要完成DO和DTO的互转,完成微服务与前端应用数据交互和转换
    facade接口服务,对多个DO对象进行组装,转换为DTO对象,向前端应用完成数据转换和传输。
    
  2. 应用层:

    应用层连接用户接口层和领域层,很薄的一层
    主要职能是协调领域层多个聚合完成服务的组合和编排。
    理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。
    
    主要有应用服务、事件订阅和发布等相关代码逻辑。
      应用服务主要负责服务的组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。
      在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等。
    
    BFF 与应用服务的区别:
      BFF是位于微服务之上,它的主要职责是负责微服务之间的服务协调和编排。
      应用服务主要处理微服务内的服务组合和编排,它可以组合和编排领域服务。
      在小型项目里,应用服务也可以编排其他微服务的应用服务,我们就没必要增加一层BFF的逻辑了。
    
  3. 领域层:

    实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。
    领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
    
    首先,领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。
    其次,实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,
      领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。
    
  4. 基础层:

    基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,
      包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。
    基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
    

DDD 分层架构最重要的原则

备注

每层只能与位于其下方的层发生耦合。架构根据耦合的紧密程度又可以分为两种:严格分层架构和松散分层架构。DDD 分层架构模型就属于严格分层架构

DDD 分层架构如何推动架构演进

1. 微服务架构的演进

领域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。实体或值对象的简单变更,一般不会让领域模型和微服务发生大的变化。但聚合的重组或拆分却可以。可以以聚合为基础单元,完成领域模型和微服务架构的演进。聚合可以作为一个整体,在不同的领域模型之间重组或者拆分,或者直接将一个聚合独立为微服务。

https://img.zhaoweiguo.com/uPic/2022/09/JcR07l.jpg

【微服务架构的演进过程】当你发现微服务 1 中聚合 a 的功能经常被高频访问,以致拖累整个微服务 1 的性能时,我们可以把聚合 a 的代码,从微服务 1 中剥离出来,独立为微服务 2,这样微服务 2 就可轻松应对高性能场景。在业务发展到一定程度以后,你会发现微服务 3 的领域模型有了变化,聚合 d 会更适合放到微服务 1 的领域模型中,这时你就可以将聚合 d 的代码整体搬迁到微服务 1 中,如果你在设计时已经定义好了聚合之间的代码边界,这个过程不会太复杂,也不会花太多时间。最后我们发现,在经历模型和架构演进后,微服务 1 已经从最初包含聚合 a、b、c,演进为包含聚合 b、c、d 的新领域模型和微服务了。

好的聚合和代码模型的边界设计,可以让你快速应对业务变化,轻松实现领域模型和微服务架构的演进。

2. 微服务内服务的演进
https://img.zhaoweiguo.com/uPic/2022/09/UF1Fqh.jpg

在服务设计时,你并不一定能完整预测有哪些下层服务会被多少个上层服务组装,因此领域层通常只提供一些原子服务,比如领域服务 a、b、c。但随着系统功能增强和外部接入越来越多,应用服务会不断丰富。有一天你会发现领域服务 b 和 c 同时多次被多个应用服务调用了,执行顺序也基本一致。这时你可以考虑将 b 和 c 合并,再将应用服务中 b、c 的功能下沉到领域层,演进为新的领域服务(b+c)。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。

备注

这就是服务演进的过程,它是随着你的系统发展的,最后你会发现你的领域模型会越来越精炼,越来越能适应需求的快速变化。

三层架构如何演进到 DDD 分层架构

DDD 分层架构的优势:

1. 首先,由于层间松耦合,可以专注于本层的设计,而不必担心自己的设计会影响其它层,成功地降低了层与层之间的依赖。
2. 其次,分层架构使得程序结构变得清晰,升级和维护更加容易。
    a. 只要本层的接口参数不变,其它层可以不必修改
    b. 即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制
https://img.zhaoweiguo.com/uPic/2022/09/GLCxtH.jpg

DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。

总结

备注

「DDD 的分层架构」作为微服务的核心框架,怎么强调其重要性都是不过分的。

DDD 分层架构包含用户接口层、应用层、领域层和基础层。通过这些层次划分,我们可以明确微服务各层的职能,划定各领域对象的边界,确定各领域对象的协作方式。这种架构既体现了微服务设计和架构演进的需求,又很好地融入了领域模型的概念,二者无缝结合

留言

  • 数据持久化对象 (Persistent Object, PO),与数据库结构一一映射,它是数据持久化过程中的数据载体。

  • 领域对象( Domain Object, DO),微服务运行时核心业务对象的载体, DO 一般包括实体或值对象。

  • 数据传输对象( Data Transfer Object, DTO),用于前端应用与微服务应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。

  • 视图对象(View Object, VO),用于封装展示层指定页面或组件的数据。

从底下往上逐层讲:

1. 单个实体:自身的方法就是实体本身的业务行为
2. 领域服务:多个实体可组成更复杂的业务动作
    实体的方法和领域服务共同构成领域模型的基础业务能力,这个能力是原子的基础的,不太考虑外界的用户行为和流程。
3. 应用服务:对这些基础的能力进行组合和编排
    它组合和编排的服务可以是跨聚合的领域服务,主要体现组合后的业务能力,更面向前端的用户操作,属于比较粗粒度的服务,通过编排可以更灵活应对外部需求变化。

08 _ 微服务架构模型

整洁架构

整洁架构又名 “洋葱架构”,整洁架构的层就像洋葱片一样,它体现了分层的设计思想。

https://img.zhaoweiguo.com/uPic/2022/09/vYei5u.jpg

在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。

备注

整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。

六边形架构

六边形架构又名 “端口适配器架构”。追溯微服务架构的渊源,一般都会涉及到六边形架构。

备注

六边形架构的核心理念是:应用是通过端口与外部进行交互的。

https://img.zhaoweiguo.com/uPic/2022/09/4Za4Tu.jpg

红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。

六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下:

1. 红圈内的六边形实现应用的核心业务逻辑
2. 外六边形完成外部应用、驱动和基础资源等的交互和访问
    对前端应用以 API 主动适配的方式提供服务
    对基础资源以依赖倒置被动适配的方式实现资源访问

三种微服务架构模型的对比和分析

https://img.zhaoweiguo.com/uPic/2022/09/9XjEMM.jpg

虽然 DDD 分层架构、整洁架构、六边形架构的架构模型表现形式不一样,但这三种架构模型的设计思想都是微服务架构高内聚低耦合原则的完美体现,是以领域模型为中心的设计思想。这三种架构都考虑了前端需求的变与领域模型的不变。

这三种架构是一种演化的关系。2003 年 DDD 诞生,它是一种上下层的关系。六边形架构是在 2005 年提出,将这种上下层的关系演变为内外关系,内部代表了应用的业务逻辑,外部代表应用的驱动逻辑。但六边形架构的内层的业务逻辑还没有明显的领域模型的概念。2008 年洋葱架构出现,六边形架构实际上是洋葱架构的一个超集。它与六边形架构有着相同的思路,都是通过编写适配器代码将应用逻辑从对基础设施的依赖中解放出来,避免基础设施代码渗透到应用逻辑中。洋葱架构在业务逻辑中加入了一些在领域驱动设计的分层的概念,比如用户接口层、应用层、领域层和基础层,另外它还明确了外层依赖内层,内层对外层无感知的这种依赖关系。虽然这三者之间在表达形式上存在差异,但它们的核心职责都是要做到核心业务逻辑和技术实现细节的分离和解耦。

从三种架构模型看中台和微服务设计

  1. 中台建设要聚焦领域模型

  2. 微服务要有合理的架构分层:

    项目级微服务
    企业级中台微服务
    
  3. 应用和资源的解耦与适配

https://img.zhaoweiguo.com/uPic/2022/09/inN9pr.jpg

[项目级微服务]通常项目级微服务之间的集成,发生在微服务的应用层,由应用服务调用其它微服务发布在 API 网关上的应用服务。你看下图中微服务 B 中红色框内的应用服务 B,它除了可以组合和编排自己的领域服务外,还可以组合和编排外部微服务的应用服务。它只要将编排后的服务发布到 API 网关供前端调用,这样前端就可以直接访问自己的微服务了。

https://img.zhaoweiguo.com/uPic/2022/09/OqB0v7.jpg

[企业级中台微服务]主要职能就是处理跨中台微服务的服务组合和编排,以及微服务之间的协调,它还可以完成前端不同渠道应用的适配。BFF 微服务与其它微服务存在较大的差异,就是它没有领域模型,因此这个微服务内也不会有领域层。BFF 微服务可以承担应用层和用户接口层的主要职能,完成各个中台微服务的服务组合和编排,可以适配不同前端和渠道的要求。

总结

备注

【设计思想】DDD 分层架构、整洁架构、六边形架构都是以领域模型为核心,实行分层架构,内部核心业务逻辑与外部应用、资源隔离并解耦。

09 _ 中台

数据中台的主要目标是打通数据孤岛,实现业务融合和创新,包括三大主要职能:

一是完成企业全域数据的采集与存储,实现各不同业务类别中台数据的汇总和集中管理。
二是按照标准的数据规范或数据模型,将数据按照不同主题域或场景进行加工和处理,形成面向不同主题和场景的数据应用,比如客户视图、代理人视图、渠道视图、机构视图等不同数据体系。
三是建立业务需求驱动的数据体系,基于各个维度的数据,深度萃取数据价值,支持业务和商业模式的创新。

数据中台的建设就可分为三步走:

第一步实现各中台业务数据的汇集,解决数据孤岛和初级数据共享问题。
第二步实现企业级实时或非实时全维度数据的深度融合、加工和共享。
第三步萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。

备注

数据中台主要完成数据的融合和加工,萃取数据业务价值,支持业务创新,对外提供数据共享服务。

阿里对前台、中台和后台的定位:

1. 前台主要面向客户以及终端销售者,实现营销推广以及交易转化;
2. 中台主要面向运营人员,完成运营支撑;
3. 后台主要面向后台管理人员,实现流程审核、内部管理以及后勤支撑,比如采购、人力、财务和 OA 等系统。

10 _ DDD.中台.微服务

备注

DDD 有两把利器,那就是它的战略设计和战术设计方法。战略设计最擅长的领域建模,形成中台的过程就是建立领域模型。战术设计又恰好可以与微服务的设计完美结合。

DDD 的本质

在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,并在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。领域可分解为子域,子域可继续分为子子域,一直到你认为适合建立领域模型为止。

中台的本质

中台来源于阿里的中台战略(详见《企业 IT 架构转型之道:阿里巴巴中台战略思想与架构实战》钟华编著)。2015 年年底,阿里巴巴集团对外宣布全面启动中台战略,构建符合数字时代的更具创新性、灵活性的 “大中台、小前台” 组织机制和业务机制,即作为前台的一线业务会更敏捷、更快速地适应瞬息万变的市场,而中台将集合整个集团的运营数据能力、产品技术能力,对各前台业务形成强力支撑。

备注

中台的本质其实就是提炼各个业务板块的共同需求,进行业务和系统抽象,形成通用的可复用的业务模型,打造成组件化产品,供前台部门使用。前台要做什么业务,需要什么资源,可以直接找中台,不需要每次都去改动自己的底层。

答疑: 3 个典型问题的讲解

领域边界划分的标准

备注

在领域不断划分的过程中,领域会被细分为不同的子域,这个过程实际上是将问题范围不断缩小的过程。直到划分为我们熟悉的、能够快速处理的小问题。然后就可以对这个子域进行事件风暴,为这个子域划分限界上下文,建立领域模型,然后就可以基于领域模型进行微服务设计了。

  • 子域的划分是一种比较粗的领域边界的划分,它不考虑子域内的领域对象、对象之间的关系和结构。子域的划分往往按照业务阶段或者功能模块边界进行粗分,其目的就是为了让你能够在一个相对较小的问题空间内,比较方便地用事件风暴来梳理业务场景。

  • 限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。

备注

核心域、通用域和支撑域,划分这三个不同类型子域的主要目的是为了区分业务域的优先级,确定 IT 战略投入。

核心域、通用域和支撑域都是业务领域,只不过重要性和功能属性不一样。采用的 DDD 设计方法和过程,是没有差异的。

聚合设计

  • 在聚合设计时,我们会用到两个重要的设计模式:工厂(Factory)模式和仓储(Repository)模式。

  • 为什么要引入工厂模式: 因为有些聚合内可能含有非常多的实体和值对象,我们需要确保聚合根以及所有被依赖的对象实例同时被创建。如果都通过聚合根来构造,将会非常复杂。

  • 为什么要引入仓储模式: 在领域层与基础层之间依赖倒置,为了解耦应用逻辑和基础资源,在基础层和上层应用逻辑之间会增加一层,这一层就是仓储层。

  • 关于聚合设计过程中的一些原则问题:大部分的业务场景我们都可以通过事件风暴,找到聚合根,建立聚合,划分限界上下文,建立领域模型。但也有部分场景,比如数据计算、统计以及批处理业务场景,所有的实体都是独立无关联的,找不到聚合根,也无法建立领域模型。但是它们之间的业务关系是非常紧密的,在业务上是高内聚的。我们也可以将这类场景作为一个聚合处理,除了不考虑聚合根的设计方法外,其它诸如 DDD 分层架构相关的设计方法都是可以采用的。

领域事件采用消息异步机制

  • 在领域事件设计中,为了解耦微服务,微服务之间数据采用最终一致性原则。

  • 关于事件总线的问题。由于微服务内的逻辑都在一个进程内,后端数据库也是一个,微服务内的事务相比微服务之间会好控制一些。在处理微服务内的领域事件时,如果引入事件总线,会增加开发的复杂度,那是否引入事件总线,就需要你来权衡。如果你的场景中,不会出现导致聚合之间数据不一致的情况,就可以不使用事件总线。另外,通过应用服务也可以实现聚合之间的服务和数据协调。

留言

03实战篇 (10 讲)

11 _ DDD实践-如何用 DDD 重构中台业务模型

DDD 的核心思想是在于划分领域边界从而来解耦。我认为领域模型才是 DDD 在微服务设计的关键,只有构建了边界清晰的领域模型的边界,才有可能设计出高质量的微服务。在建立领域模型后,微服务内部可以用 DDD 战术设计方法,传统的开发方法也不是不可以,有时候在性能方面表现更优秀。

12 _ 领域建模-如何用事件风暴构建领域模型

备注

【定义】事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对每一个事件,标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文。而事件风暴正是 DDD 战略设计中经常使用的一种方法,它可以快速分析和分解复杂的业务领域,完成领域建模。

事件风暴需要准备些什么

  1. 事件风暴的参与者:

    领域专家
    DDD 专家
    架构师
    产品经理
    项目经理
    开发人员
    测试人员
    
    领域建模是统一团队语言的过程,因此项目团队应尽早地参与到领域建模中,这样才能高效建立起团队的通用语言。到了微服务建设时,领域模型也更容易和系统架构保持一致。
    
  2. 事件风暴要准备的材料:

    用不同颜色的贴纸区分领域行为:
    a. 蓝色表示命令
    b. 用绿色表示实体
    c. 橙色表示领域事件
    d. 黄色表示补充信息
    
  3. 事件风暴的场地:

    一堵足够长的墙和足够大的空间
    
  4. 事件风暴分析的关注点:

    重点关注这类业务的语言和行为:
    某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?
    是谁(实体)发出的什么动作(命令),触发了这个动作(事件)
    …
    从这些暗藏的词汇中,分析出领域模型中的事件、命令和实体等领域对象
    

如何用事件风暴构建领域模型

  1. 产品愿景:

    主要目的是对产品顶层价值的设计,使产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
    这个过程也是明确用户中台建设方向和统一团队思想的过程。
    
    如果你的团队的产品愿景和目标已经很清晰了,那这个步骤你可以忽略。
    
https://img.zhaoweiguo.com/uPic/2022/09/9Tn978.jpg

产品愿景墙

  1. 业务场景分析:

    场景分析是从用户视角出发的,根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模。
    
https://img.zhaoweiguo.com/uPic/2022/09/3IqEFr.jpg

场景分析时会产生很多的命令和领域事件。我用蓝色来表示命令,用橙色表示领域事件,用黄色表示补充信息,比如用户信息数据来源于 HR 系统的说明。

  1. 领域建模:

    领域建模时,我们会根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,
      分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。
    
  • 3.1 从命令和事件中提取产生这些行为的实体

https://img.zhaoweiguo.com/uPic/2022/09/YxfPrh.jpg

用绿色贴纸表示实体。通过分析用户中台的命令和事件等行为数据,提取了产生这些行为的用户、账户、认证票据、系统、菜单、岗位和用户日志七个实体。

  • 3.2 根据聚合根的管理性质从七个实体中找出聚合根:

    六个聚合:
    系统功能、岗位、用户信息、用户日志、账户和认证票据
    
  • 3.3 划定限界上下文,根据上下文语义将聚合归类

https://img.zhaoweiguo.com/uPic/2022/09/um4b6c.jpg

划分为三个限界上下文:用户信息、认证和权限。

  1. 微服务拆分与设计:

    将领域模型作为拆分微服务的一个重要依据。
    微服务的设计还需要考虑:
        服务的粒度、分层、边界划分、依赖关系和集成关系。
    除了考虑业务职责单一外,我们还需要考虑:
      a. 将敏态与稳态业务的分离
      b. 非功能性需求(如弹性伸缩要求、安全性等要求)
      c. 团队组织和沟通效率
      d. 软件包大小以
      e. 技术异构等非业务因素
    

总结

一般来说一个中型规模的项目,领域建模的时间大概在两周左右,这与我们传统的需求分析和系统设计的时间基本差不多。

13 _ 代码模型上-如何使用DDD设计微服务代码模型

  • 第一点:聚合之间的代码边界一定要清晰。

  • 第二点:你一定要有代码分层的概念。

14 _ 代码模型下-如何保证领域模型与代码模型的一致性

领域层的领域对象

事件风暴结束时,领域模型聚合内一般会有:

聚合、实体、命令和领域事件等领域对象

在完成故事分析和微服务设计后,微服务的聚合内一般会有:

聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象

步骤:

1. 设计实体
2. 找出聚合根
3. 设计值对象
4. 设计领域事件
    如果领域模型中领域事件会触发下一步的业务操作,就需要设计领域事件
5. 设计领域服务
6. 设计仓储
    每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作

应用层的领域对象

应用层的主要领域对象是应用服务和事件的发布以及订阅。

https://img.zhaoweiguo.com/uPic/2022/09/ejUL08.jpg

在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。

服务的封装和调用主要有以下几种方式:

1. 实体方法的封装
2. 领域服务的组合和封装
3. 应用服务的组合和编排

15 _ 边界-微服务的各种边界在架构演进中的作用

演进式架构

Martin Fowler 在提出微服务时,他提到了微服务的一个重要特征 —— 演进式架构。那什么是演进式架构呢?演进式架构就是以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。这也是微服务设计的重点,就是看微服务设计是否能够支持架构长期、轻松的演进。

微服务边界的作用

1. 逻辑边界
    逻辑边界主要定义同一业务领域或应用内紧密关联的对象所组成的不同聚类的组合之间的边界。
2. 物理边界
    物理边界主要从部署和运行的视角来定义微服务之间的边界。
3. 代码边界
    代码边界主要用于微服务内的不同职能代码之间的隔离。
https://img.zhaoweiguo.com/uPic/2022/09/FSuqNs.jpg

随着业务的快速发展,如果某一个微服务遇到了高性能挑战,需要将部分业务能力独立出去,我们就可以以聚合为单位,将聚合代码拆分独立为一个新的微服务,这样就可以很容易地实现微服务的拆分。

16 _ 视图-如何实现服务和数据在微服务各层的协作

服务的协作

1. 服务的类型
  • Facade 服务

  • 应用服务

  • 领域服务

  • 基础服务

2. 服务的调用
https://img.zhaoweiguo.com/uPic/2022/10/sdpNfP.jpg

微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动。

微服务内跨层服务调用:

微服务架构下往往采用前后端分离的设计模式,前端应用独立部署。
前端应用调用发布在 API 网关上的 Facade 服务,Facade 定向到应用服务。

应用服务作为服务组织和编排者,它的服务调用有这样两种路径:
a. 应用服务调用并组装领域服务。
  领域服务组装实体和实体方法,实现核心领域逻辑。
  领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。
b. 应用服务直接调用仓储服务。
  这种方式主要针对像缓存、文件等类型的基础层数据访问。
  这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。

微服务之间的服务调用:

微服务之间的应用服务可以直接访问,也可以通过 API 网关访问。
由于跨微服务操作,在进行数据新增和修改操作时,你需关注分布式事务,保证数据的一致性。

领域事件驱动:

包括微服务内和微服务之间的事件。
微服务内通过事件总线(EventBus)完成聚合之间的异步处理。
微服务之间通过消息中间件完成。异步化的领域事件驱动机制是一种间接的服务访问方式。
3. 服务的封装与组合
  • 基础层

  • 领域层

  • 应用层

  • 用户接口层

4. 两种分层架构的服务依赖关系
  • 松散分层架构的服务依赖

  • 严格分层架构的服务依赖

数据对象视图

数据对象类型:

1. 数据持久化对象 PO (Persistent Object)
    与数据库结构一一映射,是数据持久化过程中的数据载体。
2. 领域对象 DO(Domain Object)
    微服务运行时的实体,是核心业务的载体。
3. 数据传输对象 DTO(Data Transfer Object)
    用于前端与应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。
4. 视图对象 VO(View Object)
    用于封装展示层指定页面或组件的数据。
https://img.zhaoweiguo.com/uPic/2022/10/wqCu31.jpg

微服务各层数据对象的职责和转换过程

17 _ 从后端到前端-微服务后.前端如何设计

https://img.zhaoweiguo.com/uPic/2022/10/Cm3Fke.jpg

一个虚框就是一个业务单元,微前端和微服务独立部署,业务单元内的微前端和微服务已完成前后端集成。你可以将这个业务单元理解为一个特定业务领域的组件。业务单元可以有多种组合方式,以实现不同的业务目标。

https://img.zhaoweiguo.com/uPic/2022/10/cXs1UG.jpg

微前端的集成方式:微前端位于前端主页面和微服务之间,它需要与两者完成集成。包括:1. 微前端与前端主页面的集成;2. 微前端与微服务的集成

备注

团队职责边界:前端项目团队只需要完成「前端主页面」与「业务单元」的融合;前端只关注「前端主页面」与「微前端页面」之间的集成;中台项目团队关注「业务单元」功能的完整性和自包含能力,完成业务单元内微服务和微前端开发、集成和部署,提供业务单元组件。

18 _ 知识点串讲-基于DDD的微服务设计实例

战略设计

1. 产品愿景
https://img.zhaoweiguo.com/uPic/2022/10/kMsTZU.jpg

产品愿景图:对产品顶层价值设计,对产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。但如果你的系统目标和需求非常清晰,这一步可以忽略。

2. 场景分析
https://img.zhaoweiguo.com/uPic/2022/10/aIB1fK.jpg

从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。

3. 领域建模

领域建模是一个收敛的过程,分三步:

1. 找出领域实体和值对象等领域对象
2. 找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合
3. 根据业务及语义边界等因素,定义限界上下文
https://img.zhaoweiguo.com/uPic/2022/10/yJp4CQ.jpg

第一步:找出实体和值对象等领域对象

https://img.zhaoweiguo.com/uPic/2022/10/H301K3.jpg

第二步:定义聚合

备注

第三步:定义限界上下文:由于人员组织关系聚合与请假聚合,共同完成请假的业务功能,两者在请假的限界上下文内。

4. 微服务的拆分

理论上一个限界上下文就可以设计为一个微服务,但还需要综合考虑多种外部因素,比如:职责单一性、敏态与稳态业务分离、非功能性需求(如弹性伸缩、版本发布频率和安全等要求)、软件包大小、团队沟通效率和技术异构等非业务要素。

战术设计

备注

战术设计是根据领域模型进行微服务设计的过程。这个阶段主要梳理微服务内的领域对象,梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系。

1. 分析微服务领域对象
  • 服务的识别和设计

  • 聚合中的对象

  • 微服务内的对象清单

2. 设计微服务代码结构
  • 应用层代码结构

  • 领域层代码结构

后续的工作

  1. 详细设计:

    实体属性、数据库表和字段、实体与数据库表映射、服务参数规约及功能实现等
    
  2. 代码开发和测试:

    按照详细的设计文档和功能要求,找到业务功能对应的代码位置,完成代码开发
    

评论

如何判断一个实体是否是聚合根:

1. 是否有独立的生命周期
2. 是否有全局唯一 ID
3. 是否可以创建或修改其他对象
4. 是否有专门的模块来管理这个实体

聚合根管理了聚合内所有实体和值对象的生命周期,我们通过聚合根就可以获取到聚合内所有实体和值对象等领域对象。
一般来说,如果聚合根被删除了,那么被它引用的实体和值对象也就不会存在了。

19 _ 总结一-微服务设计和拆分要坚持哪些原则

微服务的演进策略

  1. 绞杀者策略

  2. 修缮者策略

不同场景下的领域建模策略

  1. 新建系统

  2. 单体遗留系统

微服务设计原则

  • 第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。

  • 第二条:要边界清晰的微服务,而不是泥球小单体。

  • 第三条:要职能清晰的分层,而不是什么都放的大箩筐。

  • 第四条:要做自己能 hold 住的微服务,而不是过度拆分的微服务。

微服务拆分需要考虑哪些因素

  1. 基于领域模型

  2. 基于业务需求变化频率

  3. 基于应用性能

  4. 基于组织架构和团队规模

  5. 基于安全边界

  6. 基于技术异构等因素

20 _ 总结二-分布式架构关键设计10问

1. 选择什么样的分布式数据库

备注

分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。目前主要有三种不同的分布式数据库解决方案。它们的主要差异是数据多副本的处理方式和数据库中间件。

1. 一体化分布式数据库方案
    支持数据多副本、高可用。
    多采用 Paxos 协议,一次写入多数据副本,多数副本写入成功即算成功。
    代表产品是 OceanBase 和高斯数据库。

2. 集中式数据库 + 数据库中间件方案
    通过数据库中间件实现数据路由和全局数据管理。
    代表:开源数据库中间件 MyCat+MySQL 方案,TBase(基于 PostgreSQL)

3. 集中式数据库 + 分库类库方案
    一种轻量级的数据库中间件方案,与应用软件部署在一起,实现数据路由和数据归集。
    它适合比较简单的读写交易场景,在强一致性和聚合分析查询方面相对较弱。
    代表:ShardingSphere

2. 如何设计数据库分库主键

将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务。而对企业来说,“以客户为中心” 的业务能力,首先就要做到数据上的 “以客户为中心”。

3. 数据库的数据同步和复制

  • 传统的数据传输方式有 ETL 工具和定时提数程序,但数据在时效性方面存在短板

  • 分布式架构一般采用基于数据库逻辑日志增量数据捕获(CDC)技术,它可以实现准实时的数据复制和传输,实现数据处理与应用逻辑解耦,使用起来更加简单便捷。

4. 跨库关联查询如何处理

  • 跨多个微服务来统计这些数据。你可以建立面向主题的分布式数据库,它的数据来源于不同业务的微服务。在数据汇集时,提前做好数据关联(如将多表数据合并为一个宽表)或者建立数据模型。面向主题数据库建设查询微服务。这样一次查询你就可以获取客户所有维度的业务数据了。

  • 对于不在同一个数据库的表与表之间的关联查询场景,你可以采用小表广播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,你可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还可以提高数据的查询效率。

5. 如何处理高频热点数据

  • 常见的做法是将这些高频热点数据,从数据库加载到如 Redis 等缓存中,通过缓存提供数据访问服务。

  • 对需要模糊查询的高频数据,你也可以选用 ElasticSearch 等搜索引擎。

6. 前后序业务数据的处理

前后序的数据都跟领域事件有关。你可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。

在设计时你需要关注以下内容:

如果前序数据在当前微服务只可整体修改,并且不会对它做查询和统计分析,你可以将它设计为值对象;
当前序数据是多条,并且需要做查询和统计分析,你可以将它设计为实体。

7. 数据中台与企业级数据集成

分三步来建设数据中台:

a. 按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。
b. 建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道视图等。
c. 建立业务需求驱动的数据体系,支持业务和商业模式创新。

8. BFF 与企业级业务编排和协同

  • BFF 位于中台微服务之上,主要职责是微服务之间的服务协调;

  • 应用服务主要处理微服务内的服务组合和编排。

9. 分布式事务还是事件驱动机制

  • 对于实时性要求高的强一致性业务场景,你可以采用分布式事务,但分布式事务有性能代价,在设计时我们需平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。

  • 领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时场景的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好地解耦微服务。通过削峰填谷,可以减轻数据库实时访问压力,提高业务吞吐量和处理能力。你还可以通过事件驱动实现读写分离,提高数据库访问性能。对最终一致性的场景,我建议你采用领域事件驱动的设计方法。

10.多中心多活的设计

几个关键的设计:

a. 选择合适的分布式数据库
b. 单元化架构设计
c. 访问路由
d. 全局配置数据管理

结束语

结束语 _ 所谓高手, 就是跨过坑和大海

书籍推荐:

1. 《领域驱动设计:软件核心复杂性应对之道》
2. 《实现领域驱动设计》
3. 《微服务架构设计》

数据计算、统计以及批处理业务场景

其他

  • 示例:

    老师您好,关于领域划分想咨询下。
    
    先说下背景,我的公司是做企业软件实施的,公司有技术中台、大数据中台、业务中台,我所在的技术中台是做通用框架平台的,主要是避免重复造轮子。基于 SpringCloud 开发了很多通用微服务和通用基础组件,比如网关服务、用户权限管理服务、认证服务、平台服务、消息服务、文件服务、支付服务、OCR 服务、NLP 服务、工作流服务等等。在给客户实施软件时,通过选配需要的服务即可搭建起一个基础平台,然后重点关注客户的核心业务服务开发。
    
    首先关于核心域、通用域、支撑域的问题,像我所在技术中台这个领域内,我觉得所有的服务都属于我们的核心域,公司的战略就是成立技术中台来解决技术统一、通用能力沉淀、复用的问题,各个微服务其实没太多内在关联,都是通用微服务和组件。但要放到客户项目或者业务中台的产品,在他们的领域内,这些服务就变成了通用域或支撑域,而他们则把核心资源放到他们自身的业务上,那才是他们的核心域。这也应了文中说的商业模式的不同会导致核心域划分结果的不同。不知道这样理解有没有问题,主要是对技术中台这个领域的理解。
    
    然后是我负责的用户权限管理服务,我觉得这个服务现在越来越重了,加的功能越来越多,也不太清楚如何划分这个领域,所以想请教下。我们最开始虽然采用了 DDD 的战术设计,但实际实施出来还是面向过程式的,以数据库驱动的方式来设计的。
    主要的功能有:租户管理、用户管理、用户组管理、菜单管理、角色管理、客户端管理 (OAuth2 的客户端)、权限管理 (API)
    主要的动作有:
    用户分配角色、用户分配工作台卡片
    客户端分配角色
    角色分配菜单权限、角色分配用户、角色分配客户端、角色分配数据权限、角色分配工作台卡片、角色分配字段权限、角色分配单据权限
    角色创建又有复制、继承、直接创建三种创建方式
    菜单下有创建目录、菜单、维护 API 权限等
    其中工作台卡片、单据权限、数据权限是在平台服务进行数据维护的,平台服务和用户权限服务共用一个 schema。
    
    后面由于业务需求,又增加了安全组管理,相当于权限的集合,安全组分配菜单权限、数据权限、工作台卡片、字段权限、单据权限,然后角色增加了分配安全组的功能。
    
    然后最近还增加了三员管理 (保密系统的系统管理员、安全保密员、安全审计员),不过这个是开发的一个服务插件,是可插拔的。
    
    个人感觉整个用户权限服务越来越大、功能交错复杂,但又不好划分,而且由于代码层面各个功能耦合度较高,想拆分也比较难。
    
    但说实在的,我们是做通用框架的,要满足各个项目的功能需求以及方便项目上定制化功能逻辑,个人觉得代码水平还可以,代码质量和扩展性上不是问题。但现在我想通过 DDD 的方式来试着重构这个服务,而且我们现在有些功能也正面临这拆分重组的问题。
    
    这门课程我已经学完一遍了,如果按我的理解通过 DDD 的方式来重新拆分领域边界,将设计如下聚合:
    
    租户聚合:
    实体:租户
    动作:创建租户
    事件:租户初始化事件(租户初始化时会初始化其它的一些数据)
    
    权限聚合:
    实体:菜单、权限、用户、客户端、角色、租户
    值对象:工作台卡片、字段权限、单据权限、数据权限,这些应该是通过远程服务获取的(那应该是 DTO?还是建成值对象?)
    动作:
    菜单实体:菜单创建、目录创建、分配权限
    权限实体:查询权限
    用户实体:创建用户、修改密码
    客户端实体:创建客户端
    角色实体:创建角色、继承创建、复制创建
    领域服务:
    权限分配服务:分配角色菜单权限、分配角色卡片、分配角色字段权限、分配角色数据权限、分配角色单据权限、分配角色用户、分配角色客户端(我不是很确定是应该单独划分领域服务还是放到角色实体里面)
    
    安全组聚合:
    实体:安全组
    值对象:菜单权限、卡片、字段权限、数据权限、单据权限、角色
    领域服务:
    安全组分配服务:处理角色和安全组的关系
    应用服务:
    安全组应用服务:通过服务编排,组合权限聚合中的权限分配领域服务,处理安全组下的菜单权限、卡片、数据权限等于角色的分配关系。
    
    三员聚合:
    实体:无
    值对象:角色、菜单权限等
    领域服务:三员角色领域服务
    
    以上是我的个人理解,还望老师指点。我主要是想了解针对我们这种通用域类型的底层框架服务,好不好用 DDD 的战略和战术设计,又怎么划分边界,而且一定要满足功能的扩展,逻辑自定义。
    
    
    ====>
    
    
    作者回复:内容比较多,不知道理解的对不对?不对的地方请指出。
    在设计的时候要考虑聚合的职责单一原则。我大概梳理一下,这里面有些功能是基础数据管理和配置,如用户、租户、菜单、角色配置等,有些是多个聚合数据组合后形成的聚合,比如用户权限聚合。
    第一、要完成权限管理功能会有用户或者租户。这些数据都是非常基础的实体,完成用户和租户基础数据管理,因此会有用户或租户聚合,聚合根分别是用户和租户。
    用户和租户这一块应该在一个限界上下文内。
    第二、系统中会有菜单等基础数据配置,因此会有菜单管理相关的聚合,聚合根是菜单。
    第三、在进行权限设置时,首先需要定义角色(角色是不是岗位的意思?),角色定义完成后实际上是一种通用静态配置数据吧,角色关联菜单(一对多),字段,数据以及单据等实体,因此会有角色(岗位)聚合。但需要注意:如果不同用户角色的菜单权限一样,但是数据权限或者单据权限不同,这样设计就可能不合适,这时数据权限和单据权限就不能关联到角色了。这里不清楚你说的数据权限和单据权限是否需要单独维护和管理,如果需要独立维护就可以独立为数据和单据权限等的基础数据配置的聚合。
    第四、菜单权限、卡片、字段权限、数据权限、单据权限、角色等多个值对象权限组合为一个安全组,形成安全组聚合。
    第五、用户(租户)与角色或安全组权限结合一起形成用户(租户)权限,因此会有用户权限聚合,用户权限是聚合根,它会关联安全组或用户、角色、数据等其他聚合的数据,用户、角色、数据以值对象的形式被用户权限聚合根引用。
    三员管理我理解也应该是角色相关的管理。
    第二到第五的内容应该在一个权限相关的限界上下文内。
    第六、考虑到登录,应该还有账户聚合,账户是聚合根,完成密码和登录管理,另外账户会引用用户值对象。这些内容在一个限界上下文内。
    完成这些聚合设计后,聚合之间的协调可以通过应用服务来实现。
    

主页

索引

模块索引

搜索页面