极客时间已完结课程限时免费阅读

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

18 | 知识点串讲:基于DDD的微服务设计实例-极客时间

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

讲述:欧创新

时长21:20大小14.63M

你好,我是欧创新。
为了更好地理解 DDD 的设计流程,今天我会用一个项目来带你了解 DDD 的战略设计和战术设计,走一遍从领域建模到微服务设计的全过程,一起掌握 DDD 的主要设计流程和关键点。

项目基本信息

项目的目标是实现在线请假和考勤管理。功能描述如下:
请假人填写请假单提交审批,根据请假人身份、请假类型和请假天数进行校验,根据审批规则逐级递交上级审批,逐级核批通过则完成审批,否则审批不通过退回申请人。
根据考勤规则,核销请假数据后,对考勤数据进行校验,输出考勤统计。

战略设计

战略设计是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。
战略设计采用的方法是事件风暴,包括:产品愿景、场景分析、领域建模和微服务拆分等几个主要过程。
战略设计阶段建议参与人员:领域专家、业务需求方、产品经理、架构师、项目经理、开发经理和测试经理。

1. 产品愿景

产品愿景是对产品顶层价值设计,对产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
事件风暴时,所有参与者针对每一个要点,在贴纸上写出自己的意见,贴到白板上。事件风暴主持者会对每个贴纸,讨论并对发散的意见进行收敛和统一,形成下面的产品愿景图。
我们把这个产品愿景图整理成一段文字就是:为了满足内外部人员,他们的在线请假、自动考勤统计和外部人员管理的需求,我们建设这个在线请假考勤系统,它是一个在线请假平台,可以自动考勤统计。它可以同时支持内外网请假,同时管理内外部人员请假和定期考勤分析,而不像 HR 系统,只管理内部人员,且只能内网使用。我们的产品内外网皆可使用,可实现内外部人员无差异管理。
通过产品愿景分析,项目团队统一了系统名称——在线请假考勤系统,明确了项目目标和关键功能,与竞品(HR)的关键差异以及自己的优势和核心竞争力等。
产品愿景分析对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。但如果你的系统目标和需求非常清晰,这一步可以忽略。

2. 场景分析

场景分析是从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。
项目团队成员一起用事件风暴分析请假和考勤的用户旅程。根据不同角色的旅程和场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系等信息。
下面我就以请假和人员两个场景作为示例。
第一个场景:请假
用户:请假人
请假人登录系统:从权限微服务获取请假人信息和权限数据,完成登录认证。
创建请假单:打开请假页面,选择请假类型和起始时间,录入请假信息。保存并创建请假单,提交请假审批。
修改请假单:查询请假单,打开请假页面,修改请假单,提交请假审批。
提交审批:获取审批规则,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。
第二个场景:审批
用户:审批人
审批人登录系统:从权限微服务获取审批人信息和权限数据,完成登录认证。
获取请假单:获取审批人名下请假单,选择请假单。
审批:填写审批意见。
逐级审批:如果还需要上级审批,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。重复以上 4 步。
最后审批人完成审批。
完成审批后,产生请假审批已通过领域事件。后续有两个进一步的业务操作:发送请假审批已通过的通知,通知邮件系统告知请假人;将请假数据发送到考勤以便核销。
下面这个图是人员组织关系场景分析结果图,详细的分析过程以及考勤的场景分析就不描述了。

3. 领域建模

领域建模是通过对业务和问题域进行分析,建立领域模型。向上通过限界上下文指导微服务边界设计,向下通过聚合指导实体对象设计。
领域建模是一个收敛的过程,分三步:
第一步找出领域实体和值对象等领域对象;
第二步找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合;
第三步根据业务及语义边界等因素,定义限界上下文。
下面我们就逐步详细讲解一下。
第一步:找出实体和值对象等领域对象
根据场景分析,分析并找出发起或产生这些命令或领域事件的实体和值对象,将与实体或值对象有关的命令和事件聚集到实体。
下面这个图是分析后的实体与命令的关系。通过分析,我们找到了:请假单、审批意见、审批规则、人员、组织关系、刷卡明细、考勤明细以及考勤统计等实体和值对象。
第二步:定义聚合
定义聚合前,先找出聚合根。从上面的实体中,我们可以找出“请假单”和“人员”两个聚合根。然后找出与聚合根紧密依赖的实体和值对象。我们发现审批意见、审批规则和请假单紧密关联,组织关系和人员紧密关联。
找出这些实体的关系后,我们发现还有刷卡明细、考勤明细和考勤统计,这几个实体没有聚合根。这种情形在领域建模时你会经常遇到,对于这类场景我们需要分情况特殊处理。
刷卡明细、考勤明细和考勤统计这几个实体,它们之间相互独立,找不出聚合根,不是富领域模型,但它们一起完成考勤业务逻辑,具有很高的业务内聚性。我们将这几个业务关联紧密的实体,放在一个考勤聚合内。在微服务设计时,我们依然采用 DDD 的设计和分析方法。由于没有聚合根来管理聚合内的实体,我们可以用传统的方法来管理实体。
经过分析,我们建立了请假、人员组织关系和考勤三个聚合。其中请假聚合有请假单、审批意见实体和审批规则等值对象。人员组织关系聚合有人员和组织关系等实体。考勤聚合有刷卡明细、考勤明细和考勤统计等实体。
第三步:定义限界上下文
由于人员组织关系聚合与请假聚合,共同完成请假的业务功能,两者在请假的限界上下文内。考勤聚合则单独构成考勤统计限界上下文。因此我们为业务划分请假和考勤统计两个限界上下文,建立请假和考勤两个领域模型。

4. 微服务的拆分

理论上一个限界上下文就可以设计为一个微服务,但还需要综合考虑多种外部因素,比如:职责单一性、敏态与稳态业务分离、非功能性需求(如弹性伸缩、版本发布频率和安全等要求)、软件包大小、团队沟通效率和技术异构等非业务要素。
在这个项目,我们划分微服务主要考虑职责单一性原则。因此根据限界上下文就可以拆分为请假和考勤两个微服务。其中请假微服务包含人员组织关系和请假两个聚合,考勤微服务包含考勤聚合。
到这里,战略设计就结束了。通过战略设计,我们建立了领域模型,划分了微服务边界。下一步就是战术设计了,也就是微服务设计。下面我们以请假微服务为例,讲解其设计过程。

战术设计

战术设计是根据领域模型进行微服务设计的过程。这个阶段主要梳理微服务内的领域对象,梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系。
战术设计阶段建议参与人员:领域专家、产品经理、架构师、项目经理、开发经理和测试经理等。
战术设计包括以下两个阶段:分析微服务领域对象和设计微服务代码结构。

1. 分析微服务领域对象

领域模型有很多领域对象,但是这些对象带有比较重的业务属性。要完成从领域模型到微服务的落地,还需要进一步的分析和设计。在事件风暴基础上,我们进一步细化领域对象以及它们的关系,补充事件风暴可能遗漏的业务和技术细节。
我们分析微服务内应该有哪些服务?服务的分层?应用服务由哪些服务组合和编排完成?领域服务包括哪些实体和实体方法?哪个实体是聚合根?实体有哪些属性和方法?哪些对象应该设计为值对象等。
服务的识别和设计
事件风暴的命令是外部的一些操作和业务行为,也是微服务对外提供的能力。它往往与微服务的应用服务或者领域服务对应。我们可以将命令作为服务识别和设计的起点。具体步骤如下:
根据命令设计应用服务,确定应用服务的功能,服务集合,组合和编排方式。服务集合中的服务包括领域服务或其它微服务的应用服务。
根据应用服务功能要求设计领域服务,定义领域服务。这里需要注意:应用服务可能是由多个聚合的领域服务组合而成的。
根据领域服务的功能,确定领域服务内的实体以及功能。
设计实体基本属性和方法。
另外,我们还要考虑领域事件的异步化处理。
我以提交审批这个动作为例,来说明服务的识别和设计。提交审批的大体流程是:
根据请假类型和时长,查询请假审批规则,获取下一步审批人的角色。
根据审批角色从人员组织关系中查询下一审批人。
为请假单分配审批人,并将审批规则保存至请假单。
通过分析,我们需要在应用层和领域层设计以下服务和方法。
应用层:提交审批应用服务。
领域层:领域服务有查询审批规则、修改请假流程信息服务以及根据审批规则查询审批人服务,分别位于请假和人员组织关系聚合。请假单实体有修改请假流程信息方法,审批规则值对象有查询审批规则方法。人员实体有根据审批规则查询审批人方法。下图是我们分析出来的服务以及它们之间的依赖关系。
服务的识别和设计过程就是这样了,我们再来设计一下聚合内的对象。
聚合中的对象
在请假单聚合中,聚合根是请假单。
请假单经多级审核后,会产生多条审批意见,为了方便查询,我们可以将审批意见设计为实体。请假审批通过后,会产生请假审批通过的领域事件,因此还会有请假事件实体。请假聚合有以下实体:审批意见(记录审批人、审批状态和审批意见)和请假事件实体。
我们再来分析一下请假单聚合的值对象。请假人和下一审批人数据来源于人员组织关系聚合中的人员实体,可设计为值对象。人员类型、请假类型和审批状态是枚举值类型,可设计为值对象。确定请假审批规则后,审批规则也可作为请假单的值对象。请假单聚合将包含以下值对象:请假人、人员类型、请假类型、下一审批人、审批状态和审批规则。
综上,我们就可以画出请假聚合对象关系图了。
在人员组织关系聚合中,我们可以建立人员之间的组织关系,通过组织关系类型找到上级审批领导。它的聚合根是人员,实体有组织关系(包括组织关系类型和上级审批领导),其中组织关系类型(如项目经理、处长、总经理等)是值对象。上级审批领导来源于人员聚合根,可设计为值对象。人员组织关系聚合将包含以下值对象:组织关系类型、上级审批领导。
综上,我们又可以画出人员组织关系聚合对象关系图了。
微服务内的对象清单
在确定各领域对象的属性后,我们就可以设计各领域对象在代码模型中的代码对象(包括代码对象的包名、类名和方法名),建立领域对象与代码对象的一一映射关系了。根据这种映射关系,相关人员可快速定位到业务逻辑所在的代码位置。在经过以上分析后,我们在微服务内就可以分析出如下图的对象清单。

2. 设计微服务代码结构

根据 DDD 的代码模型和各领域对象所在的包、类和方法,我们可以定义出请假微服务的代码结构,设计代码对象。
应用层代码结构
应用层包括:应用服务、DTO 以及事件发布相关代码。在 LeaveApplicationService 类内实现与聚合相关的应用服务,在 LoginApplicationService 封装外部微服务认证和权限的应用服务。
这里提醒一下:如果应用服务逻辑复杂的话,一个应用服务就可以构建一个类,这样可以避免一个类的代码过于庞大,不利于维护。
领域层代码结构
领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码。一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调。
请假微服务领域层包含请假和人员两个聚合。人员和请假代码都放在各自的聚合所在目录结构的代码包中。如果随着业务发展,人员相关功能需要从请假微服务中拆分出来,我们只需将人员聚合代码包稍加改造,独立部署,即可快速发布为人员微服务。到这里,微服务内的领域对象,分层以及依赖关系就梳理清晰了。微服务的总体架构和代码模型也基本搭建完成了。

后续的工作

1. 详细设计

在完成领域模型和微服务设计后,我们还需要对微服务进行详细的设计。主要设计以下内容:实体属性、数据库表和字段、实体与数据库表映射、服务参数规约及功能实现等。

2. 代码开发和测试

开发人员只需要按照详细的设计文档和功能要求,找到业务功能对应的代码位置,完成代码开发就可以了。代码开发完成后,开发人员要编写单元测试用例,基于挡板模拟依赖对象完成服务测试。

总结

今天我们通过在线请假考勤项目,把 DDD 设计过程完整地走了一遍。
DDD 战略设计从事件风暴开始,然后我们要找出实体等领域对象,找出聚合根构建聚合,划分限界上下文,建立领域模型。
战术设计从事件风暴的命令开始,识别和设计服务,建立各层服务的依赖关系,设计微服务内的实体和值对象,找出微服务中所有的领域对象,并建立领域对象与代码对象的映射关系。
这样就可以很好地指导项目团队进行微服务开发和测试了。总结完毕,到这你是否已经清楚 DDD 全部的设计过程了呢?有疑问欢迎留言讨论。

思考题

你现在采用的是什么样的微服务设计方法?你认为有什么需要特别注意的事项呢?目前有何难点痛点?分享出来,也许我能给你一些有效的建议。
最后,如果今天的实战项目,可以让你举一反三、有所收获,欢迎分享给你的朋友,邀请他加入学习。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 38

提建议

上一篇
17 | 从后端到前端:微服务后,前端如何设计?
下一篇
19 | 总结(一):微服务设计和拆分要坚持哪些原则?
 写留言

精选留言(91)

  • movesan
    2020-05-23
    从第一课看到这里,终于有了一些领域建模的一些理解,希望可以慢慢的打开这扇门。 战略设计阶段: 此阶段主要是依赖于事件风暴(可理解为基于事件流程的头脑风暴),来呈现出产品的发展方向以及核心流程和场景,并文档化。 1.产品要解决的问题,以及从用户角度归纳出典型业务场景,落实文档 -----> 产品愿景、场景分析 2.找出上一步总结出的关键名词,作为各个场景的实体 -----> 领域建模:找出领域对象 3.根据上一步总结出实体,总结出之间的关系(聚合根、值对象、普通实体),划分出聚合 -----> 领域建模:定义聚合 4.以上一步归纳出的聚合为单位,根据业务场景将聚合分组,得到限界上下文(也就是所属的领域) ----->领域建模:定义界限上下文 感觉在第 1 步落实文档后,后面的 2,3,4 领域建模阶段都要不断的参照第 1 步总结出的业务流程场景来进行拆解与合并; 产品愿景、场景分析 两个阶段是从宏观到微观的过程,而 领域建模阶段是从微观到宏观的过程,也就是自底向上的思想。整体就像是总分、分总的过程。 战术设计阶段: 有了战略设计阶段的结果,反而战术设计阶段相对清晰一些。 1.按照 DDD 四层模型建包 2.确定聚合中的对象关系 3.通过战略设计阶段文档中的命令、事件来编排充血模型的领域对象,构建应用服务与领域服务 以上是初识领域驱动设计自己的一些理解,感觉如果战略设计阶段清晰完整,后面的战术设计阶段(代码落实阶段)会相对更容易一些。
    展开

    作者回复: 理解的很好。

    30
  • 盲僧
    2019-11-25
    新哥,把代码放到git上给兄弟们个地址吧

    作者回复: 我需要时间整理一下哈,等好了再共享。

    共 3 条评论
    24
  • Aries
    2019-12-30
    命令和事件那块感觉有些模糊,比如下单是命令还是事件呢? 按照我们的系统设计,下单则是事件。

    作者回复: 下单是命令,订单已下单是事件。

    共 4 条评论
    15
  • iMARS
    2020-10-12
    请教老师一个问题,在上述考勤系统中,在人员实体和组织关系实体之间,如何抉择人员是聚合根,而组织关系不是?或者说判断聚合根的依据是什么?谢谢

    作者回复: 判断一个实体是否是聚合根,你可以结合以下内容进行分析。是否有独立的生命周期?是否有全局唯一 ID ?是否可以创建或修改其他对象?是否有专门的模块来管理这个实体等。 聚合根管理了聚合内所有实体和值对象的生命周期,我们通过聚合根就可以获取到聚合内所有实体和值对象等领域对象。一般来说,如果聚合根被删除了,那么被它引用的实体和值对象也就不会存在了。 这个场景是以人员关系管理为主,所以人员就成为了聚合根,而组织关系只是描述人员之间的关系,所以成为实体,被人员聚合根引用。

    共 2 条评论
    13
  • zj
    2019-12-01
    我觉得老师可以讲一下CQRS,毕竟微服务好多都是要查询的哈哈

    作者回复: CQRS其实就是读写分离,主要解决DDD的复杂查询问题。一般是写库和读库分离,但是实效性不容易保证。其实你也可以在同一个库,用领域或者应用查询服务来完成复杂查询的。

    共 3 条评论
    8
  • Alex zhang
    2019-11-25
    老师,代码有github链接吗

    作者回复: 本来没准备放代码的哈,我后面花时间整理一下吧。

    共 3 条评论
    6
  • 2020-11-10
    老师这篇文章对DDD的理解效果非常高,实际的案例分析过程有一种Ddd不再是飘在空中,有点落地的感觉了,谢谢老师👨‍🏫,真的很用心

    作者回复: 感谢,掌握了DDD的设计思想和过程,相信微服务的拆分和设计不再是难事。

    5
  • 古腪
    2020-05-29
    有个场景希望老师帮分析下,一个是用户可以有多个角色,一个角色有多个用户,并且有多个权限,一个是权限可能配置多个角色,这种情况要怎么设计聚合咧?

    作者回复: 你看这样设计合适不?用户、角色和权限是三个不同的聚合,其实这三个聚合最关键的是用户聚合。角色和权限实际上属于配置类聚合。我们在完成用户的权限或者角色配置后,可以将角色和权限聚合相关的数据复制到用户聚合,作为用户聚合根的值对象。只要能够找到用户,就可以获取角色和权限的基本数据。

    共 3 条评论
    4
  • Tan
    2019-12-24
    产品愿景可以不安上面的来吗? 我的理解就是 1、产品是为谁服务 2、解决了什么问题 3、给产品确定名称 4、给产品定义功能 5、竞品分析 6、本产品的优势
    展开

    作者回复: 可以的啊,这只是一种手段。只要你们统一团队语言和目标,找到产品的核心价值点,多种方法都可以。

    3
  • Keith
    2021-04-18
    关于场景分析中的第一个场景的"请假人登录系统:从权限微服务获取请假人信息和权限数据,完成登录认证。", 这"权限微服务"哪里来, 业务场景还没确定怎么可能知道有哪些微服务, 而且用户也感知不到这些技术细节
    共 2 条评论
    2
  • 大飞
    2020-09-30
    新哥,问个有点奇怪的问题,在设计过程中,对于一些复杂的流程细节没考虑到位,或者忽略了某个细节流程,而导致在程序落地过程中,发现原有的建模不够严谨,对于这种场景,有什么补救措施吗,或者如何避免这一问题的发生?

    作者回复: 这个可能需要分一下几类情况来处理。 1、如果是聚合内实体的业务逻辑没考虑到,只需要修改对应实体内的属性或者方法即可。 2、如何是聚合内实体之间的关系没考虑到,调整或新增领域服务,或者聚合根的方法即可。 3、如果是在同一个限界上下文内的聚合之间的关系没考虑到,在应用层的应用服务中调整或新增即可。 4、如果是聚合划分到了错误的限界上下文内,整体将聚合内所有对象和代码调整到合适的限界上下文即可,并重新建立新的限界上下文内聚合之间的关系。

    2
  • JKing
    2020-05-06
    应用层我理解其实就是BFF

    作者回复: BFF是位于微服务之上,它的主要职责是负责微服务之间的服务协调和编排。而应用服务主要处理微服务内的服务组合和编排,它可以组合和编排领域服务。在小型项目里,它也可以编排其它微服务的应用服务。 在设计时我们应尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用。 BFF像齿轮一样,来适配前端应用与微服务之间的步调。它通过Facade服务适配不同的前端,通过服务组合和编排,组织和协调不同的微服务。 BFF微服务可根据需求和流程变化,与前端应用版本协同发布,避免中台微服务为适配前端需求的变化,而频繁地修改和发布版本,从而保证微服务版本和核心领域逻辑的稳定。

    2
  • zj
    2019-11-26
    推广活动为一个聚合,直播推广为一个聚合,但是这两个聚合之间又是有联系的,比如直播推广可以参与推广活动。那这样这个命令到底属于哪个聚合呢?还是说将推广活动作为一个值对象呢

    作者回复: 聚合内有实体吧,看看这些实体跟那个聚合根关联紧密,生命周期归聚合根管理,就放在跟聚合根在一起的聚合内,如果别的聚合要用,有两种方案,第一种是通过聚合根引用实体。第二种方案,在另外的聚合内将这个实体设计为值对象或者实体,值对象或实体的数据来源于另外的那个聚合的实体。

    2
  • Sylo Tsui
    2021-11-18
    需要DDD项目事例的,可以参考下开源项目SpringBootAdmin: https://github.com/codecentric/spring-boot-admin/tree/2.2.x 的 spring-boot-admin-server模块。当时看到还是深受裨益的。
    1
  • Geek_8dfa3e
    2021-01-30
    中台和应用的边界怎么定义?应用会有实体对象吗?哪些实体对象会落中台,哪些落应用

    作者回复: 我写的这篇文章里面有他们关系的详细介绍:https://xie.infoq.cn/article/cc8ab87bda888d0dfa686fc91

    1
  • Jie
    2020-06-13
    窗外37度,空调房内看得酣畅淋漓,之前的知识都串起来了!

    作者回复: 很好哈。

    1
  • 小之
    2020-05-25
    老师你好,我看这边直接从限界上下文入手了,这边为什么没有聊子域划分问题呢,子域和限界上下文的区别一直是个老大难问题,一个在问题空间,一个在解决方案空间,我们日常怎么取舍呢

    作者回复: 业务领域的子域划分其实是一种比较粗的领域边界的划分阶段,它不考虑子域内的领域对象以及对象之间的关系和层次结构。子域的划分往往可以按照业务流程或者功能模块的边界进行粗分,其目的就是为了逐步缩小业务边界,让你能够在一个相对较小的空间内,比较舒适的用事件风暴来梳理业务场景,构建领域模型。 关于子域与限界上下文的关系。其实你可以这样理解,有时候业务领域太大,不方便开展事件风暴,那我们就先把领域分解成若干个大小合适的子域,然后在这些子域内开展事件风暴,划分限界上下文完成领域建模。 限界上下文本质上也是子域,只不过它更多的考虑这些领域对象的语义边界。限界上下文的划分体现的是一种更为详细的设计过程,这个过程划分了业务的上下文语义边界,完成了领域模型,明确了领域对象以及领域对象之间的依赖关系等。

    共 2 条评论
    2
  • alex
    2019-12-10
    有没有一个基于 DDD 设计实现的实际可用的开源项目可以分享下

    作者回复: 正在准备,好了会通知大家的。

    1
  • 霹雳大仙pp
    2019-12-02
    审批规则值对象有查询审批规则方法?这里不是很明白。 不应该通过领域服务或者聚合根来做查询吗?这里的值对象是充血模型? 望老师回复。

    作者回复: 审批规则有两个,一个是审批规则的配置数据,独立存在。另一个是保存在请假单上的审批规则,它根据请假基本信息匹配审批规则配置数据后获得,只要请假基础数据不变,你就不需要在每次提交审批的时候去查询审批规则的配置数据。依附于请假单的这个审批规则是值对象。

    共 3 条评论
    1
  • MaLu
    2019-11-25
    这篇实例,将之前的铺垫进行的应用,很有参考与启发,我已珍藏为行动指南。

    作者回复: 照着样例来试着做几个,慢慢就能体会到DDD的设计精要了。

    1