This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="01 | 领域驱动设计微服务设计为什么要选择DDD" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/e3/7ef189df2d774075ed833e15bf92aee3.mp3"></audio>
你好,我是欧创新。
我们知道,微服务设计过程中往往会面临边界如何划定的问题,我经常看到项目团队为微服务到底应该拆多小而争得面红耳赤。不同的人会根据自己对微服务的理解而拆分出不同的微服务,于是大家各执一词,谁也说服不了谁,都觉得自己很有道理。
那在实际落地过程中,我也确实见过不少项目在面临这种微服务设计困惑时,是靠拍脑袋硬完成的,上线后运维的压力就可想而知了。那是否有合适的理论或设计方法来指导微服务设计呢?当你看到这一讲的题目时,我想你已经知道答案了。
没错就是DDD。那么今天我就给你详细讲解下“微服务设计为什么要选择领域驱动设计
## 软件架构模式的演进
在进入今天的主题之前,我们先来了解下背景。
我们知道,这些年来随着设备和新技术的发展,软件的架构模式发生了很大的变化。软件架构模式大体来说经历了从单机、集中式到分布式微服务架构三个阶段的演进。随着分布式技术的快速兴起,我们已经进入到了微服务架构时代。
<img src="https://static001.geekbang.org/resource/image/8a/f8/8a859915250ffcab04675fb02fdf34f8.jpg" alt="">
我们先来分析一下软件架构模式演进的三个阶段。
**第一阶段是单机架构:**采用面向过程的设计方法系统包括客户端UI层和数据库两层采用C/S架构模式整个系统围绕数据库驱动设计和开发并且总是从设计数据库和字段开始。
**第二阶段是集中式架构:**采用面向对象的设计方法系统包括业务接入层、业务逻辑层和数据库层采用经典的三层架构也有部分应用采用传统的SOA架构。这种架构容易使系统变得臃肿可扩展性和弹性伸缩性差。
**第三阶段是分布式微服务架构:**随着微服务架构理念的提出,集中式架构正向分布式微服务架构演进。微服务架构可以很好地实现应用之间的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题。
我们知道,在单机和集中式架构时代,系统分析、设计和开发往往是独立、分阶段割裂进行的。
比如在系统建设过程中我们经常会看到这样的情形A负责提出需求B负责需求分析C负责系统设计D负责代码实现这样的流程很长经手的人也很多很容易导致信息丢失。最后就很容易导致需求、设计与代码实现的不一致往往到了软件上线后我们才发现很多功能并不是自己想要的或者做出来的功能跟自己提出的需求偏差太大。
而且在单机和集中式架构这两种模式下,软件无法快速响应需求和业务的迅速变化,最终错失发展良机。此时,分布式微服务的出现就有点恰逢其时的意思了。
## 微服务设计和拆分的困境
那进入微服务架构时代以后,微服务确实也解决了原来采用集中式架构的单体应用的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等等。
但在看到这些好处的同时,微服务实践过程中也产生了不少的争论和疑惑:微服务的粒度应该多大呀?微服务到底应该如何拆分和设计呢?微服务的边界应该在哪里?
可以说很久以来都没有一套系统的理论和方法可以指导微服务的拆分包括微服务架构模式的提出者Martin Fowler在提出微服务架构的时候也没有告诉我们究竟应该如何拆分微服务。
于是,在这段较长的时间里,就有不少人对微服务的理解产生了一些曲解。有人认为:“微服务很简单,不过就是把原来一个单体包拆分为多个部署包,或者将原来的单体应用架构替换为一套支持微服务架构的技术框架,就算是微服务了。” 还有人说:“微服务嘛,就是要微要小,拆得越小效果越好。”
但我想,这两年,你在技术圈中一定听说过一些项目因为前期微服务拆分过度,导致项目复杂度过高,无法上线和运维。
综合来看,我认为微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了。
那如何确定,是否有相关理论或知识体系支持呢?在回答这些问题之前,我们先来了解一下领域驱动设计与微服务的前世今生。
2004年埃里克·埃文斯Eric Evans发表了《领域驱动设计》Domain-Driven Design Tackling Complexity in the Heart of Software这本书从此领域驱动设计Domain Driven Design简称DDD诞生。DDD核心思想是通过领域驱动设计方法定义领域模型从而确定业务和应用边界保证业务模型与代码模型的一致性。
但DDD提出后在软件开发领域一直都是“雷声大雨点小”直到Martin Fowler提出微服务架构DDD才真正迎来了自己的时代。
有些熟悉DDD设计方法的软件工程师在进行微服务设计时发现可以利用DDD设计方法来建立领域模型划分领域边界再根据这些领域边界从业务视角来划分微服务边界。而按照DDD方法设计出的微服务的业务和应用边界都非常合理可以很好地实现微服务内部和外部的“高内聚、低耦合”。于是越来越多的人开始把DDD作为微服务设计的指导思想。
现在很多大型互联网企业已经将DDD设计方法作为微服务的主流设计方法了。DDD也从过去“雷声大雨点小”开始真正火爆起来。
## 为什么DDD适合微服务
“众里寻他千百度。蓦然回首,那人却在灯火阑珊处。”在经历了多年的迷茫和争论后,微服务终于寻到了他的心上人。
那DDD到底是何方神圣拥有什么神器呢
DDD是一种处理高度复杂领域的设计思想它试图分离技术实现的复杂性并围绕业务概念构建领域模型来控制业务的复杂性以解决软件难以理解难以演进的问题。DDD不是架构而是一种架构设计方法论它通过边界划分将复杂业务领域简单化帮我们设计出清晰的领域和应用边界可以很容易地实现架构演进。
**DDD包括战略设计和战术设计两部分。**
战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
我们不妨来看看DDD是如何进行战略设计的。
DDD战略设计会建立领域模型领域模型可以用于指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析尽可能全面不遗漏地分解业务领域并梳理领域对象之间的关系这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象我们将这些领域对象从不同的维度进行聚类形成如聚合、限界上下文等边界建立领域模型这就是一个收敛的过程。
<img src="https://static001.geekbang.org/resource/image/3b/73/3bb8915fd6e880d64e9029a1f8677473.jpg" alt="">
**我们可以用三步来划定领域模型和微服务的边界。**
第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。
第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。
第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。
有了这两层边界,微服务的设计就不是什么难事了。
在战略设计中我们建立了领域模型,划定了业务领域的边界,建立了通用语言和限界上下文,确定了领域模型中各个领域对象的关系。到这儿,业务端领域模型的设计工作基本就完成了,这个过程同时也基本确定了应用端的微服务边界。
在从业务模型向微服务落地的过程中,也就是从战略设计向战术设计的实施过程中,我们会将领域模型中的领域对象与代码模型中的代码对象建立映射关系,将业务架构和系统架构进行绑定。当我们去响应业务变化调整业务架构和领域模型时,系统架构也会同时发生调整,并同步建立新的映射关系。
## DDD与微服务的关系
有了上面的讲解现在我们不妨再次总结下DDD与微服务的关系。
DDD是一种架构设计方法微服务是一种架构风格两者从本质上都是为了追求高响应力而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发其核心要义是强调根据业务发展合理划分领域边界持续调整现有架构优化现有代码以保持架构和代码的生命力也就是我们常说的演进式架构。
DDD主要关注从业务领域视角划分领域边界构建通用语言进行高效沟通通过业务抽象建立领域模型维持业务和代码的逻辑一致性。
微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
## 总结
今天我们主要讨论了微服务设计和拆分的难题。通过DDD战略设计可以建立领域模型划定领域边界解决微服务设计过程中边界难以划定的难题。如果你的业务焦点在领域和领域逻辑那么你就可以选择DDD作为微服务的设计方法
更关键的一点是DDD不仅可以用于微服务设计还可以很好地应用于企业中台的设计。如果你的企业正在做中台转型DDD将会是一把利器它可以帮你建立一个非常好的企业级中台业务模型。有关这点你还会在后面的文章中见到详解。
除此之外DDD战术设计对设计和开发人员的要求相对较高实现起来相对复杂。不同企业的研发管理能力和个人开发水平可能会存在差异。尤其对于传统企业而言在战术设计落地的过程中可能会存在一定挑战和困难我建议你和你的公司如果有这方面的想法就一定要谨慎评估自己的能力选择最合适的方法落地DDD。
也不妨根据收获权衡一下,**总体来说DDD可以给你带来以下收获**
1. DDD是一套完整而系统的设计方法它能带给你从战略设计到战术设计的标准设计过程使得你的设计思路能够更加清晰设计过程更加规范。
1. DDD善于处理与领域相关的拥有高复杂度业务的产品开发通过它可以建立一个核心而稳定的领域模型有利于领域知识的传递与传承。
1. DDD强调团队与领域专家的合作能够帮助你的团队建立一个沟通良好的氛围构建一致的架构体系。
1. DDD的设计思想、原则与模式有助于提高你的架构设计能力。
1. 无论是在新项目中设计微服务还是将系统从单体架构演进到微服务都可以遵循DDD的架构原则。
1. DDD不仅适用于微服务也适用于传统的单体应用。
## 思考题
你的公司是否在实施微服务架构,你在微服务设计过程中面临的最大问题是什么?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,93 @@
<audio id="audio" title="02 | 领域、子域、核心域、通用域和支撑域:傻傻分不清?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/1a/4c67484764fb15be0c50387fd4ac7d1a.mp3"></audio>
你好,我是欧创新。
DDD的知识体系提出了很多的名词领域、子域、核心域、通用域、支撑域、限界上下文、聚合、聚合根、实体、值对象等等非常多。这些名词都是关键概念但它们实在有些晦涩难懂可能导致你还没开始实践DDD就打起了退堂鼓。因此在基础篇中我希望能带着你一起做好实践前的准备工作。
除此之外我想说的是这些名词在你的微服务设计和开发过程中不一定都用得上但它可以帮你理解DDD的核心设计思想和理念。而这些思想和理念在IT战略设计、业务建模和微服务设计中都是可以借鉴的。
那么从这讲开始我就会围绕以上这些DDD关键概念进行讲解帮助你彻底理清它们与微服务的关系了解它们在微服务设计中的作用。今天我们重点了解DDD的领域、子域、核心域、通用域和支撑域等重要概念。
## 如何理解领域和子域?
我们先看一下汉语词典中对领域的解释:“领域是从事一种专门活动或事业的**范围**、部类或部门。”百度百科对领域的解释:“领域具体指一种特定的**范围**或区域。”
两个解释有一个共同点——范围。对了领域就是用来确定范围的范围即边界这也是DDD在设计中不断强调边界的原因。
在研究和解决业务问题时DDD会按照一定的规则将业务领域进行细分当领域细分到一定的程度后DDD会将问题范围限定在特定的边界内在这个边界内建立领域模型进而用代码实现该领域模型解决相应的业务问题。简言之DDD的领域就是这个边界内要解决的业务问题域。
既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务范围就越大,反之则相反。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
我们知道DDD是一种处理高度复杂领域的设计思想它试图分离技术实现的复杂度。那么面对错综复杂的业务领域DDD是如何使业务从复杂变得简单更容易让人理解技术实现更容易呢
其实很好理解DDD的研究方法与自然科学的研究方法类似。当人们在自然科学研究中遇到复杂问题时通常的做法就是将问题一步一步地细分再针对细分出来的问题域逐个深入研究探索和建立所有子域的知识体系。当所有问题子域完成研究时我们就建立了全部领域的完整知识体系了。
<img src="https://static001.geekbang.org/resource/image/c9/c3/c99d4ef4fe28f483938e4fa03afb98c3.jpg" alt="">
我们来看一下上面这张图。这个例子是在讲如何给桃树建立一个完整的生物学知识体系。初中生物课其实早就告诉我们研究方法了。**它的研究过程是这样的。**
第一步:确定研究对象,即研究领域,这里是一棵桃树。
第二步对研究对象进行细分将桃树细分为器官器官又分为营养器官和生殖器官两种。其中营养器官包括根、茎和叶生殖器官包括花、果实和种子。桃树的知识体系是我们已经确定要研究的问题域对应DDD的领域。根、茎、叶、花、果实和种子等器官则是细分后的问题子域。这个过程就是DDD将领域细分为多个子域的过程。
第三步对器官进行细分将器官细分为组织。比如叶子器官可细分为保护组织、营养组织和输导组织等。这个过程就是DDD将子域进一步细分为多个子域的过程。
第四步:对组织进行细分,将组织细分为细胞,细胞成为我们研究的最小单元。细胞之间的细胞壁确定了单元的边界,也确定了研究的最小边界。
这里先剧透一点聚合、聚合根、实体以及值对象的内容,我还会在 [第04讲] 和 [第05讲] 中详细讲解。
我们知道细胞核、线粒体、细胞膜等物质共同构成细胞这些物质一起协作让细胞具有这类细胞特定的生物功能。在这里你可以把细胞理解为DDD的聚合细胞内的这些物质就可以理解为聚合里面的聚合根、实体以及值对象等在聚合内这些实体一起协作完成特定的业务功能。这个过程类似DDD设计时确定微服务内功能要素和边界的过程。
这里总结一下就是说每一个细分的领域都会有一个知识体系也就是DDD的领域模型。在所有子域的研究完成后我们就建立了全域的知识体系了也就建立了全域的领域模型。
上面我们用自然科学研究的方法,说明了领域可以通过细分为子域的方法,来降低研究的复杂度。现在我们把这个话题再切换到业务领域,对比验证下,二者的细分过程是否是一致的。这里以我从事的保险行业为例。
保险是个比较大的领域,很早以前的保险核心系统把所有的功能都放在一个系统里来实现,这个系统就是我们常说的单体系统。后来单体系统开始无法适应保险业务的发展,因此保险公司开始了中台转型,引入分布式微服务架构来替换原来的单体系统。而分布式微服务架构就需要划分业务领域边界,建立领域模型,并实现微服务落地了。
为实现保险领域建模和微服务建设,我们可以根据业务关联度以及流程边界将保险领域细分为:承保、收付、再保以及理赔等子域,而承保子域还可以继续细分为投保、保全(寿险)、批改(财险)等子子域。
在投保这个限界上下文内可以建立投保的领域模型,投保的领域模型最后映射到系统就是投保微服务。这就是一个保险领域的细分和微服务的建设过程。
那么你可能会说,我不是保险行业的人,我怎么理解这个过程呢?我认为,不同行业的业务模型可能会不一样,但领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。
## 如何理解核心域、通用域和支撑域?
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
这三类子域相较之下,核心域是最重要的,我们下面讲目的的时候还会以核心域为例详细介绍。通用域和支撑域如果对应到企业系统,举例来说的话,通用域则是你需要用到的通用系统,比如认证、权限等等,这类应用很容易买到,没有企业特点限制,不需要做太多的定制化。而支撑域则具有企业特性,但不具有通用性,例如数据代码类的数据字典等系统。
**那为什么要划分核心域、通用域和支撑域,主要目的是什么呢?**
还是拿上图的桃树来说吧。我们将桃树细分为了根、茎、叶、花、果实和种子等六个子域,那桃树是否有核心域?有的话,到底哪个是核心域呢?
不同的人对桃树的理解是不同的。如果这棵桃树生长在公园里,在园丁的眼里,他喜欢的是“人面桃花相映红”的阳春三月,这时花就是桃树的核心域。但如果这棵桃树生长在果园里,对果农来说,他则是希望在丰收的季节收获硕果累累的桃子,这时果实就是桃树的核心域。
在不同的场景下,不同的人对桃树核心域的理解是不同的,因此对桃树的处理方式也会不一样。园丁更关注桃树花期的营养,而果农则更关注桃树落果期的营养,有时为了保证果实的营养供给,还会裁剪掉疯长的茎和叶(通用域或支撑域)。
同样的道理公司在IT系统建设过程中由于预算和资源有限对不同类型的子域应有不同的关注度和资源投入策略记住好钢要用在刀刃上。
很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异。
比如同样都是电商平台的淘宝、天猫、京东和苏宁易购他们的商业模式是不同的。淘宝是C2C网站个人卖家对个人买家而天猫、京东和苏宁易购则是B2C网站是公司卖家对个人买家。即便是苏宁易购与京东都是B2C的模式他们的商业模式也是不一样的苏宁易购是典型的传统线下卖场转型成为电商京东则是直营加部分平台模式。
商业模式的不同会导致核心域划分结果的不同。有的公司核心域可能在客户服务,有的可能在产品质量,有的可能在物流。在公司领域细分、建立领域模型和系统建设时,我们就要结合公司战略重点和商业模式,找到核心域了,且重点关注核心域。
如果你的公司刚好有意向转型微服务架构的话,我建议你和你的技术团队要将核心域的建设排在首位,最好是有绝对的掌控能力和自主研发能力,如果资源实在有限的话,可以在支撑域或者通用域上想想办法,暂时采用外购的方式也未尝不可。
## 总结
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小微服务需要解决的问题域,构建合适的领域模型,而领域模型映射成系统就是微服务了。
核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。
## 思考题
请结合你所在公司的业务情况,尝试给业务做一个领域拆分,看看哪些子域是核心域,哪些子域是通用域和支撑域?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="03 | 限界上下文:定义领域边界的利器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/70/0d/70b1dcf0026b969a1f5d8464045f030d.mp3"></audio>
你好,我是欧创新。今天我们重点学习“限界上下文”。
在DDD领域建模和系统建设过程中有很多的参与者包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识不同的参与角色可能会有不同的理解那大家交流起来就会有障碍怎么办呢因此在DDD中就出现了“通用语言”和“限界上下文”这两个重要的概念。
这两者相辅相成,通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。你是不是感觉这么描述很抽象?没关系,接下来我会给你一一详细讲解。
在这之前,我想请你先看这样两个问题,这也是今天内容的核心。
1. 为什么要提出限界上下文的概念(也就是说除了解决交流障碍这个广义的原因,还有更具体的吗)?
1. 限界上下文在微服务设计中的作用和意义是什么?
## 什么是通用语言?
为了更好地理解限界上下文,回答这两个问题,我们先从通用语言讲起。
怎么理解通用语言这个概念呢?在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。也就是说,通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。
那么,通用语言的价值也就很明了了,它可以解决交流障碍这个问题,使领域专家和开发人员能够协同合作,从而确保业务需求的正确表达。
但是,对这个概念的理解,到这里还不够。
通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。
通用语言贯穿DDD的整个设计过程。作为项目团队沟通和协商形成的统一语言基于它你就能够开发出可读性更好的代码将业务需求准确转化为代码设计。
下面我带你看一张图,这张图描述了从事件风暴建立通用语言到领域对象设计和代码落地的完整过程。
<img src="https://static001.geekbang.org/resource/image/69/ee/69f44e120de5019c0fbff4d3fbc0afee.png" alt="">
1. 在事件风暴的过程中,领域专家会和设计、开发人员一起建立领域模型,在领域建模的过程中会形成通用的业务术语和用户故事。事件风暴也是一个项目团队统一语言的过程。
1. 通过用户故事分析会形成一个个的领域对象,这些领域对象对应领域模型的业务对象,每一个业务对象和领域对象都有通用的名词术语,并且一一映射。
1. 微服务代码模型来源于领域模型,每个代码模型的代码对象跟领域对象一一对应。
这里我再给你分享一条经验我自己经常用特别有效。设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象及其属性。比如领域对象在DDD分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。
下面是一个微服务设计实例的部分数据表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。在这个表格里面我们可以看到DDD分析过程中所有的领域对象以及它们的属性都被记录下来了除了DDD的领域对象我们还记录了在微服务设计过程中领域对象所对应的代码对象并将它们一一映射。
<img src="https://static001.geekbang.org/resource/image/09/b8/09ca1ccc982d02634a856b2e80cf24b8.jpg" alt="">
到这里我要再强调一次。DDD分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一在代码模型设计的时侯就要建立领域对象和代码对象的一一映射从而保证业务模型和代码模型的一致实现业务语言与代码语言的统一。
如果你做到了这一点,也就是建立了领域对象和代码对象的映射关系,那就可以指导软件开发人员准确无误地按照设计文档完成微服务开发了。即使是不熟悉代码的业务人员,也可以很快找到代码的位置。
## 什么是限界上下文?
那刚刚提到的限界上下文又是用来做什么的呢?
我们知道语言都有它的语义环境同样通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义DDD在战略设计上提出了“限界上下文”这个概念用来确定语义所在的领域边界。
我们可以将限界上下文拆解为两个词:限界和上下文。限界就是领域的边界,而上下文则是语义环境。通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言进行交流。
综合一下,我认为限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
### 进一步理解限界上下文
我们可以通过一些例子进一步理解一下这个概念不要小看它彻底弄懂会给你后面实践DDD打下一个坚实的基础。
都说中文这门语言非常丰富,在不同的时空和背景下,同样的一句话会有不同的涵义。有一个例子你应该听说过。
在一个明媚的早晨,孩子起床问妈妈:“今天应该穿几件衣服呀?”妈妈回答:“能穿多少就穿多少!”
那到底是穿多还是穿少呢?
如果没有具体的语义环境,还真不太好理解。但是,如果你已经知道了这句话的语义环境,比如是寒冬腊月或者是炎炎夏日,那理解这句话的涵义就会很容易了。
所以语言离不开它的语义环境。
而业务的通用语言就有它的业务边界,我们不大可能用一个简单的术语没有歧义地去描述一个复杂的业务领域。限界上下文就是用来细分领域,从而定义通用语言所在的边界。
现在我们用一个保险领域的例子来说明下术语的边界。保险业务领域有投保单、保单、批单、赔案等保险术语,它们分别应用于保险的不同业务流程。
1. 客户投保时,业务人员记录投保信息,系统对应有投保单实体对象。
1. 缴费完成后,业务人员将投保单转为保单,系统对应有保单实体对象,保单实体与投保单实体关联。
1. 如客户需要修改保单信息,保单变为批单,系统对应有批单实体对象,批单实体与保单实体关联。
1. 如果客户发生理赔,生成赔案,系统对应有报案实体对象,报案实体对象与保单或者批单实体关联。
投保单、保单、批单、赔案等,这些术语虽然都跟保单有关,但不能将保单这个术语作用在保险全业务领域。因为术语有它的边界,超出了边界理解上就会出现问题。
如果你对我从事的保险业不大了解也没关系,电商肯定再熟悉不过了吧?
正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。看到这,我想你应该非常清楚了,领域边界就是通过限界上下文来定义的。
## 限界上下文和微服务的关系
接下来,我们对这个概念做进一步的延伸。看看限界上下文和微服务具体存在怎样的关系。
我想你买过车险吧,或者听过吧。车险承保的流程包含了投保、缴费、出单等几个主要流程。如果出险了还会有报案、查勘、定损、理算等理赔流程。
保险领域还是很复杂的,在这里我用一个简化的保险模型来说明下限界上下文和微服务的关系。这里还会用到我们在 [[第02讲]](https://time.geekbang.org/column/article/149945) 学到一些基础知识,比如领域和子域。
<img src="https://static001.geekbang.org/resource/image/4c/5b/4c26f42d035da0cd5cbe1f25c48c205b.jpg" alt="">
首先,领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。在这个图里面保险领域被拆分为:投保、支付、保单管理和理赔四个子域。
子域还可根据需要进一步拆分为子子域,比如,支付子域可继续拆分为收款和付款子子域。拆到一定程度后,有些子子域的领域边界就可能变成限界上下文的边界了。
子域可能会包含多个限界上下文,如理赔子域就包括报案、查勘和定损等多个限界上下文(限界上下文与理赔的子子域领域边界重合)。也有可能子域本身的边界就是限界上下文边界,如投保子域。
每个领域模型都有它对应的限界上下文,团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成整个领域的领域模型。
理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。
可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
不过,这里还是要提示一下:除了理论,微服务的拆分还是有很多限制因素的,在设计中不宜过度拆分。那这个度怎么把握好呢?有关微服务设计和具体的拆分方法,我会在实战篇中详细讲解。
## 总结
通用语言确定了项目团队内部交流的统一语言,而这个语言所在的语义环境则是由限界上下文来限定的,以确保语义的唯一性。
而领域专家、架构师和开发人员的主要工作就是通过事件风暴来划分限界上下文。限界上下文确定了微服务的设计和拆分方向,是微服务设计和拆分的主要依据。如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
可以说限界上下文在微服务设计中具有很重要的意义如果限界上下文的方向偏离那微服务的设计结果也就可想而知了。因此我们只有理解了限界上下文的真正涵义以及它在微服务设计中的作用才能真正发挥DDD的价值这是基础也是前提。
## 思考题
现在,不妨回头看看,开头的两个问题你能回答了吗?你可以尝试用自己的话来总结一下。
最后再给你留一个作业,你能找一找自己工作中的通用语言和限界上下文吗?可以把你的答案和感受写下来,分享到留言区,与我一起讨论。也欢迎你把今天的内容分享给同事和朋友,邀请他一起学习。

View File

@@ -0,0 +1,149 @@
<audio id="audio" title="04 | 实体和值对象:从领域模型的基础单元看系统设计" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/69/e8/69a2e44b51107f72973d4e437a1cdbe8.mp3"></audio>
你好我是欧创新。今天我们来学习DDD战术设计中的两个重要概念实体和值对象。
这两个概念都是领域模型中的领域对象。它们在领域模型中起什么作用,战术设计时如何将它们映射到代码和数据模型中去?就是我们这一讲重点要关注的问题。
另外,在战略设计向战术设计过渡的这个过程中,理解和区分实体和值对象在不同阶段的形态是很重要的,毕竟阶段不同,它们的形态也会发生变化,这与我们的设计和代码实现密切相关。
接下来,我们就分别看看实体和值对象的这些问题,从中找找答案。
## 实体
我们先来看一下实体是什么东西?
在DDD中有这样一类对象它们拥有唯一标识符且标识符在历经各种状态变更后仍能保持一致。对这些对象而言重要的不是其属性而是其延续性和标识对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。没理解没关系请继续阅读。
### 1. 实体的业务形态
在DDD不同的设计过程中实体的形态是不同的。在战略设计时实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中我们可以根据命令、操作或者事件找出产生这些行为的业务实体对象进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类形成聚合。你可以这么理解实体和值对象是组成领域模型的基础单元。
### 2. 实体的代码形态
在代码模型中实体的表现形式是实体类这个类包含了实体的属性和方法通过这些方法实现实体自身的业务逻辑。在DDD里这些实体类通常采用充血模型与这个实体相关的所有业务逻辑都在实体类的方法中实现跨多个实体的领域逻辑则在领域服务中实现。
### 3. 实体的运行形态
实体以DO领域对象的形式存在每个实体对象都有唯一的ID。我们可以对一个实体对象进行多次修改修改后的数据和原来的数据可能会大不相同。但是由于它们拥有相同的ID它们依然是同一个实体。比如商品是商品上下文的一个实体通过唯一的商品ID来标识不管这个商品的数据如何变化商品的ID一直保持不变它始终是同一个商品。
### 4. 实体的数据库形态
与传统数据模型设计优先不同DDD是先构建领域模型针对实际业务场景构建实体对象和行为再将实体对象映射到数据持久化对象。
在领域模型映射到数据模型时一个实体可能对应0个、1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中有些实体只是暂驻静态内存的一个运行态实体它不需要持久化。比如基于多个价格配置数据计算后生成的折扣实体。
而在有些复杂场景下实体与持久化对象则可能是一对多或者多对一的关系。比如用户user与角色role两个持久化对象可生成权限实体一个实体对应两个持久化对象这是一对多的场景。再比如有些场景为了避免数据库的联表查询提升系统性能会将客户信息customer和账户信息account两类数据保存到同一张数据库表中客户和账户两个实体可根据需要从一个持久化对象中生成这就是多对一的场景。
## 值对象
值对象相对实体来说,会更加抽象一些,概念上我们会结合例子来讲。
我们先看一下《实现领域驱动设计》一书中对值对象的定义通过对象属性值来识别的对象它将多个相关属性组合为一个概念整体。在DDD中用来描述领域的特定方面并且是一个没有标识符的对象叫作值对象。
也就说,值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另外一个值对象予以替换。它可以和其它值对象进行相等性比较,且不会对协作对象造成副作用。这部分在后面讲“值对象的运行形态”时还会有例子。
上面这两段对于定义的阐述,如果你还是觉得有些晦涩,我们不妨“翻译”一下,用更通俗的语言把定义讲清楚。
简单来说,值对象本质上就是一个集。那这个集合里面有什么呢?若干个用于描述目的、具有整体概念和不可修改的属性。那这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。
这里我举个简单的例子,请看下面这张图:
<img src="https://static001.geekbang.org/resource/image/13/f6/136512ac4c65b3f2ed4b2898b40965f6.jpg" alt="">
人员实体原本包括:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎了对不对?现在,我们可以将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象了。
### 1. 值对象的业务形态
值对象是DDD领域模型中的一个基础对象它跟实体一样都来源于事件风暴所构建的领域模型都包含了若干个属性它与实体一起构成聚合。
我们不妨对照实体,来看值对象的业务形态,这样更好理解。本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。
### 2. 值对象的代码形态
值对象在代码中有这样两种形态。如果值对象是单一属性则直接定义为实体类的属性如果值对象是属性集合则把它设计为Class类Class将具有整体概念的多个属性归集到属性集合这样的值对象没有ID会被实体整体引用。
我们看一下下面这段代码person这个实体有若干个单一属性的值对象比如Id、name等属性同时它也包含多个属性的值对象比如地址address。
<img src="https://static001.geekbang.org/resource/image/c5/1b/c597d53a1a1aeca274d355e8ac79cc1b.jpg" alt="">
### 3. 值对象的运行形态
实体实例化后的DO对象的业务属性和业务行为非常丰富但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外其它业务行为就很少了。
值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式和序列化大对象的方式。
引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入。比如,人员实体可以有多个通讯地址,多个地址序列化后可以嵌入人员的地址属性。值对象创建后就不允许修改了,只能用另外一个值对象来整体替换。
**如果你对这两种方式不够了解,可以看看下面的例子。**
案例1以属性嵌入的方式形成的人员实体对象地址值对象直接以属性值嵌入人员实体中。
<img src="https://static001.geekbang.org/resource/image/32/fa/323c8aca1271cc043558dfd1f95f57fa.jpg" alt="">
案例2以序列化大对象的方式形成的人员实体对象地址值对象被序列化成大对象Json串后嵌入人员实体中。
<img src="https://static001.geekbang.org/resource/image/96/23/96253ac8bd1d93a2786b59d7b9c2c423.jpg" alt="">
### 4. 值对象的数据库形态
DDD引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变减少数据库表的数量和表与表之间复杂的依赖关系尽可能地简化数据库设计提升数据库性能。
如何理解用值对象来简化数据库设计呢?
传统的数据建模大多是根据数据库范式设计的每一个数据库表对应一个实体每一个实体的属性值用单独的一列来存储一个实体主表会对应N个实体从表。而值对象在数据库持久化方面简化了设计它的数据库设计大多采用非数据库范式值对象的属性值和实体对象的属性值保存在同一个数据库实体表中。
举个例子,还是基于上述人员和地址那个场景,实体和数据模型设计通常有两种解决方案:第一是把地址值对象的所有属性都放到人员实体表中,创建人员实体,创建人员数据表;第二是创建人员和地址两个实体,同时创建人员和地址两张表。
第一个方案会破坏地址的业务涵义和概念完整性,第二个方案增加了不必要的实体和表,需要处理多个实体和表的关系,从而增加了数据库设计的复杂性。
**那到底应该怎样设计,才能让业务含义清楚,同时又不让数据库变得复杂呢?**
我们可以综合这两个方案的优势,扬长避短。在领域建模时,我们可以把地址作为值对象,人员作为实体,这样就可以保留地址的业务涵义和概念完整性。而在数据建模时,我们可以将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表。这样既可以兼顾业务含义和表达,又不增加数据库的复杂度。
值对象就是通过这种方式,简化了数据库设计,总结一下就是:在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
另外也有DDD专家认为要想发挥对象的威力就需要优先做领域建模弱化数据库的作用只把数据库作为一个保存数据的仓库即可。即使违反数据库设计原则也不用大惊小怪只要业务能够顺利运行就没什么关系。
### 5. 值对象的优势和局限
值对象是一把双刃剑,它的优势是可以简化数据库设计,提升数据库性能。但如果值对象使用不当,它的优势就会很快变成劣势。“知彼知己,方能百战不殆”,你需要理解值对象真正适合的场景。
值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
所以,你可以对照着以上这些优劣势,结合你的业务场景,好好想一想了。那如果在你的业务场景中,值对象的这些劣势都可以避免掉,那就请放心大胆地使用值对象吧。
## 实体和值对象的关系
实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。
值对象和实体在某些场景下可以互换很多DDD专家在这些场景下其实也很难判断到底将领域对象设计成实体还是值对象可以说值对象在某些场景下有很好的价值但是并不是所有的场景都适合值对象。你需要根据团队的设计和开发习惯以及上面的优势和局限分析选择最适合的方法。
关于值对象我还要多说几句。其实DDD引入值对象还有一个重要的原因就是到底领域建模优先还是数据建模优先
DDD提倡从领域模型设计出发而不是先设计数据模型。前面讲过了传统的数据模型设计通常是一个表对应一个实体一个主表关联多个从表当实体表太多的时候就很容易陷入无穷无尽的复杂的数据库设计领域模型就很容易被数据模型绑架。可以说值对象的诞生在一定程度上和实体是互补的。
我们还是以前面的图示为例:
<img src="https://static001.geekbang.org/resource/image/13/f6/136512ac4c65b3f2ed4b2898b40965f6.jpg" alt="">
在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。在数据模型设计时,地址值对象可以作为一个属性集整体嵌入人员实体中,组合形成上图这样的数据模型;也可以以序列化大对象的形式加入到人员的地址属性中,前面表格有展示。
从这个例子中,我们可以看出,同样的对象在不同的场景下,可能会设计出不同的结果。有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。
## 总结
今天我们主要学习了实体和值对象在DDD不同设计阶段的形态以及它们从战略设计向战术设计演进过程中的设计方法。
这个过程是从业务模型向系统模型落地的过程比较复杂很考验你的设计能力很多时候我们都要结合自己的业务场景选择合适的方法来进行微服务设计。强调一点我们不避讳传统的设计方法毕竟适合自己的才是最好的。希望你能充分理解实体和值对象的概念和应用将学到的知识复用最终将适合自己业务的DDD设计方法纳入到架构体系实现落地。
## 思考题
请用自己的话总结下,实体和值对象的主要区别是什么?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,95 @@
<audio id="audio" title="05 | 聚合和聚合根:怎样设计聚合?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/8d/507678a0ca5a2a764ed37f400595798d.mp3"></audio>
你好我是欧创新。今天我们来学习聚合Aggregate和聚合根AggregateRoot
我们先回顾下上一讲在事件风暴中我们会根据一些业务操作和行为找出实体Entity或值对象ValueObject进而将业务关联紧密的实体和值对象进行组合构成聚合再根据业务语义将多个聚合划定到同一个限界上下文Bounded Context并在限界上下文内完成领域建模。
那你知道为什么要在限界上下文和实体之间增加聚合和聚合根这两个概念吗?它们的作用是什么?怎么设计聚合?这就是我们这一讲重点要关注的问题。
## 聚合
在DDD中实体和值对象是很基础的领域对象。实体一般对应业务对象它具有业务属性和业务行为而值对象主要是属性集合对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象它们的行为表现出来的是个体的能力。
**那聚合在其中起什么作用呢?**
举个例子。社会是由一个个的个体组成的,象征着我们每一个人。随着社会的发展,慢慢出现了社团、机构、部门等组织,我们开始从个人变成了组织的一员,大家可以协同一致的工作,朝着一个最大的目标前进,发挥出更大的力量。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是“高内聚、低耦合”的。
聚合在DDD分层架构里属于领域层领域层包含了多个聚合共同实现核心业务逻辑。聚合内实体以充血模型实现个体业务能力以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现跨多个聚合的业务逻辑通过应用服务来实现。比如有的业务场景需要同一个聚合的A和B两个实体来共同完成我们就可以将这段业务逻辑用领域服务来实现而有的业务逻辑需要聚合C和聚合D中的两个服务共同完成这时你就可以用应用服务来组合这两个服务。
## 聚合根
聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。
传统数据模型中的每一个实体都是对等的,如果任由实体进行无控制地调用和数据修改,很可能会导致实体之间数据逻辑的不一致。而如果采用锁的方式则会增加软件的复杂度,也会降低系统的性能。
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
最后在聚合之间它还是聚合对外的接口人以聚合根ID关联的方式接受外部任务和请求在上下文内实现聚合之间的业务协同。也就是说聚合之间通过聚合根ID关联引用如果需要访问其它聚合的实体就要先访问聚合根再导航到聚合内部实体外部对象不能直接访问聚合内实体。
## 怎样设计聚合?
DDD领域建模通常采用事件风暴它通常采用用例分析、场景分析和用户旅程分析等方法通过头脑风暴列出所有可能的业务行为和事件然后找出产生这些行为的领域对象并梳理领域对象之间的关系找出聚合根找出与聚合根业务紧密关联的实体和值对象再将聚合根、实体和值对象组合构建聚合。
下面我们以保险的投保业务场景为例,看一下聚合的构建过程主要都包括哪些步骤。
<img src="https://static001.geekbang.org/resource/image/d4/dc/d4975de95bc31f954d11yyaee32a65dc.png" alt="">
**第 1 步:**采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单、标的、客户、被保人等等。
**第 2 步:**从众多实体中选出适合作为对象管理者的根实体也就是聚合根。判断一个实体是否是聚合根你可以结合以下场景分析是否有独立的生命周期是否有全局唯一ID是否可以创建或修改其它对象是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体。
**第 3 步:**根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。在图中我们构建了客户和投保这两个聚合。
**第 4 步:**在聚合内根据聚合根、实体和值对象的依赖关系画出对象的引用和依赖模型。这里我需要说明一下投保人和被保人的数据是通过关联客户ID从客户聚合中获取的在投保聚合里它们是投保单的值对象这些值对象的数据是客户的冗余数据即使未来客户聚合的数据发生了变更也不会影响投保单的值对象数据。从图中我们还可以看出实体之间的引用关系比如在投保聚合里投保单聚合根引用了报价单实体报价单实体则引用了报价规则子实体。
**第 5 步:**多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。
这就是一个聚合诞生的完整过程了。
## 聚合的一些设计原则
我们不妨先看一下《实现领域驱动设计》一书中对聚合设计原则的描述,原文是有点不太好理解的,我来给你解释一下。
**1. 在一致性边界内建模真正的不变条件。**聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
**2. 设计小聚合。**如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
**3. 通过唯一标识引用其它聚合。**聚合之间是通过关联外部聚合根ID的方式引用而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理容易导致聚合的边界不清晰也会增加聚合之间的耦合度。
**4. 在边界之外使用最终一致性。**聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦(相关内容我会在领域事件部分详解)。
**5. 通过应用层实现跨聚合的服务调用。**为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。
上面的这些原则是DDD的一些通用的设计原则还是那句话“适合自己的才是最好的。”在系统设计过程时你一定要考虑项目的具体情况如果面临使用的便利性、高性能要求、技术能力缺失和全局事务管理等影响因素这些原则也并不是不能突破的总之一切以解决实际问题为出发点。
## 总结
[[第04讲]](https://time.geekbang.org/column/article/152677) 和 [第05讲] 的内容,其实是有强关联的。我们不妨在这里总结下聚合、聚合根、实体和值对象它们之间的联系和区别。
**聚合的特点:**高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。
一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了。
**聚合根的特点:**聚合根是实体有实体的特点具有全局唯一标识有独立的生命周期。一个聚合只有一个聚合根聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调聚合根与聚合根之间通过ID关联的方式实现聚合之间的协同。
**实体的特点:**有ID标识通过ID判断相等性ID在聚合内唯一即可。状态可变它依附于聚合根其生命周期由聚合根管理。实体一般会持久化但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
**值对象的特点:**无ID不可变无生命周期用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值是一组概念完整的属性组成的集合用于描述实体的状态和特征。值对象尽量只引用值对象。
## 思考题
请你结合公司的某个业务场景,试试能分析出哪些聚合?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进阶。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">