- 一、软件设计发展过程
- 二、什么是DDD?
- 2.1 战略设计
- 2.2 战术设计
- 2.3 名词扫盲
- 1. 领域和子域
- 2. 核心域、通用域和支撑域
- 3. 通用语言
- 4. 限界上下文
- 5. 实体和值对象
- 6. 聚合和聚合根
- 2.4 事件风暴
- 2.5 领域事件
- 三、DDD与微服务
- 3.1 DDD与微服务的关系
- 3.2 基于DDD进行微服务设计
- 3.3 基于DDD重构中台业务模型
- 1. 自顶向下
- 2. 自底向上
- 四、总结
一、软件设计发展过程
软件架构模式大体上经历了从单机架构 -> 集中式架构 -> 分布式微服务三个阶段的架构演进。
在单机和集中式架构模式下,软件无法快速响应需求和业务的迅速变化,直到微服务架构时代的来临。微服务架构解决了单机和集中式架构的很多问题,比如扩展性、弹性伸缩性、小规模团队的敏捷开发等。
但是,带来好处的同时,微服务实践过程中也产生了不少争议性的问题:“微服务到底应该怎么拆分和设计才算合理,拆多小才叫微服务”,而微服务的边界历来也是最容易产生争议的地方。
二、什么是DDD?
2004年埃里克·埃文斯发表了《领域驱动设计》这本书,DDD(Domain Driven Design)由此诞生。
DDD核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模式和系统架构模式的一致性。
DDD不是架构,而是一种架构设计方法论,它通过边界划分将复杂的业务领域简单化,从而设计出清晰的领域和应用边界,进而可以非常容易地实现架构演进。
DDD主要包括战略设计和战术设计两大部分。
- 战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,而限界上下文可以作为微服务划分的主要参考边界。
- 战术设计主要从技术视角出发,侧重于领域模型的技术实现,完成软件开发的落地,包括聚合、聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
2.1 战略设计
DDD 战略设计主要从业务视角出发,建立业务领域模型,领域模型可以用于指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程。
什么是事件风暴?
事件风暴是一种协作的过程,通常采用用例分析、业务场景分析和用户旅程分析等,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多实体、命令、事件等领域对象,通过将这些领域对象从不同维度进行聚类(将数据集中的对象或数据点按照某种相似性或相关性的标准分成不同的组别,即“簇”。聚类的目标是将相似的数据点分到同一簇中,使得同一簇内的数据点之间更加相似,而不同簇之间的数据点相对较不相似。),形成聚合、聚合根、限界上下文等边界,建立领域模型,这是一个收敛的过程。
事件风暴可以帮助开发团队、业务团队共同理解和共享对业务领域的见解,有助于捕获关键的业务事件(领域事件)、过程和概念,以指导软件系统的设计和实施,从而更好地支撑业务需求落地。此外,事件风暴有助于提高团队的沟通、协助和业务理解能力。
在战略设计中建立了领域模型,划定了业务领域的边界,并建立了通用语言和限界上下文,确定了领域模型中各个领域对象的关系,如上图所示。
2.2 战术设计
当领域模型的设计工作完成后,基本也就确定了应用端的微服务边界。在从领域模型向微服务架构设计落地的过程中,也就是从战略设计向战术设计的实施过程中,开发人员会将领域模型中的领域对象(聚合、聚合根、实体、值对象等)与代码模型中的对象建立映射关系(可以通过表格进行罗列记录),将业务架构和系统架构进行绑定。
战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
经验:
可通过 Excel 表格记录、罗列战术设计中的聚合根、实体、值对象等关系、属性
2.3 名词扫盲
DDD 的知识体系提出了很多的名词,像:领域、子域、核心域、通用域、支撑域、通用语言、限界上下文、聚合、聚合根、实体、值对象等等,非常多,下面一一进行解释。
1. 领域和子域
领域,指的是一个特定业务领域或问题领域,它通常涉及一组相关的业务规则、概念和数据。领域可以进一步划分为子领域,我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
2. 核心域、通用域和支撑域
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
- 核心域:决定产品和公司核心竞争力的子域。**核心域是业务成功的主要因素和公司的核心竞争力。**如平安的保险。
- 通用域:没有太多个性化诉求,同时被多个子域使用的通用功能子域。如系统权限、合同。
- 支撑域:既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。如数据字典系统。
3. 通用语言
在事件风暴的过程中,领域专家、业务人员、开发人员会一起建立领域模型,在领域建模的过程中会形成通用的业务术语和用户故事,进而形成能够简单、清晰、准确描述业务含义和规则的通用语言。通用语言可以解决交流障碍问题,使得领域专家、业务人员和开发人员能够更加高效的协同合作,进而确保业务需求的正确表达与落地。
通用语言贯穿 DDD 的整个设计过程(事件风暴 -> 领域故事分析 -> 提取领域对象 -> 领域对象与代码模型映射 -> 代码落地),基于通用语言,可以设计出可读性更好的代码,将业务需求准确转化为代码设计。
4. 限界上下文
任何语言都存在语义环境的,在不同的时空和背景下,同一句话会存在不同的含义,比如语言:“衣服能穿多少就穿多少。”,在夏天表就是要穿少一点儿,冬天却表示需要穿的很多。所以DDD的通用语言也存在它的语义环境,为了避免同样的概念或语义在不同的环境中产生企业,DDD提出了 限界上下文 用来确定语义所在的领域边界。限界上下文可以通过将其拆分为限界、上下文来进行理解。
- 限界:领域的边界;
- 上下文:语义环境;
限界上下文是用来封装通用语言和领域对象,提供上下文环境,保证在领域内的一些术语、业务相关对象等有一个唯一的含义。例如,在电商领域中,商品在不同阶段存在不同的含义,在销售、售卖时是商品,在打包运输时则为货物,所以在销售领域和运输领域之间,存在限界上下文,如下:
限界上下文是微服务设计和拆分的主要依据,它确定了微服务的设计和拆分方向,在事件风暴中,领域专家、架构师和开发人员的主要工作就是划分限界上下文。理论上,可以将一个限界上下文设计为一个微服务,当然还需根据其他限制因素考虑,防止过度拆分。
5. 实体和值对象
实体和值对象都是用来表示领域模型中的事物或概念的重要元素。它们有不同的特性和用途。实体和值对象是组成领域模型的基础单元。
- 实体:通常代表领域中的具体事务,具体唯一标识、生命周期和状态,并且可以被识别、跟踪和区分。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中,我们可以根据命令、操作或事件,找出产生这些行为的业务实体对象。
- 值对象:通常将多个相关属性组成的集合称为一个值对象。值对象没有唯一标识,是不可变的,通常用来表示领域中的属性和特性,帮助描述实体的特性。
实体一般对应业务对象,它具有业务属性和业务行为。而值对象主要是多个相关属性集合,对实体的状态和特征进行描述。例如人员实体原本包括:姓名、身份证号、性别、年龄以及所在省、市、县和街道等属性。我们可以将 “省、市、县和街道等属性” 抽离出来,构建一个地址属性集合,这个集合对象就是值对象。如下:
一个实体可以存在多个值对象。
6. 聚合和聚合根
由业务和逻辑紧密关联的实体和值对象组合而成的对象称为聚合。聚合都存在一个聚合根和上下文边界。聚合是数据修改和持久化的基本单位。
- 聚合根:如果把聚合比作组织,那聚合根就是这个组织的 “头”。聚合根也称为根实体,它不仅是实体,还是聚合的管理者,在聚合之间,还是聚合对外的接口人,以聚合根ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。聚合之间通过聚合根ID关联引用,如果需要访问其他聚合的实体,需要先访问聚合根,再通过聚合根导航到聚合内部实体,外部对象不能直接访问聚合内实体。
- 上下文边界:上下文边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含那些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就满足“高内聚、低耦合”的特点。
如何设计聚合???
DDD 领域建模通常采用事件风暴,通过用例分析、场景分析和用户旅程分析等方法,列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理领域对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。
下面我们以大家都熟悉的学校选课业务场景为例,看一下聚合的构建过程主要包括哪些步骤。
第一步:采用事件风暴,通过用例分析、场景分析和用户旅程分析等方法,如下,梳理出在选课过程中发生这些行为的所有的实体和值对象,比如课程、老师、学生、选课规则、上课时间等等。
- 老师通过账号登录教务系统,发布课程相关信息,包括课程学分、上课地址、上课时间、选课规则等。
- 学生通过账号登录教务系统,进行选课。
第二步:从众多实体中选出适合作为对象管理者的根实体,既聚合根。
如何判断一个实体是否为聚合根:可以通过是否有独立的生命周期?是否有全局唯一ID? 是否可以创建或修改其它对象?是否有专门的模块来管这个实体。图中的聚合根分别是课程和用户实体。
第三步:根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出1个包含聚合根(唯一)、多个实体和值对象的对象聚合,在图中我们构建了课程和用户这两个聚合。
第四步:在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。
第五步:多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。
聚合的设计原则:
- 单一职责:聚合内的每个实体和值对象应该具有单一责任。它们应该专注于执行特定的任务,而不应该包含不相关的功能。
- 保持事务边界:聚合应该定义一个明确的事务边界。这意味着所有聚合内的实体和值对象应该作为一个整体一起加载、保存和维护。在跨聚合的操作中,事务应该在每个聚合内进行管理,实现对象数据的一致性,这就是聚合能实现业务高内聚的原因。
- 通过唯一标识引用其它聚合:聚合之间是通过关联外部聚合根ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
- 设计小聚合:如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
- 高内聚:聚合内的实体和值对象应该具有高内聚性,即它们应该紧密相关,共同支持聚合的目标。
这些原则有助于创建具有高内聚性、低耦合性和良好一致性的聚合,从而更好地管理领域中的复杂性。在DDD中,良好的聚合设计有助于将复杂的领域问题划分为更容易管理和理解的单元。
2.4 事件风暴
事件风暴是一种协作的过程,通常采用用例分析、业务场景分析和用户旅程分析等,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多实体、命令、事件等领域对象,通过将这些领域对象从不同维度进行聚类(将数据集中的对象或数据点按照某种相似性或相关性的标准分成不同的组别,即“簇”。聚类的目标是将相似的数据点分到同一簇中,使得同一簇内的数据点之间更加相似,而不同簇之间的数据点相对较不相似。),形成聚合、聚合根、上下文边界、限界上下文等边界,建立领域模型,这是一个收敛的过程。
事件风暴可以帮助开发团队、业务团队共同理解和共享对业务领域的见解,有助于捕获关键的业务事件(领域事件)、过程和概念,以指导软件系统的设计和实施,从而更好地支撑业务需求落地。此外,事件风暴有助于提高团队的沟通、协助和业务理解能力。事件风暴的过程可以大概分为以下几步:
- 会议准备: 确认会议参与者,通常包括领域专家、业务领域、开发人员等;明确风暴目标,确定需要解决的具体业务问题或挑战,以便确保风暴的焦点明确。
- 会议进行: 会议参与者一起聚焦本次会议需要解决的特定业务问题或理解领域概念。通常采用用例分析、业务场景分析和用户旅程分析 等,尽可能全面不遗漏地分解业务领域。
- 识别事件: 团队开始识别与业务问题或领域相关的事件。事件是业务过程中的关键时刻或状态变化。每个事件都应该有一个名称,例如,“用户注册”或“订单支付”。
- 绘制事件流: 将事件以时间顺序的方式进行绘制,以创建一个事件流。在事件流中,每个事件都有自己的标记,通常包括事件的名称、触发条件、内容和结果。事件之间可以使用箭头表示它们的顺序关系。
- 添加领域概念: 在事件风暴期间,团队还会标识和讨论与事件相关的领域概念,如聚合、聚合根、实体、值对象等。这有助于明确事件与领域中的关键概念之间的关系。
- 讨论和澄清: 团队成员之间会讨论事件和概念,澄清不明确的地方,确保大家对业务领域有一致的理解。
- 可视化领域: 通过可视化,团队能够更好地理解和共享对业务领域的见解。事件和概念的可视化帮助团队在整个过程中保持一致性。
- 生成模型: 根据事件风暴的结果,可以生成领域模型或其他文档,用于指导软件系统的设计和开发。这些模型通常包括领域对象、聚合根、实体和事件。
- 制定项目计划: 最后,团队制定项目计划,包括开发、测试和迭代的任务等。
事件风暴是一种非常有用的工具,用于在项目的早期阶段构建共享理解和对业务领域进行深入挖掘。它有助于软件团队更好地满足业务需求,确保领域知识的一致性,并提高团队协作的效率。
2.5 领域事件
在进行事件风暴时,除了命令和操作等业务行为以外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,例如:当老师做完课程信息上传时,需在某一个时间后通知学生进行选课,在 DDD 中这种事件被称为领域事件。
领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。领域事件也是微服务解耦的关键。
- 领域事件可以是业务流程的一个步骤,比如学生选课完成后,触发学生当前学期课程列表动作;
- 也可能是定时批处理过程中发生的事件,比如批处理选手选课情况,触发选课邮件通知操作;
- 或者一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作。
如何识别领域事件呢?
在做用例分析、业务场景分析和用户旅程分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”、“当做完……的时候,请通知……”、“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。
领域事件有的发生在微服务内,有的发生在微服务之间,还有两者皆有的场景,一般跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布/订阅。消息中间件的产品非常成熟,市场上可选的技术也非常多,比如RabbitMQ、Kafka、RocketMQ等。
三、DDD与微服务
3.1 DDD与微服务的关系
DDD 是一种架构设计方法,微服务是一种架构风格,两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。
- DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。
- 微服务主要关注:服务设计、拆分,运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。首先通过DDD战略设计,建立领域模型,划分微服务边界,每个边界都由一个或多个领域模型或聚合组成。然后通过战术设计,将领域模型转化为微服务设计,并进行落地。
3.2 基于DDD进行微服务设计
DDD 包括战略设计和战术设计两部分。
-
战略设计:战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。 战略设计通过事件风暴,通常采用用例分析、业务场景分析和用户旅程分析等,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。
- 战略设计主要包括产品愿景、用例分析、业务场景分析、用户旅程分析、领域建模和微服务拆分等几个主要过程。
- 战略设计阶段建议参与人员:领域专家、业务需求方、产品经理、架构师、项目经理、开发 经理和测试经理。
-
战术设计:战术设计侧重于从技术视角出发,根据领域模型进行微服务设计。这个阶段主要梳理微服务内的领域对象, 梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系,进而完成软件开发和落地,主要包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
- 战术设计包括以下两个阶段:分析微服务领域对象和设计微服务代码结构。
- 分析微服务领域对象;
- 设计微服务代码结构;
- 战术设计阶段建议参与人员:领域专家、产品经理、架构师、项目经理、开发经理和测试经理等。
- 战术设计包括以下两个阶段:分析微服务领域对象和设计微服务代码结构。
3.3 基于DDD重构中台业务模型
“中台” 是一种在软件和业务领域中经常使用的概念,它涉及到软件系统和业务流程的架构和设计。
中台的核心思想是将系统或业务流程中的共享资源、功能和服务抽象成一个中间层,以便多个应用程序或模块可以共享和重用这些资源。这种架构方法有助于提高系统的效率、可维护性和可扩展性,同时减少了冗余工作和开发成本。
中台的设计思想与“高内聚、低耦合”的设计原则是高度一致的。高内聚是把相关的业务行为聚集在一起,把不相关的行为放在其它地方,按照“高内聚、松耦合”的原则,实现企业级的能力复用。
中台的目标是通过资源共享、业务模块化、标准化接口和降低复杂度,使企业的IT系统更具弹性,更容易适应快速变化的业务需求。
将重复的需要共享的通用能力、核心能力沉淀到中台,将分离的业务能力重组为完整的业务板块,构建可复用的中台业务模型。中台业务建模有自顶向下和自底向上两种策略,这两种策略有自己的适用场景,你需要结合自己公司的情况选择合适的策略。
1. 自顶向下
自顶向下策略是先做顶层设计,从最高领域逐级分解为中台,分别建立领域模型,根据业务属性分为通用中台或核心中台。领域建模过程主要基于业务现状,暂时不考虑系统现状。自顶向下的策略适用于全新的应用系统建设,或旧系统推倒重建的情况。
由于这种策略不必受限于现有系统,你可以用 DDD 领域逐级分解的领域建模方法。从下面这张图我们可以看出它的主要步骤:
- 第一步是将领域分解为子域,子域可以分为核心域、通用域和支撑域;
- 第二步是对子域建模,划分领域边界,建立领域模型和限界上下文;
- 第三步是根据限界上下文进行微服务设计。
2. 自底向上
自底向上种策略是基于业务和系统现状完成领域建模。首先分别完成系统所在业务域的领域建模;然后对齐业务域,找出具有同类或相似业务功能的领域模型,对比分析领域模型的差异,重组领域对象,重构领域模型。这个过程会沉淀公共和复用的业务能力,会将分散的业务模型整合。自底向上策略适用于遗留系统业务模型的演进式重构。
如何采用自底向 上的策略来构建中台业务模型,主要分为这样三个步骤。
-
第一步:锁定各个系统所在业务域,构建领域模型。
锁定各个系统所在的业务域,采用事件风暴,找出领域对象,构建聚合,划分限界上下文,建立领域模型。那在构建中台业务模型时,需要重点关注那些存在业务能力重复的领域模型,将这些不同领域模型中重复的业务能力沉淀到中台业务模型中,将分散的领域模型整合到统一的中台业务模型中,对外提供统一的共享的中台服务。 -
第二步:对齐业务域,构建中台业务模型。
构建多业务域的中台业务模型的过程,就是找出同一业务域内所有同类业务的领域模型,对比分析域内领域模型和聚合的差异和共同点,打破原有的模型,完成新的中台业务模型重组或归并的过程。构建中台业务模型的要点总结成一句话就是:“分域建模型,找准基准域,划定上下文,聚合重归类。” -
第三步:中台归类,根据领域模型设计微服务。
完成中台业务建模后,就可以通过其领域模型中看到总共需要构建了多少个中台,中台下面有哪些领域模型,哪些中台是通用中台,哪些中台是核心中台,中台的基本信息等等,都一目了然。最后就可以根据中台下的领域模型进行微服务的设计。
四、总结
我认为,要想应用 DDD,首要任务就是要吃透 DDD 的核心设计思想,搞清楚 DDD、微服务和中台之间的关系。中台本质是业务模型,微服务是业务模型的具体实现,DDD 是一种设计思想,它可以同时指导中台业务建模和微服务设计,它们之间就是这样的一个铁三角关系。DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。DDD主要包括战略设计和战术设计两大部分。战略设计用于定义领域模型,确定业务边界,这对于中台和微服务的构建至关重要。在战术设计阶段,你需要将领域模型映射到微服务设计中,并严格遵循DDD原则,确保微服务与领域模型保持一致性。
拥有一套清晰的微服务设计和拆分方法是确保微服务架构的成功的关键。这种方法可以帮助你建立清晰的微服务边界,确保微服务系统的可维护性和可持续演进。
最终,DDD、中台和微服务的结合可以为企业提供高度灵活性和创新性,帮助他们更好地应对不断变化的市场需求。这种铁三角关系可以帮助企业建立坚实的技术基础,实现持续增长和竞争优势。