赞
踩
目录
领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。
微服务拆分困境产生的根本原因:不知道业务或者微服务的边界到底在什么地方。
DDD 核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
对于领域驱动设计的学习做的总结主要写三篇博客,主要包括三部分:基本理论总结与分析、架构分析与代码设计、具体应用设计分析,主要参考的资料为极客时间的欧创新架构师的《DDD》实战,其他参考书籍在文章下方的参考书籍中。
本次主要总结DDD架构分析与代码设计:
微服务架构模型现有的选择模型包括:整洁架构、CQRS 和六边形架构、DDD 分层架构等。
(注:CQRS架构之前博客中有讲,本次不做分析)
每种架构模式虽然提出的时代和背景不同,但其核心理念都是为了设计出“高内聚低耦合”的架构,轻松实现架构演进。
DDD 分层架构的思想使架构边界变得越来越清晰,它在微服务架构模型中,占有非常重要的位置。建议选择DDD 分层架构。
在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。
整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。
六边形架构的核心理念是:应用是通过端口与外部进行交互的。
也就是说,在下图的六边形架构中,红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。
六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。
六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用。
从上到下依次是:用户接口层、应用层、领域层和基础层。
用户接口层负责向用户显示信息和解释用户指令。
这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。
应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。
位于领域层之上,领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。
应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。
注意:
领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。
领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
注意:
基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。
基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。
DDD 并没有给出标准的代码模型,不同的人可能会有不同理解。这里我们在使用的时候还是建议按照欧创新架构师总结的来进行适用,具体如下:
根据 DDD 分层架构模型建立了标准的微服务代码模型,在代码模型里面,各代码对象各据其位、各司其职,共同协作完成微服务的业务逻辑。它包括用户接口层、应用层、领域层和基础层,分层架构各层的职责边界非常清晰,又能有条不紊地分层协作。
业务逻辑从领域层、应用层到用户接口层逐层封装和协作,对外提供灵活的服务,既实现了各层的分工,又实现了各层的协作。
微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。
主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。
具体如下:
主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
具体如下:
主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。
具体如下:
主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
具体如下:
应用层的主要领域对象是应用服务和事件的发布以及订阅。
在事件风暴或领域故事分析时,我们往往会根据用户或系统发起的命令,来设计服务或实体方法。为了响应这个命令,我们需要分析和记录:
在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。建议采用服务逐层封装的方式,服务的封装和调用主要有以下几种方式:
实体方法是最底层的原子业务逻辑。
经过逐层服务封装,实体方法就可以暴露给上面不同的层,实现跨层调用。
封装时服务前面的名字可以保持一致,你可以用 *DomainService 或 *AppService 后缀来区分领域服务或应用服务。
领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。
如果它需要暴露给用户接口层,领域服务就需要封装成应用服务。
应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。
多个应用服务可能会对多个同样的领域服务重复进行同样业务逻辑的组合和编排。当出现这种情况时,就需要分析是不是领域服务可以整合了。可以将这几个不断重复组合的领域服务,合并到一个领域服务中实现,这样领域模型将会越来越精炼,更能适应业务的要求。
应用服务类放在应用层 Service 目录结构下。领域事件的发布和订阅类放在应用层 Event 目录结构下。
事件风暴结束时,领域模型聚合内一般会有:聚合、实体、命令和领域事件等领域对象。
在完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。
大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的。
但某些领域模型的实体在微服务设计时,可能会被设计为多个数据实体,或者实体的某些属性被设计为值对象。
在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为,比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。
实体类放在领域层的 Entity 目录结构下。
聚合根来源于领域模型,聚合根是一种特殊的实体,它有自己的属性和方法。聚合根可以实现聚合之间的对象引用,还可以引用聚合内的所有实体。
聚合根类放在代码模型的 Entity 目录结构下。聚合根有自己的实现方法,比如生成客户编码,新增和修改客户信息等方法。
根据需要将某些实体的某些属性或属性集设计为值对象。值对象类放在代码模型的 Entity 目录结构下。
在个人客户聚合中,客户拥有客户证件类型,它是以枚举值的形式存在,所以将它设计为值对象。
有些领域对象可以设计为值对象,也可以设计为实体,我们需要根据具体情况来分析:
如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。
领域事件实体和处理类放在领域层的 Event 目录结构下。领域事件的发布和订阅类建议放在应用层的 Event 目录结构下。
如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。
领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
按照严格分层架构层的依赖关系:
领域服务类放在领域层的 Service 目录结构下。
每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。
仓储包括仓储的接口和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。
仓储代码放在领域层的 Repository 目录结构下。
聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。
这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。
写代码时一定要搞清楚代码的职责,将它放在职责对应的代码目录内。
应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码。
领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。
如果将核心领域逻辑代码放到应用层,你的基于 DDD 分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。
微服务设计的重点,就是看微服务设计是否能够支持架构长期、轻松的演进。
在事件风暴中,我们会梳理出业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出实体等领域对象。
为了方便理解,我们将这些边界细分为:逻辑边界、物理边界和代码边界。
逻辑边界主要定义同一业务领域或应用内紧密关联的对象所组成的不同聚类的组合之间的边界。
微服务内聚合之间的边界就是逻辑边界。一般来说微服务会有一个以上的聚合,在开发过程中不同聚合的代码隔离在不同的聚合代码目录中。它是一个虚拟的边界,强调业务的内聚,可根据需要变成物理边界,也就是说聚合也可以独立为微服务。
微服务的架构演进并不是随心所欲的,需要遵循一定的规则,这个规则就是逻辑边界。微服务架构演进时,在业务端以聚合为单位进行业务能力的重组,在微服务端以聚合的代码目录为单位进行微服务代码的重组。
来看一个微服务实例,在下面这张图中,我们可以看到微服务里包含了两个聚合的业务逻辑,两个聚合分别内聚了各自不同的业务能力,聚合内的代码分别归到了不同的聚合目录下。
那随着业务的快速发展,如果某一个微服务遇到了高性能挑战,需要将部分业务能力独立出去,我们就可以以聚合为单位,将聚合代码拆分独立为一个新的微服务,这样就可以很容易地实现微服务的拆分。
另外,我们也可以对多个微服务内有相似功能的聚合进行功能和代码重组,组合为新的聚合和微服务,独立为通用微服务,有点做中台的感觉!
微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。
物理边界主要从部署和运行的视角来定义微服务之间的边界。不同微服务部署位置和运行环境是相互物理隔离的,分别运行在不同的进程中。这种边界就是微服务之间的物理边界。
举例:
有些项目团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码仍然是集中式三层架构的模式,“微服务”内的代码高度耦合,逻辑边界不清晰,这里我们暂且称它为“小单体微服务”。
而随着新需求的提出和业务的发展,这些小单体微服务会慢慢膨胀起来。当有一天你发现这些膨胀了的微服务,有一部分业务功能需要拆分出去,或者部分功能需要与其它微服务进行重组时,你会发现原来这些看似清晰的微服务,不知不觉已经摇身一变,变成了臃肿油腻的大单体了,而这个大单体内的代码依然是高度耦合且边界不清的。
这种单体式微服务只定义了一个维度的边界,也就是微服务之间的物理边界,本质上还是单体架构模式。微服务设计时要考虑的不仅仅只有这一个边界,别忘了还要定义好微服务内的逻辑边界和代码边界,这样才能得到你想要的结果。
不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。
代码边界主要用于微服务内的不同职能代码之间的隔离。
微服务开发过程中会根据代码模型建立相应的代码目录,实现不同功能代码的隔离。由于领域模型与代码模型的映射关系,代码边界直接体现出业务边界。
代码边界可以控制代码重组的影响范围,避免业务和服务之间的相互影响。
微服务如果需要进行功能重组,只需要以聚合代码为单位进行重组就可以了。
按照分层架构设计出来的微服务,其内部有 Facade 服务、应用服务、领域服务和基础服务。之前都有提到,这里可以回忆一下!
微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动。
前端应用调用发布在 API 网关上的 Facade 服务,Facade 定向到应用服务。应用服务作为服务组织和编排者,它的服务调用有这样两种路径:
微服务之间的应用服务可以直接访问,也可以通过 API 网关访问。
由于跨微服务操作,在进行数据新增和修改操作时,你需关注分布式事务,保证数据的一致性。
领域事件驱动包括微服务内和微服务之间的事件。
当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。
当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。
微服务的服务是从领域层逐级向上封装、组合和暴露的。基本如下图体现:
在 DDD 中有很多的数据对象,这些对象分布在不同的层里。它们在不同的阶段有不同的形态:
微服务各层数据对象的职责和转换过程如下:
基础层的主要对象是 PO 对象。
我们需要先建立 DO 和 PO 的映射关系:
大多数情况下 PO 和 DO 是一一对应的。但也有 DO 和 PO 多对多的情况,在 DO 和 PO 数据转换时,需要进行数据重组。
领域层的主要对象是 DO 对象。
DO 是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑。
通过 DO 和 PO 转换,我们可以完成数据持久化和初始化。
应用层的主要对象是 DO 对象。
如果需要调用其它微服务的应用服务,DO 会转换为 DTO,完成跨微服务的数据组装和传输。
用户接口层先完成 DTO 到 DO 的转换,然后应用服务接收 DO 进行业务处理。如果 DTO 与 DO 是一对多的关系,这时就需要进行 DO 数据重组。
用户接口层会完成 DO 和 DTO 的互转,完成微服务与前端应用数据交互及转换。
Facade 服务会对多个 DO 对象进行组装,转换为 DTO 对象,向前端应用完成数据转换和传输。
前端应用主要是 VO 对象。
展现层使用 VO 进行界面展示,通过用户接口层与应用层采用 DTO 对象进行数据交互。
1.极客时间课程《DDD实战》,欧创新,2019.
2.郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.
3.陈超、秦金卫、张逸等. 高可用可伸缩微服务架构. 电子工业出版社. 2019.
4.Eric Evans. 领域驱动设计-软件核心复杂性应对之道。 人民邮电出版社. 2018.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。