学习链接
time.geekbang.org/column/intr…
第一章 微服务为什么要选择DDD
内容
软件设计模式的演进
-
单机架构
- 面向过程的设计方法、包括客户端UI和数据库两层
-
集中式架构
- 面向对象的设计方法、包括业务的接入层、业务逻辑层、数据库层
-
分布式微服务架构
- 微服务架构可以很好的实现应用直接的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题
-
在单机和集中式架构的两种模式下,软件无法快速响应需求和业务的快速变化,最后错失发展良机。
微服务设计和拆分的困境
- 思考问题
- 微服务的粒度应该多大
- 微服务到底如何拆分和设计
- 微服务的边界应该在哪里
- 你在技术圈中一定听说过一些项目因为前期微服务拆分过度,导致项目复杂度过高,无法上线和运维。
- 综合来看,我认为微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了。
DDD 核心思想
- 通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
- 有些熟悉 DDD 设计方法的软件工程师在进行微服务设计时,发现可以利用 DDD 设计方法来建立领域模型,划分领域边界,再根据这些领域边界从业务视角来划分微服务边界。
- 而按照 DDD 方法设计出的微服务的业务和应用边界都非常合理,可以很好地实现微服务内部和外部的“高内聚、低耦合”。于是越来越多的人开始把 DDD 作为微服务设计的指导思想。
为什么DDD适合微服务
- DDD是处理高度复杂领域的设计思想
- 试图分离技术实现的复杂性、并围绕业务概念构建领域模型来控制业务的复杂性、以解决软件难以理解,难以演进的问题
- DDD不是架构、而是一种架构设计的方法论,通过边界划分将复杂的业务领域简单化、帮我们设计出清晰的领域和应用边界,可以很容易地实现架构的演进
DDD包括战略设计和战术设计
- 战略设计主要从业务视角出发、建立业务领域模型、划分领域边界、建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
- 战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
战略设计
- DDD 战略设计会建立领域模型,领域模型可以用于指导微服务的设计和拆分 事件风暴
- 事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程
- 通常采用用例分析、场景分析和用户旅程分析、尽可能全面不遗留地分解业务领域、并梳理对象之间的关系 这个叫做发散的过程
- 事件风暴过程会产生很多实例、命令、事件等领域对象,我们将这些领域的对象从不同的维度进行聚类、形成聚合、建立领域模型 这叫收敛的过程
从三步来划定领域模型和微服务的边界
- 在事件风暴中梳理业务过程中的用户操作,事件以及外部依赖的关系、根据这些要素梳理出领域实体等领域对象
- 根据领域实体之前的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体根
- 根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。
DDD与微服务关系
- DDD 是一种架构设计方法,微服务是一种架构风格两者从本质上都是为了追求高响应力,而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。
- DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。
- 微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
领域、子域、核心域、通用域和支撑域:傻傻分不清?
内容
如何理解领域和子域?
-
领域的定义
- 领域是从事一种专门活动或者事业的范围、部落或者部门
- 领域就是用来确定范围的、边界即边界
-
DDD 按照一定规则将业务进行划分,当领域细分到一定程度后,DDD会将问题限定在特定的边界内,在这个边界内建立模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。
-
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
DDD 的研究方法
- 通常的做法就是将问题一步一步地细分,再针对细分出来的问题域,逐个深入研究,探索和建立所有子域的知识体系。
- 领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。
如何理解核心域、通用域和支撑域。
- 决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。
- 同时被多个子域使用的通用功能子域是通用域
- 还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
总结
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小微服务需要解决的问题域,构建合适的领域模型,而领域模型映射成系统就是微服务了。 核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。
限界上下文:定义领域边界的利器
通用语言
介绍
* 在事件风暴过程中,通过团队交流达成的共识,能够简单、清晰、准确描述业务含义和规则的语言就是通用语言。
* 通用语言包含术语和用力场景,并且能够直接反映在代码中。
* 通用语言中的名词可以给领域对象命名、商品、订单等
* 而动词则是一个动作和时间、如商品下单,订单已付款等、对应领域事件或者命令
从事件风暴建立通用语言到领域对象设计和代码落
* 事件风暴
* 领域故事分析
* 提取领域对象
* 领域对象与代码模型映射
* 代码落地
总结
事件风暴的过程中,领域专家会和设计、开发人员一起建立领域模型,在领域建模的过程中会形成通用的业务术语和用户故事。
通过用户故事分析会形成一个个的领域对象,这些领域对象对应领域模型的业务对象,每一个业务对象和领域对象都有通用的名词术语,并且一一映射。微服务代码模型来源于领域模型,每个代码模型的代码对象跟领域对象一一对应。
限界上下文
介绍
* 为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。
* 用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
限界上下文和微服务的关系
* 领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。
* 子域还可根据需要进一步拆分为子子域,比如,支付子域可继续拆分为收款和付款子子域。拆到一定程度后,有些子子域的领域边界就可能变成限界上下文的边界了
* 每个领域模型都有它对应的限界上下文,团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成整个领域的领域模型。理论上限界上下文就是微服务的边界。
* 我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。
* 可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
实体和值对象:从领域模型的基础单元看系统设计
内容
实体
* 定义
* 在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。
* 实体的业务形态
* 领域模型中的实体是多少属性、行为的载体
* 在事件风暴中,可以根据命令、操作或者事件,是到业务的实体对象
* 实体和值对象是组成领域模型的基础单元
* 实体的代码形态
* 实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,
* 跨多个实体的领域逻辑则在领域服务中实现。
* 实体的运行形态
* 通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品
* 实体的数据库形态
* 在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象
值对象
* 定义
* 值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。
* 值对象本是就是一个集合
* 例子
* 值对象的业务形态
* 值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑
* 值对象的代码形态
* 两种形态:
* 如果值对象是单一属性,则直接定义为实体类的属性;
* 如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
* 值对象的运行形态
* 引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。
* 值对象的数据库形态
* DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。
* 在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;
* 在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
* 值对象的优势和局限
* 值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
* 值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便
聚合和聚合根:怎样设计聚合?
* 聚合的作用
* 领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
* 聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
* 聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的
* 聚合根的作用
* 聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。
* 如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
* 在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
* 怎么设计聚合
* 采用事件风暴、根据业务行为梳理出所有实体和值对象
* 从众多实体中选出适合作为对象管理者的根实体,也就是聚合根。
* 根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。
* 在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型
* 多个聚合根据业务语义和上下文一起划分到同一个限界上下文内
* 聚合的一些设计原则
* 聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性
* 如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差
* 聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。
* 聚合内数据强一致性,而聚合之间数据最终一致性
* 通过应用层实现跨聚合的服务调用