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,139 @@
<audio id="audio" title="11 | DDD实践如何用DDD重构中台业务模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b0/79/b0b0110790317f92ab46705c7fbed179.mp3"></audio>
你好,我是欧创新。
进入两千年后随着互联网应用的快速发展很多传统企业开始触网建设自己的互联网电商平台。后来又随着微信和App等移动互联应用的兴起又形成了新一轮的移动应用热潮。这些移动互联应用大多面向个人或者第三方市场和需求变化快需要以更敏捷的速度适应市场变化为了保持快速响应能力和频繁发版的要求很多时候这些移动互联网应用是独立于传统核心系统建设的但两者承载的业务大部分又都是同质的因此很容易出现业务能力重叠的问题。
阿里巴巴过去带动了传统企业向互联网电商转型。而如今又到了一个新的历史时期,在阿里巴巴提出中台战略后,很多企业又紧跟它的步伐,高举中台大旗,轰轰烈烈地开始了数字化转型之路。
那么传统企业在中台转型时该如何从错综复杂的业务中构建中台业务模型呢今天我就用一个传统企业中台建模的案例带你一起用DDD的设计思想来构建中台业务模型。
## 传统企业应用分析
互联网电商平台和传统核心应用,两者面向的渠道和客户不一样,但销售的产品却很相似,它们之间的业务模型既有相同的地方,又有不同的地方。
现在我拿保险行业的互联网电商和传统核心应用来做个对比分析。我们看一下下面这张图,这两者在业务功能上会有很多相似和差异,这种相似和差异主要体现在四个方面。
<img src="https://static001.geekbang.org/resource/image/25/07/255beadd4cf7a842c083ccea0ec19807.jpg" alt="">
**1. 核心能力的重复建设。**由于销售同质保险产品,二者在核心业务流程和功能上必然相似,因此在核心业务能力上存在功能重叠是不可避免的。传统保险核心应用有报价、投保、核保和出单功能,同样在互联网电商平台也有。这就是核心能力的重复建设。
**2. 通用能力的重复建设。**传统核心应用的通用平台大而全,通常会比较重。而互联网电商平台离不开这些通用能力的支撑,但为了保持敏捷性,一般会自己建设缩小版的通用功能,比如用户、客户等。这是通用能力的重复建设。
**3. 业务职能的分离建设。**有一类业务功能在互联网电商平台中建设了一部分在传统核心应用中也建设了一部分二者功能不重叠而且还互补组合在一起是一个完整的业务职能。比如缴费功能互联网电商平台主要面向个人客户于是采用了支付宝和微信支付的方式。而传统核心应用主要是柜台操作仍在采用移动POS机的缴费方式。二者都是缴费为了保证业务模型的完整性在构建中台业务模型时我们可以考虑将这两部分模型重组为一个完整的业务模型。
**4. 互联网电商平台和传统核心功能前后完全独立建设。**传统核心应用主要面向柜台,不需要互联网电商平台的在线客服、话务、订单和购物车等功能。而互联网电商平台主要面向个人客户,它不需要后端比较重的再保、佣金、打印等功能。在构建中台业务模型时,对这种情况应区别对待,将面向后端业务管理的应用沉淀到后台,将前端能力构建为面向互联网渠道的通用中台,比如订单等。
## 如何避免重复造轮子?
要避免重复建设,就要理解中台的理念和思想。前面说了“**中台是企业级能力复用平台**”,“**复用**”用白话说就是重复使用,就是要避免重复造轮子的事情。
中台的设计思想与“高内聚、低耦合”的设计原则是高度一致的。高内聚是把相关的业务行为聚集在一起,把不相关的行为放在其它地方,如果你要修改某个业务行为,只需要修改一处。对了!中台就是要这样做,按照“高内聚、松耦合”的原则,实现企业级的能力复用!
那如果你的企业遇到了重复造轮子的情况,应该怎么处理?
你需要站在企业高度,将重复的需要共享的通用能力、核心能力沉淀到中台,将分离的业务能力重组为完整的业务板块,构建可复用的中台业务模型。前端个性能力归前端,后端管理能力归后台。建立前、中、后台边界清晰,融合协作的企业级可复用的业务模型。
## 如何构建中台业务模型?
我们可以用DDD领域建模的方法来构建中台业务模型。你可以选择两种建模策略自顶向下和自底向上的策略。具体采用哪种策略你需要结合公司的具体情况来分析下面我就来介绍一下这两种策略。
**1. 自顶向下的策略**
第一种策略是自顶向下。这种策略是先做顶层设计,从最高领域逐级分解为中台,分别建立领域模型,根据业务属性分为通用中台或核心中台。领域建模过程主要基于业务现状,暂时不考虑系统现状。自顶向下的策略适用于全新的应用系统建设,或旧系统推倒重建的情况。
由于这种策略不必受限于现有系统你可以用DDD领域逐级分解的领域建模方法。从下面这张图我们可以看出它的主要步骤第一步是将领域分解为子域子域可以分为核心域、通用域和支撑域第二步是对子域建模划分领域边界建立领域模型和限界上下文第三步则是根据限界上下文进行微服务设计。
<img src="https://static001.geekbang.org/resource/image/e6/da/e665d85381a9b2c599555cac6a06deda.jpg" alt="">
**2. 自底向上的策略**
第二种策略是自底向上。这种策略是基于业务和系统现状完成领域建模。首先分别完成系统所在业务域的领域建模;然后对齐业务域,找出具有同类或相似业务功能的领域模型,对比分析领域模型的差异,重组领域对象,重构领域模型。这个过程会沉淀公共和复用的业务能力,会将分散的业务模型整合。自底向上策略适用于遗留系统业务模型的演进式重构。
下面我以互联网电商和传统核心应用的几个典型业务域为例,带你了解具体如何采用自底向上的策略来构建中台业务模型,主要分为这样三个步骤。
**第一步:锁定系统所在业务域,构建领域模型。**
锁定系统所在的业务域,采用事件风暴,找出领域对象,构建聚合,划分限界上下文,建立领域模型。看一下下面这张图,我们选取了传统核心应用的用户、客户、传统收付和承保四个业务域以及互联网电商业务域,共计五个业务域来完成领域建模。
<img src="https://static001.geekbang.org/resource/image/f5/46/f537a7a43e77212c8a85241439b2f246.jpg" alt="">
从上面这张图中我们可以看到传统核心共构建了八个领域模型。其中用户域构建了用户认证和权限两个领域模型客户域构建了个人和团体两个领域模型传统收付构建了POS刷卡领域模型承保域构建了定报价、投保和保单管理三个领域模型。
互联网电商构建了报价、投保、订单、客户、用户认证和移动收付六个领域模型。
在这些领域模型的清单里,我们可以看到二者之间有很多名称相似的领域模型。深入分析后你会发现,这些名称相似的领域模型存在业务能力重复,或者业务职能分散(比如移动支付和传统支付)的问题。那在构建中台业务模型时,你就需要重点关注它们,将这些不同领域模型中重复的业务能力沉淀到中台业务模型中,将分散的领域模型整合到统一的中台业务模型中,对外提供统一的共享的中台服务。
**第二步:对齐业务域,构建中台业务模型。**
在下面这张图里,你可以看到右侧的传统核心领域模型明显多于左侧的互联网电商,那我们是不是就可以得出一个初步的结论:传统核心面向企业内大部分应用,大而全,领域模型相对完备,而互联网电商面向单一渠道,领域模型相对单一。
这个结论也给我们指明了一个方向:首先我们可以将传统核心的领域模型作为主领域模型,将互联网电商领域模型作为辅助模型来构建中台业务模型。然后再将互联网电商中重复的能力沉淀到传统核心的领域模型中,只保留自己的个性能力,比如订单。中台业务建模时,既要关注领域模型的完备性,也要关注不同渠道敏捷响应市场的要求。
<img src="https://static001.geekbang.org/resource/image/25/1d/25cd1e7fe14bfa22a752c1b184b9c91d.jpg" alt="">
有了上述这样一个思路,我们就可以开始构建中台业务模型了。
我们从互联网电商和传统核心的领域模型中,归纳并分离出能覆盖两个域的所有业务子域。通过分析,我们找到了用户、客户、承保、收付和订单五个业务域,它们是可以用于领域模型对比分析的基准域。
**下面我以客户为例,来给你讲一下客户中台业务模型的构建过程。**
互联网电商客户主要面向个人客户,除了有个人客户信息管理功能外,基于营销目的它还有客户积分功能,因此它的领域模型有个人和积分两个聚合。
而传统核心客户除了支持个人客户外,还有单位和组织机构等团体客户,它有个人和团体两个领域模型。其中个人领域模型中除了个人客户信息管理功能外,还有个人客户的评级、重复客户的归并和客户的统一视图等功能,因此它的领域模型有个人、视图、评级和归并四个聚合。
构建多业务域的中台业务模型的过程,就是找出同一业务域内所有同类业务的领域模型,对比分析域内领域模型和聚合的差异和共同点,打破原有的模型,完成新的中台业务模型重组或归并的过程。
我们将互联网电商和传统核心的领域模型分解后,我们找到了五个与个人客户领域相关的聚合,包括:个人、积分、评级、归并和视图。这五个聚合原来分别分散在互联网电商和传统核心的领域模型中,我们需要打破原有的领域模型,进行功能沉淀和聚合的重组,重新找出这些聚合的限界上下文,重构领域模型。
**最终个人客户的领域模型重构为:**个人、归并和视图三个聚合重构为个人领域模型(客户信息管理),评级和积分两个聚合重构为评级积分领域模型(面向个人客户)。到这里我们就完成了个人客户领域模型的构建了。
好像还漏掉点什么东西呢?对了,还有团队客户领域模型!其实团体客户很简单。由于它只在传统核心中出现,我们将它在传统核心中的领域模型直接拿过来用就行了。
至此我们就完成了客户中台业务模型的构建了,客户中台构建了个人、团体和评级积分三个领域模型。
通过客户中台业务模型的构建你是否get到构建中台业务模型的要点了呢总结成一句话就是“分域建模型找准基准域划定上下文聚合重归类。”
其它业务域其实也是一样的过程,在这里我就不一一讲述了,你可以自己练习一下,作为课后作业。完成后你可以对照下面这张图看一下,这就是其它业务域重构后的中台业务模型。
<img src="https://static001.geekbang.org/resource/image/fb/70/fb11e6941fc471c734d0b85c25cc5370.jpg" alt="">
**第三步:中台归类,根据领域模型设计微服务。**
完成中台业务建模后,我们就有了下面这张图。从这张图中我们可以看到总共构建了多少个中台,中台下面有哪些领域模型,哪些中台是通用中台,哪些中台是核心中台,中台的基本信息等等,都一目了然。你根据中台下的领域模型就可以设计微服务了。
<img src="https://static001.geekbang.org/resource/image/a8/c5/a88e9695c7198a1f88f537564ada0bc5.jpg" alt="">
## 重构过程中的领域对象
上面主要是从聚合的角度来描述中台业务模型的重组,是相对高阶的业务模块的重构。业务模型重构和聚合重组,往往会带来领域对象和业务行为的变化。下面我带你了解一下,在领域模型重组过程中,发生在更底层的领域对象的活动。
我们还是以客户为例来讲述。由于对象过多,我只选取了部分领域对象和业务行为。
传统核心客户领域模型重构之前,包含个人、团体和评级三个聚合,每个聚合内部都有自己的聚合根、实体、方法和领域服务等。
<img src="https://static001.geekbang.org/resource/image/ae/3c/ae33bc5c0cda28740363e39edbc1e53c.jpg" alt="">
互联网电商客户领域模型重构前包含个人和积分两个聚合,每个聚合包含了自己的领域对象、方法和领域服务等。
<img src="https://static001.geekbang.org/resource/image/d0/7d/d0f8fb06797a5983c7fd00d59d8be57d.jpg" alt="">
传统核心和互联网电商客户领域模型重构成客户中台后,建立了个人、团体和评级积分三个领域模型。其中个人领域模型有个人聚合,团体领域模型有团体聚合,评级积分领域模型有评级和积分两个聚合。这些领域模型的领域对象来自原来的领域模型,但积分评级是重组后的领域模型,它们原来的聚合会带着各自的领域对象,加入到新的领域模型中。
这里还要注意:部分领域对象可能会根据新的业务要求,从原来的聚合中分离,重组到其它聚合。新领域模型的领域对象,比如实体、领域服务等,在重组后可能还会根据新的业务场景和需求进行代码重构。
<img src="https://static001.geekbang.org/resource/image/f1/c4/f1b2e04d38ba13d8c318aa3539604bc4.jpg" alt="">
## 总结
今天我们一起讨论了传统企业中台数字化转型在面对多个不同渠道应用重复建设时如何用DDD领域建模的思想来构建中台业务模型。中台业务建模有自顶向下和自底向上两种策略这两种策略有自己的适用场景你需要结合自己公司的情况选择合适的策略。
其实呢,中台业务模型的重构过程,也是微服务架构演进的过程。业务边界即微服务边界,业务边界做好了,微服务的边界自然就会很好。
## 思考题
思考一下你公司的应用系统建设现状,是否存在重复建设的问题?你能否借用今天学到的方法来尝试构建中台呢?
欢迎留言分享,你也可以把今天所学分享给身边的朋友,邀请他一同交流、打卡。

View File

@@ -0,0 +1,126 @@
<audio id="audio" title="12 | 领域建模:如何用事件风暴构建领域模型?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/82/06/822b54f1f9197724bfe5a62530ef7b06.mp3"></audio>
你好,我是欧创新。
还记得我在 [[第01讲]](https://time.geekbang.org/column/article/149943) 中说过微服务设计为什么要选择DDD吗其中有一个非常重要的原因就是采用DDD方法建立的领域模型可以清晰地划分微服务的逻辑边界和物理边界。可以说在DDD的实践中好的领域模型直接关乎微服务的设计水平。因此我认为DDD的战略设计是比战术设计更为重要的也正是这个原因我们的内容会更侧重于战略设计。
那么我们该采用什么样的方法,才能从错综复杂的业务领域中分析并构建领域模型呢?
它就是我在前面多次提到的事件风暴。事件风暴是一项团队活动领域专家与项目团队通过头脑风暴的形式罗列出领域中所有的领域事件整合之后形成最终的领域事件集合然后对每一个事件标注出导致该事件的命令再为每一个事件标注出命令发起方的角色。命令可以是用户发起也可以是第三方系统调用或者定时器触发等最后对事件进行分类整理出实体、聚合、聚合根以及限界上下文。而事件风暴正是DDD战略设计中经常使用的一种方法它可以快速分析和分解复杂的业务领域完成领域建模。
那到底怎么做事件风暴呢?事件风暴需要提前准备些什么?又如何用事件风暴来构建领域模型呢?今天我们就来重点解决这些问题,深入了解事件风暴的全过程。
## 事件风暴需要准备些什么?
### 1. 事件风暴的参与者
事件风暴采用工作坊的方式,将项目团队和领域专家聚集在一起,通过可视化、高互动的方式一步一步将领域模型设计出来。领域专家是事件风暴中必不可少的核心参与者。很多公司可能并没有这个角色,那我们该寻找什么样的人来担当领域专家呢?
领域专家就是对业务或问题域有深刻见解的主题专家,他们非常了解业务和系统是怎么做的,同时也深刻理解为什么要这样设计。如果你的公司里并没有这个角色,那也没关系,你可以从业务人员、需求分析人员、产品经理或者在这个领域有多年经验的开发人员里,按照这个标准去选择合适的人选。
除了领域专家事件风暴的其他参与者可以是DDD专家、架构师、产品经理、项目经理、开发人员和测试人员等项目团队成员。
领域建模是统一团队语言的过程,因此项目团队应尽早地参与到领域建模中,这样才能高效建立起团队的通用语言。到了微服务建设时,领域模型也更容易和系统架构保持一致。
### 2. 事件风暴要准备的材料
事件风暴参与者会将自己的想法和意见写在即时贴上,并将贴纸贴在墙上的合适位置,我们戏称这个过程是“刷墙”。所以即时贴和水笔是必备材料,另外,你还可以准备一些胶带或者磁扣,以便贴纸随时能更换位置。
值得提醒一下的是,在这个过程中,我们要用不同颜色的贴纸区分领域行为。如下图,我们可以用蓝色表示命令,用绿色表示实体,橙色表示领域事件,黄色表示补充信息等。补充信息主要用来说明注意事项,比如外部依赖等。颜色并不固定,这只是我的习惯,团队内统一才是重点。
<img src="https://static001.geekbang.org/resource/image/3a/f8/3a80b8e7648440a49b809d945e6439f8.jpg" alt="">
### 3. 事件风暴的场地
什么样的场地适合做事件风暴呢?是不是需要跟组织会议一样,准备会议室、投影,还有椅子?这些都不需要!你只需要一堵足够长的墙和足够大的空间就可以了。墙是用来贴纸的,大空间可以让人四处走动,方便合作。撤掉会议桌和椅子的事件风暴,你会发现参与者们的效率更高。
事件风暴的发明者曾经建议要准备八米长的墙,这样设计就不会受到空间的限制了。当然,这个不是必要条件,看各自的现实条件吧,不要让思维受限就好。
### 4. 事件风暴分析的关注点
在领域建模的过程中,我们需要重点关注这类业务的语言和行为。比如某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?是谁(实体)发出的什么动作(命令),触发了这个动作(事件)…我们可以从这些暗藏的词汇中,分析出领域模型中的事件、命令和实体等领域对象。
## 如何用事件风暴构建领域模型?
领域建模的过程主要包括产品愿景、业务场景分析、领域建模和微服务拆分与设计这几个重要阶段。下面我以用户中台为例,介绍一下如何用事件风暴构建领域模型。
### 1. 产品愿景
产品愿景的主要目的是对产品顶层价值的设计,使产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
产品愿景的参与角色:领域专家、业务需求方、产品经理、项目经理和开发经理。
在建模之前,项目团队要思考这样两点:
- 用户中台到底能够做什么?
- 它的业务范围、目标用户、核心价值和愿景,与其它同类产品的差异和优势在哪里?
这个过程也是明确用户中台建设方向和统一团队思想的过程。参与者要对每一个点(下图最左侧列的内容)发表意见,用水笔写在贴纸上,贴在黄色贴纸的位置。这个过程会让参与者充分发表意见,最后会将发散的意见统一为通用语言,建立如下图的产品愿景墙。如果你的团队的产品愿景和目标已经很清晰了,那这个步骤你可以忽略。
<img src="https://static001.geekbang.org/resource/image/b8/c4/b85983fa6a8c877e77387fdafe1598c4.jpg" alt="">
### 2. 业务场景分析
场景分析是从用户视角出发的,根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模。事件风暴参与者要尽可能地遍历所有业务细节,充分发表意见,不要遗漏业务要点。
场景分析的参与角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
用户中台有这样三个典型的业务场景:
- 第一个是系统和岗位设置,设置系统中岗位的菜单权限;
- 第二个是用户权限配置,为用户建立账户和密码,设置用户岗位;
- 第三个是用户登录系统和权限校验,生成用户登录和操作日志。
我们可以按照业务流程一步一步搜寻用户业务流程中的关键领域事件比如岗位已创建用户已创建等事件。再找出什么行为会引起这些领域事件这些行为可能是一个或若干个命令组合在一起产生的比如创建用户时第一个命令是从公司HR系统中获取用户信息第二个命令是根据HR的员工信息在用户中台创建用户创建完用户后就会产生用户已创建的领域事件。当然这个领域事件可能会触发下一步的操作比如发布到邮件系统通知用户已创建但也可能到此就结束了你需要根据具体情况来分析是否还有下一步的操作。
场景分析时会产生很多的命令和领域事件。我用蓝色来表示命令用橙色表示领域事件用黄色表示补充信息比如用户信息数据来源于HR系统的说明。
<img src="https://static001.geekbang.org/resource/image/e2/e4/e2f91189e25bbaa81307d1fea694aee4.jpg" alt="">
### 3. 领域建模
领域建模时,我们会根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。领域模型利用限界上下文向上可以指导微服务设计,通过聚合向下可以指导聚合根、实体和值对象的设计。
领域建模的参与角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
具体可以分为这样三步。
第一步:从命令和事件中提取产生这些行为的实体。用绿色贴纸表示实体。通过分析用户中台的命令和事件等行为数据,提取了产生这些行为的用户、账户、认证票据、系统、菜单、岗位和用户日志七个实体。
<img src="https://static001.geekbang.org/resource/image/cf/fd/cf35a9437319169784db9e5aab97b1fd.jpg" alt="">
第二步:根据聚合根的管理性质从七个实体中找出聚合根,比如,用户管理用户相关实体以及值对象,系统可以管理与系统相关的菜单等实体等,可以找出用户和系统等聚合根。然后根据业务依赖和业务内聚原则,将聚合根以及它关联的实体和值对象组合为聚合,比如系统和菜单实体可以组合为“系统功能”聚合。按照上述方法,用户中台就有了系统功能、岗位、用户信息、用户日志、账户和认证票据六个聚合。
第三步:划定限界上下文,根据上下文语义将聚合归类。根据用户域的上下文语境,用户基本信息和用户日志信息这两个聚合共同构成用户信息域,分别管理用户基本信息、用户登录和操作日志。认证票据和账户这两个聚合共同构成认证域,分别实现不同方式的登录和认证。系统功能和岗位这两个聚合共同构成权限域,分别实现系统和菜单管理以及系统的岗位配置。根据业务边界,我们可以将用户中台划分为三个限界上下文:用户信息、认证和权限。
<img src="https://static001.geekbang.org/resource/image/d0/e1/d0191d4e4c51ff91dc830bf38c0e7ae1.jpg" alt="">
到这里我们就完成了用户中台领域模型的构建了。那由于领域建模的过程中产生的领域对象实在太多了,我们可以借助表格来记录。
<img src="https://static001.geekbang.org/resource/image/04/2c/04893881bfc410fac43ba5462f3be92c.jpg" alt="">
### 4. 微服务拆分与设计
我们在基础篇讲过,原则上一个领域模型就可以设计为一个微服务,但由于领域建模时只考虑了业务因素,没有考虑微服务落地时的技术、团队以及运行环境等非业务因素,因此在微服务拆分与设计时,我们不能简单地将领域模型作为拆分微服务的唯一标准,它只能作为微服务拆分的一个重要依据。
微服务的设计还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。除了考虑业务职责单一外,我们还需要考虑将敏态与稳态业务的分离、非功能性需求(如弹性伸缩要求、安全性等要求)、团队组织和沟通效率、软件包大小以及技术异构等非业务因素。
微服务设计建议参与的角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
用户中台微服务设计如果不考虑非业务因素,我们完全可以按照领域模型与微服务一对一的关系来设计,将用户中台设计为:用户、认证和权限三个微服务。但如果用户日志数据量巨大,大到需要采用大数据技术来实现,这时用户信息聚合与用户日志聚合就会有技术异构。虽然在领域建模时,我们将他们放在一个了领域模型内,但如果考虑技术异构,这两个聚合就不适合放到同一个微服务里了。我们可以以聚合作为拆分单位,将用户基本信息管理和用户日志管理拆分为两个技术异构的微服务,分别用不同的技术来实现它们。
## 总结
今天我们讲了事件风暴的设计方法以及如何用事件风暴来构建领域模型。事件风暴是一种不同于传统需求分析和系统设计的方法,最好的学习方法就是找几个业务场景多做几次。
综合我的经验,一般来说一个中型规模的项目,领域建模的时间大概在两周左右,这与我们传统的需求分析和系统设计的时间基本差不多。但是如果在领域建模的过程中,团队成员全员参与,在项目开发之前就建立了共同语言,这对于后续的微服务设计与开发是很有帮助的,时间成本也可以视情况降低。
其实我也了解到了很多开发人员在初次学习DDD时似乎并不太关心领域建模而只是想学学DDD的战术设计思想快速上手开发微服务。我想这是对DDD的一个误解这已经偏离了DDD的核心设计思想即先有边界清晰的领域模型才能设计出清晰的微服务边界这两个阶段一前一后是刚需我们不能忽略。
## 思考题
请找一个你擅长的业务领域,试着用事件风暴来构建一下领域模型。
欢迎留言分享,期待你的打卡!
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,128 @@
<audio id="audio" title="13 | 代码模型如何使用DDD设计微服务代码模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1b/f4/1bfc04771ecedc86c2ab37445801aff4.mp3"></audio>
你好,我是欧创新。
上一讲我们完成了领域模型的设计,接下来我们就要开始微服务的设计和落地了。那微服务落地时首先要确定的就是微服务的代码结构,也就是我今天要讲的微服务代码模型。
只有建立了标准的微服务代码模型和代码规范后,我们才可以将领域对象所对应的代码对象放在合适的软件包的目录结构中。标准的代码模型可以让项目团队成员更好地理解代码,根据代码规范实现团队协作;还可以让微服务各层的逻辑互不干扰、分工协作、各据其位、各司其职,避免不必要的代码混淆。另外,标准的代码模型还可以让你在微服务架构演进时,轻松完成代码重构。
那在DDD里微服务的代码结构长什么样子呢我们又是依据什么来建立微服务代码模型这就是我们今天重点要解决的两个问题。
## DDD分层架构与微服务代码模型
我们参考DDD分层架构模型来设计微服务代码模型。没错微服务代码模型就是依据DDD分层架构模型设计出来的。那为什么是DDD分层架构模型呢
<img src="https://static001.geekbang.org/resource/image/a3/01/a308123994f87a5ce99adc85dd9b4d01.jpg" alt="">
我们先简单回顾一下 [[第 07 讲]](https://time.geekbang.org/column/article/156849) 介绍过的DDD分层架构模型。它包括用户接口层、应用层、领域层和基础层分层架构各层的职责边界非常清晰又能有条不紊地分层协作。
- 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
- 应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
- 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
- 基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。
业务逻辑从领域层、应用层到用户接口层逐层封装和协作对外提供灵活的服务既实现了各层的分工又实现了各层的协作。因此毋庸置疑DDD分层架构模型就是设计微服务代码模型的最佳依据。
## 微服务代码模型
现在我们来看一下按照DDD分层架构模型设计出来的微服务代码模型到底长什么样子呢
其实DDD并没有给出标准的代码模型不同的人可能会有不同理解。下面要说的这个微服务代码模型是我经过思考和实践后建立起来的主要考虑的是微服务的边界、分层以及架构演进。
### 微服务一级目录结构
微服务一级目录是按照DDD分层架构的分层职责来定义的。从下面这张图中我们可以看到在代码模型里分别为用户接口层、应用层、领域层和基础层建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。
<img src="https://static001.geekbang.org/resource/image/d1/71/d1bea7dc6bd93f3bd30ced821f36bb71.jpg" alt="">
这些目录的职能和代码形态是这样的。
**Interfaces用户接口层**它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口向应用服务获取展现所需的数据。这一层主要用来处理用户发送的Restful请求解析用户输入的配置文件并将数据传递给Application层。数据的组装、数据传输格式以及Facade接口等代码都会放在这一层目录里。
**Application应用层**它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
**Domain领域层**它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。
**Infrastructure基础层**它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
### 各层目录结构
**1. 用户接口层**
Interfaces 的代码目录结构有assembler、dto 和 façade 三类。
<img src="https://static001.geekbang.org/resource/image/c6/ef/c6ea040a520c91dfe6400f206ff36fef.jpg" alt="">
**Assembler**实现DTO与领域对象之间的相互转换和数据交换。一般来说Assembler与DTO总是一同出现。
**Dto**它是数据传输的载体内部不存在任何业务逻辑我们可以通过DTO把内部的领域对象与外界隔离。
**Facade**提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
**2. 应用层**
Application 的代码目录结构有event 和 service。
<img src="https://static001.geekbang.org/resource/image/30/61/30caee3ceaf1085b7aa2cc388f996e61.jpg" alt="">
**Event事件**这层目录主要存放事件相关的代码。它包括两个子目录publish 和 subscribe。前者主要存放事件发布相关代码后者主要存放事件订阅相关代码事件处理相关的核心业务逻辑在领域层实现
这里提示一下:虽然应用层和领域层都可以进行事件的发布和处理,但为了实现事件的统一管理,我建议你将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。
**Service应用服务**这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。
**3. 领域层**
Domain 是由一个或多个聚合包构成共同实现领域模型的核心业务逻辑。聚合内的代码模型是标准和统一的包括entity、event、repository 和 service 四个子目录。
<img src="https://static001.geekbang.org/resource/image/68/2c/688dd55b8399779baff8fc5b7c124c2c.jpg" alt="">
而领域层聚合内部的代码目录结构是这样的。
**Aggregate聚合**它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。
以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。
**Entity实体**它存放聚合根、实体、值对象以及工厂模式Factory相关代码。实体类采用充血模型同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
**Event事件**它存放事件实体以及与事件活动相关的业务逻辑代码。
**Service领域服务**它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。
**Repository仓储**它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。
特别说明按照DDD分层架构仓储实现本应该属于基础层代码但为了在微服务架构演进时保证代码拆分和重组的便利性我是把聚合仓储实现的代码放到了聚合包内。这样如果需求或者设计发生变化导致聚合需要拆分或重组时我们就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移轻松实现微服务架构演进。
**4. 基础层**
Infrastructure 的代码目录结构有config 和 util 两个子目录。
<img src="https://static001.geekbang.org/resource/image/5b/5a/5bbe3454e2ecf4ff4770e887a4967b5a.jpg" alt="">
**Config**主要存放配置相关代码。
**Util**主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。
### 代码模型总目录结构
在完成一级和二级代码模型设计后,你就可以看到下图这样的微服务代码模型的总目录结构了。
<img src="https://static001.geekbang.org/resource/image/91/b8/915ad8d830d925a893cd09ff6cbdadb8.jpg" alt="">
## 总结
今天我们根据DDD分层架构模型建立了标准的微服务代码模型在代码模型里面各代码对象各据其位、各司其职共同协作完成微服务的业务逻辑。
那关于代码模型我还需要强调两点内容。
第一点:聚合之间的代码边界一定要清晰。聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。
第二点你一定要有代码分层的概念。写代码时一定要搞清楚代码的职责将它放在职责对应的代码目录内。应用层代码主要完成服务组合和编排以及聚合之间的协作它是很薄的一层不应该有核心领域逻辑代码。领域层是业务的核心领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层你的基于DDD分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。
## 思考题
对比一下DDD分层架构和三层架构的代码结构的差异
期待你的分享,我们一同交流!

View File

@@ -0,0 +1,176 @@
<audio id="audio" title="14 | 代码模型(下):如何保证领域模型与代码模型的一致性?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b7/f2/b7d8a2501c35001d951129638084aff2.mp3"></audio>
你好,我是欧创新。
在 [[第 12 讲]](https://time.geekbang.org/column/article/163961) 中,我们了解了如何用事件风暴来构建领域模型,在构建领域模型的过程中,我们会提取出很多的领域对象,比如聚合、实体、命令和领域事件等。到了 [[第 13 讲]](https://time.geekbang.org/column/article/165248)我们又根据DDD分层架构模型建立了标准的微服务代码模型为代码对象定义好了分层和目录结构。
那要想完成微服务的设计和落地,这之后其实还有一步,也就是我们今天的重点——将领域对象映射到微服务代码模型中。那为什么这一步如此重要呢?
DDD强调先构建领域模型然后设计微服务以保证领域模型和微服务的一体性因此我们不能脱离领域模型来谈微服务的设计和落地。但在构建领域模型时我们往往是站在业务视角的并且有些领域对象还带着业务语言。我们还需要将领域模型作为微服务设计的输入对领域对象进行设计和转换让领域对象与代码对象建立映射关系。
接下来我们围绕今天的重点,详细来讲一讲。
## 领域对象的整理
完成微服务拆分后,领域模型的边界和领域对象就基本确定了。
我们第一个重要的工作就是,整理事件风暴过程中产生的各个领域对象,比如:聚合、实体、命令和领域事件等内容,将这些领域对象和业务行为记录到下面的表格中。
你可以看到,这张表格里包含了:领域模型、聚合、领域对象和领域类型四个维度。一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。
<img src="https://static001.geekbang.org/resource/image/b5/ce/b5570b95095fd9103506fef3fa6a87ce.jpg" alt="">
## 从领域模型到微服务的设计
从领域模型到微服务落地,我们还需要做进一步的设计和分析。事件风暴中提取的领域对象,还需要经过用户故事或领域故事分析,以及微服务设计,才能用于微服务系统开发。
这个过程会比事件风暴来的更深入和细致。主要关注内容如下:
- 分析微服务内有哪些服务?
- 服务所在的分层?
- 应用服务由哪些服务组合和编排完成?
- 领域服务包括哪些实体的业务逻辑?
- 采用充血模型的实体有哪些属性和方法?
- 有哪些值对象?
- 哪个实体是聚合根等?
- 最后梳理出所有的领域对象和它们之间的依赖关系,我们会给每个领域对象设计对应的代码对象,定义它们所在的软件包和代码目录。
这个设计过程建议参与的角色有DDD专家、架构师、设计人员和开发经理。
### 领域层的领域对象
事件风暴结束时,领域模型聚合内一般会有:聚合、实体、命令和领域事件等领域对象。在完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。
下面我们就来看一下这些领域对象是怎么得来的?
**1. 设计实体**
大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的。但某些领域模型的实体在微服务设计时,可能会被设计为多个数据实体,或者实体的某些属性被设计为值对象。
我们分析个人客户时,还需要有地址、电话和银行账号等实体,它们被聚合根引用,不容易在领域建模时发现,我们需要在微服务设计过程中识别和设计出来。
在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为,比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。
实体类放在领域层的Entity目录结构下。
**2. 找出聚合根**
聚合根来源于领域模型,在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地址、电话以及银行账号的生命周期。个人客户聚合根通过工厂和仓储模式,实现聚合内地址、银行账号等实体和值对象数据的初始化和持久化。
聚合根是一种特殊的实体它有自己的属性和方法。聚合根可以实现聚合之间的对象引用还可以引用聚合内的所有实体。聚合根类放在代码模型的Entity目录结构下。聚合根有自己的实现方法比如生成客户编码新增和修改客户信息等方法。
**3. 设计值对象**
根据需要将某些实体的某些属性或属性集设计为值对象。值对象类放在代码模型的Entity目录结构下。在个人客户聚合中客户拥有客户证件类型它是以枚举值的形式存在所以将它设计为值对象。
有些领域对象可以设计为值对象,也可以设计为实体,我们需要根据具体情况来分析。如果这个领域对象在其它聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,我们就可以将它设计为值对象。如果这个对象是多条且需要基于它做查询统计,我建议将它设计为实体。
**4. 设计领域事件**
如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。首先确定领域事件发生在微服务内还是微服务之间。然后设计事件实体对象,事件的发布和订阅机制,以及事件的处理机制。判断是否需要引入事件总线或消息中间件。
在个人客户聚合中有客户已创建的领域事件,因此它有客户创建事件这个实体。
领域事件实体和处理类放在领域层的Event目录结构下。领域事件的发布和订阅类我建议放在应用层的Event目录结构下。
**5. 设计领域服务**
如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。你可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。所以如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。然后再被封装为创建个人客户信息应用服务,向前端应用暴露。
领域服务类放在领域层的Service目录结构下。
**6. 设计仓储**
每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。仓储包括仓储的接口和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。
仓储代码放在领域层的Repository目录结构下。
### 应用层的领域对象
应用层的主要领域对象是应用服务和事件的发布以及订阅。
在事件风暴或领域故事分析时,我们往往会根据用户或系统发起的命令,来设计服务或实体方法。为了响应这个命令,我们需要分析和记录:
- 在应用层和领域层分别会发生哪些业务行为;
- 各层分别需要设计哪些服务或者方法;
- 这些方法和服务的分层以及领域类型(比如实体方法、领域服务和应用服务等),它们之间的调用和组合的依赖关系。
在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。
如果需要实现服务的跨层调用,我们应该怎么办?我建议你采用服务逐层封装的方式。
<img src="https://static001.geekbang.org/resource/image/eb/b2/eb626396fcb9f541ec46a799275e04b2.png" alt="">
我们看一下上面这张图,服务的封装和调用主要有以下几种方式。
**1. 实体方法的封装**
实体方法是最底层的原子业务逻辑。如果单一实体的方法需要被跨层调用,你可以将它封装成领域服务,这样封装的领域服务就可以被应用服务调用和编排了。如果它还需要被用户接口层调用,你还需要将这个领域服务封装成应用服务。经过逐层服务封装,实体方法就可以暴露给上面不同的层,实现跨层调用。
封装时服务前面的名字可以保持一致,你可以用*DomainService或*AppService后缀来区分领域服务或应用服务。
**2. 领域服务的组合和封装**
领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。如果它需要暴露给用户接口层,领域服务就需要封装成应用服务。
**3.应用服务的组合和编排**
应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。
在应用服务组合和编排时,你需要关注一个现象:多个应用服务可能会对多个同样的领域服务重复进行同样业务逻辑的组合和编排。当出现这种情况时,你就需要分析是不是领域服务可以整合了。你可以将这几个不断重复组合的领域服务,合并到一个领域服务中实现。这样既省去了应用服务的反复编排,也实现了服务的演进。这样领域模型将会越来越精炼,更能适应业务的要求。
应用服务类放在应用层Service目录结构下。领域事件的发布和订阅类放在应用层Event目录结构下。
## 领域对象与微服务代码对象的映射
在完成上面的分析和设计后,我们就可以建立像下图一样的,领域对象与微服务代码对象的映射关系了。
### 典型的领域模型
个人客户领域模型中的个人客户聚合,就是典型的领域模型,从聚合内可以提取出多个实体和值对象以及它的聚合根。
我们看一下下面这个图,我们对个人客户聚合做了进一步的分析。提取了个人客户表单这个聚合根,形成了客户类型值对象,以及电话、地址、银行账号等实体,为实体方法和服务做了封装和分层,建立了领域对象的关联和依赖关系,还有仓储等设计。关键是这个过程,我们建立了领域对象与微服务代码对象的映射关系。
<img src="https://static001.geekbang.org/resource/image/c1/70/c1fce57f9e2a88ab2728db79ff45c770.png" alt="">
下面我对表格的各栏做一个简要的说明。
- 层:定义领域对象位于分层架构中的哪一层,比如:接口层、应用层、领域层以及基础层等。
- 领域对象:领域模型中领域对象的具体名称。
- 领域类型根据DDD知识体系定义的领域对象的类型包括限界上下文、聚合、聚合根、实体、值对象、领域事件、应用服务、领域服务和仓储服务等领域类型。
- 依赖的领域对象:根据业务对象依赖或分层调用的依赖关系,建立的领域对象的依赖关系,比如:服务调用依赖、关联对象聚合等。
- 包名:代码模型中的包名,对应领域对象所在的软件包。
- 类名:代码模型中的类名,对应领域对象的类名。
- 方法名:代码模型中的方法名,对应领域对象实现或操作的方法名。
在建立这种映射关系后,我们就可以得到如下图的微服务代码结构了。
<img src="https://static001.geekbang.org/resource/image/84/5e/84a486d4c0d9146462b31c7fcd5d835e.png" alt="">
### 非典型领域模型
有些业务场景可能并不能如你所愿,你可能无法设计出典型的领域模型。这类业务中有多个实体,实体之间相互独立,是松耦合的关系,这些实体主要参与分析或者计算,你找不出聚合根,但就业务本身来说它们是高内聚的。而它们所组合的业务与其它聚合是在一个限界上下文内,你也不大可能将它单独设计为一个微服务。
这种业务场景其实很常见。比如,在个人客户领域模型内有客户归并的聚合,它扫描所有客户,按照身份证号码、电话号码等是否重复的业务规则,判断是否是重复的客户,然后对重复的客户进行归并。这种业务场景你就找不到聚合根。
那对于这类非典型模型,我们怎么办?
我们还是可以借鉴聚合的思想仍然用聚合来定义这部分功能并采用与典型领域模型同样的分析方法建立实体的属性和方法对方法和服务进行封装和分层设计设计仓储建立领域对象之间的依赖关系。唯一可惜的就是我们依然找不到聚合根不过也没关系除了聚合根管理功能外我们还可以用DDD的其它设计方法。
## 总结
今天我们学习了从领域模型到微服务的设计过程,这个过程在微服务设计过程中非常的关键。你需要从微服务系统的角度,对领域模型做深入、细致的分析,为领域对象分层,找出各个领域对象的依赖关系,建立领域对象与微服务代码对象的映射关系,从而保证领域模型与代码模型的一致性,最终完成微服务的设计。
在建立这种业务模型与微服务系统架构的关系后,整个项目团队就可以在统一的通用语言下工作,即使不熟悉业务的开发人员,或者不熟悉代码的业务人员,也可以很快就定位到代码位置。
## 思考题
分析一下基于DDD领域模型的微服务设计方式和你公司现在所进行的微服务设计或者和你了解到的微服务设计有什么不同
期待你的分享,我们一同交流!

View File

@@ -0,0 +1,97 @@
<audio id="audio" title="15 | 边界:微服务的各种边界在架构演进中的作用?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/47/c1/47adac19535a0d7a70623f70a98e45c1.mp3"></audio>
你好,我是欧创新。
前几讲我们已经介绍过了在用DDD进行微服务设计时我们可以通过事件风暴来确定领域模型边界划定微服务边界定义业务和系统运行边界从而保证微服务的单一职责和随需而变的架构演进能力。
那重点落到边界的时候,总结一下就是,微服务的设计要涉及到逻辑边界、物理边界和代码边界等等。
那么这些边界在微服务架构演进中到底起到什么样的作用?我们又该如何理解这些边界呢?这就是我们今天重点要解决的问题。
## 演进式架构
在微服务设计和实施的过程中,很多人认为:“将单体拆分成多少个微服务,是微服务的设计重点。”可事实真的是这样吗?其实并非如此!
Martin [Fowler](http://www.baidu.com/link?url=YyTvvp3mcbbqbI5LH2o8zWknPkc42pT_71P2crD_LZ7nXMjI9dYbKmcHADU15M5zCZvQjqvj9P7NR6QKHTJzpq2-c6LwhMCz50sZMvmdlPu) 在提出微服务时,他提到了微服务的一个重要特征——演进式架构。那什么是演进式架构呢?演进式架构就是以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。
那如何判断微服务设计是否合理呢?其实很简单,只需要看它是否满足这样的情形就可以了:随着业务的发展或需求的变更,在不断重新拆分或者组合成新的微服务的过程中,不会大幅增加软件开发和维护的成本,并且这个架构演进的过程是非常轻松、简单的。
这也是微服务设计的重点,就是看微服务设计是否能够支持架构长期、轻松的演进。
那用DDD方法设计的微服务不仅可以通过限界上下文和聚合实现微服务内外的解耦同时也可以很容易地实现业务功能积木式模块的重组和更新从而实现架构演进。
## 微服务还是小单体?
有些项目团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码仍然是集中式三层架构的模式,“微服务”内的代码高度耦合,逻辑边界不清晰,这里我们暂且称它为“小单体微服务”。
下面这张图也很好地展示了这个过程。
<img src="https://static001.geekbang.org/resource/image/cc/eb/cc697f4e8eef2629a660d247c8a1eceb.jpg" alt="">
而随着新需求的提出和业务的发展,这些小单体微服务会慢慢膨胀起来。当有一天你发现这些膨胀了的微服务,有一部分业务功能需要拆分出去,或者部分功能需要与其它微服务进行重组时,你会发现原来这些看似清晰的微服务,不知不觉已经摇身一变,变成了臃肿油腻的大单体了,而这个大单体内的代码依然是高度耦合且边界不清的。
“辛辛苦苦好多年,一夜回到解放前啊!”这个时候你就需要一遍又一遍地重复着从大单体向单体微服务重构的过程。想想,这个代价是不是有点高了呢?
其实这个问题已经很明显了,那就是边界。
这种单体式微服务只定义了一个维度的边界,也就是微服务之间的物理边界,本质上还是单体架构模式。微服务设计时要考虑的不仅仅只有这一个边界,别忘了还要定义好微服务内的逻辑边界和代码边界,这样才能得到你想要的结果。
那现在你知道了我们一定要避免将微服务设计为小单体微服务那具体该如何避免呢清晰的边界人人想要可该如何保证呢DDD已然给出了答案。
## 微服务边界的作用
你应该还记得DDD设计方法里的限界上下文和聚合吧它们就是用来定义领域模型和微服务边界的。
我们再来回顾一下DDD的设计过程。
在事件风暴中,我们会梳理出业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出实体等领域对象。根据实体对象之间的业务关联性,将业务紧密相关的多个实体进行组合形成聚合,聚合之间是第一层边界。根据业务及语义边界等因素将一个或者多个聚合划定在一个限界上下文内,形成领域模型,限界上下文之间的边界是第二层边界。
为了方便理解,我们将这些边界分为:**逻辑边界、物理边界和代码边界**。
**逻辑边界**主要定义同一业务领域或应用内紧密关联的对象所组成的不同聚类的组合之间的边界。事件风暴对不同实体对象进行关联和聚类分析后,会产生多个聚合和限界上下文,它们一起组成这个领域的领域模型。微服务内聚合之间的边界就是逻辑边界。一般来说微服务会有一个以上的聚合,在开发过程中不同聚合的代码隔离在不同的聚合代码目录中。
逻辑边界在微服务设计和架构演进中具有非常重要的意义!
微服务的架构演进并不是随心所欲的需要遵循一定的规则这个规则就是逻辑边界。微服务架构演进时在业务端以聚合为单位进行业务能力的重组在微服务端以聚合的代码目录为单位进行微服务代码的重组。由于按照DDD方法设计的微服务逻辑边界清晰业务高内聚聚合之间代码松耦合因此在领域模型和微服务代码重构时我们就不需要花费太多的时间和精力了。
现在我们来看一个微服务实例,在下面这张图中,我们可以看到微服务里包含了两个聚合的业务逻辑,两个聚合分别内聚了各自不同的业务能力,聚合内的代码分别归到了不同的聚合目录下。
那随着业务的快速发展,如果某一个微服务遇到了高性能挑战,需要将部分业务能力独立出去,我们就可以以聚合为单位,将聚合代码拆分独立为一个新的微服务,这样就可以很容易地实现微服务的拆分。
<img src="https://static001.geekbang.org/resource/image/88/3d/88d709569367264d368b08a7d9658c3d.png" alt="">
另外,我们也可以对多个微服务内有相似功能的聚合进行功能和代码重组,组合为新的聚合和微服务,独立为通用微服务。现在你是不是有点做中台的感觉呢?
**物理边界**主要从部署和运行的视角来定义微服务之间的边界。不同微服务部署位置和运行环境是相互物理隔离的,分别运行在不同的进程中。这种边界就是微服务之间的物理边界。
**代码边界**主要用于微服务内的不同职能代码之间的隔离。微服务开发过程中会根据代码模型建立相应的代码目录,实现不同功能代码的隔离。由于领域模型与代码模型的映射关系,代码边界直接体现出业务边界。代码边界可以控制代码重组的影响范围,避免业务和服务之间的相互影响。微服务如果需要进行功能重组,只需要以聚合代码为单位进行重组就可以了。
## 正确理解微服务的边界
从上述内容中我们知道了按照DDD设计出来的逻辑边界和代码边界让微服务架构演进变得不那么费劲了。
微服务的拆分可以参考领域模型,也可以参考聚合,因为聚合是可以拆分为微服务的最小单位的。但实施过程是否一定要做到逻辑边界与物理边界一致性呢?也就是说聚合是否也一定要设计成微服务呢?答案是不一定的,这里就涉及到微服务过度拆分的问题了。
微服务的过度拆分会使软件维护成本上升,比如:集成成本、发布成本、运维成本以及监控和定位问题的成本等。在项目建设初期,如果你不具备较强的微服务管理能力,那就不宜将微服务拆分过细。当我们具备一定的能力以后,且微服务内部的逻辑和代码边界也很清晰,你就可以随时根据需要,拆分出新的微服务,实现微服务的架构演进了。
当然,还要记住一点,微服务内聚合之间的服务调用和数据依赖需要符合高内聚松耦合的设计原则和开发规范,否则你也不能很快完成微服务的架构演进。
## 总结
今天我们主要讨论了微服务架构设计中的各种边界在架构演进中的作用。
**逻辑边界:**微服务内聚合之间的边界是逻辑边界。它是一个虚拟的边界,强调业务的内聚,可根据需要变成物理边界,也就是说聚合也可以独立为微服务。
**物理边界:**微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。
**代码边界:**不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。
通过以上边界,我们可以让业务能力高内聚、代码松耦合,且清晰的边界,可以快速实现微服务代码的拆分和组合,轻松实现微服务架构演进。但有一点一定要格外注意,边界清晰的微服务,不是大单体向小单体的演进。
## 思考题
分享一下你们公司目前采用了什么样的方法来实现微服务的架构演进和DDD设计方法相比有何区别你觉得哪种方式更好呢可结合业务场景进行分析。
期待你的分享,你可以在留言区中畅所欲言,我们一同交流!

View File

@@ -0,0 +1,165 @@
<audio id="audio" title="16 | 视图:如何实现服务和数据在微服务各层的协作?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5f/8a/5fd9e9e65b56d3402563d030b9d9898a.mp3"></audio>
你好,我是欧创新。
在DDD分层架构和微服务代码模型里我们根据领域对象的属性和依赖关系将领域对象进行分层定义了与之对应的代码对象和代码目录结构。分层架构确定了微服务的总体架构微服务内的主要对象有服务和实体等它们一起协作完成业务逻辑。
那在运行过程中这些服务和实体在微服务各层是如何协作的呢今天我们就来解剖一下基于DDD分层架构的微服务看看它的内部结构到底是什么样的。
## 服务的协作
### 1. 服务的类型
我们先来回顾一下分层架构中的服务。按照分层架构设计出来的微服务其内部有Facade服务、应用服务、领域服务和基础服务。各层服务的主要功能和职责如下。
**Facade服务**位于用户接口层包括接口和实现两部分。用于处理用户发送的Restful请求和解析用户输入的配置文件等并将数据传递给应用层。或者在获取到应用层数据后将DO组装成DTO将数据传输到前端应用。
**应用服务:**位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果拼装,对外提供粗粒度的服务。
**领域服务:**位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域逻辑。它对多个实体或方法的业务逻辑进行组合或编排,或者在严格分层架构中对实体方法进行封装,以领域服务的方式供应用层调用。
**基础服务:**位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置提供基础资源服务。领域服务和应用服务都可以调用仓储服务接口,通过仓储服务实现数据持久化。
### 2. 服务的调用
我们看一下下面这张图。微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动。
<img src="https://static001.geekbang.org/resource/image/e5/db/e5d025a6fd69d1f2cf2a1af53253abdb.png" alt="">
**微服务内跨层服务调用**
微服务架构下往往采用前后端分离的设计模式前端应用独立部署。前端应用调用发布在API网关上的Facade服务Facade定向到应用服务。应用服务作为服务组织和编排者它的服务调用有这样两种路径
- 第一种是应用服务调用并组装领域服务。此时领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。
- 第二种是应用服务直接调用仓储服务。这种方式主要针对像缓存、文件等类型的基础层数据访问。这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。
**微服务之间的服务调用**
微服务之间的应用服务可以直接访问也可以通过API网关访问。由于跨微服务操作在进行数据新增和修改操作时你需关注分布式事务保证数据的一致性。
**领域事件驱动**
领域事件驱动包括微服务内和微服务之间的事件(详见 [[第 06 讲]](https://time.geekbang.org/column/article/155444)。微服务内通过事件总线EventBus完成聚合之间的异步处理。微服务之间通过消息中间件完成。异步化的领域事件驱动机制是一种间接的服务访问方式。
当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。
当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。
### 3. 服务的封装与组合
我们看一下下面这张图。微服务的服务是从领域层逐级向上封装、组合和暴露的。
<img src="https://static001.geekbang.org/resource/image/2d/1d/2d6a328a9fd8b4b3906bb9f59435ca1d.png" alt="">
**基础层**
基础层的服务形态主要是仓储服务。仓储服务包括接口和实现两部分。仓储接口服务供应用层或者领域层服务调用,仓储实现服务,完成领域对象的持久化或数据初始化。
**领域层**
领域层实现核心业务逻辑,负责表达领域模型业务概念、业务状态和业务规则。主要的服务形态有实体方法和领域服务。
实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,实现的形式是实体类中的方法。实体是微服务的原子业务逻辑单元。在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力。不必过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。
DDD提倡富领域模型尽量将业务逻辑归属到实体对象上实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排实现跨多个实体的复杂核心业务逻辑。
对于严格分层架构,如果单个实体的方法需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务。
**应用层**
应用层用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,负责不同聚合之间的服务和数据协调,负责微服务之间的事件发布和订阅。
通过应用服务对外暴露微服务的内部功能,这样就可以隐藏领域层核心业务逻辑的复杂性以及内部实现机制。应用层的主要服务形态有:应用服务、事件发布和订阅服务。
应用服务内用于组合和编排的服务,主要来源于领域服务,也可以是外部微服务的应用服务。除了完成服务的组合和编排外,应用服务内还可以完成安全认证、权限校验、初步的数据校验和分布式事务控制等功能。
为了实现微服务内聚合之间的解耦,聚合之间的服务调用和数据交互应通过应用服务来完成。原则上我们应该禁止聚合之间的领域服务直接调用和聚合之间的数据表关联。
**用户接口层**
用户接口层是前端应用和微服务之间服务访问和数据交换的桥梁。它处理前端发送的Restful请求和解析用户输入的配置文件等将数据传递给应用层。或获取应用服务的数据后进行数据组装向前端提供数据服务。主要服务形态是Facade服务。
Facade服务分为接口和实现两个部分。完成服务定向DO与DTO数据的转换和组装实现前端与应用层数据的转换和交换。
### 4. 两种分层架构的服务依赖关系
现在我们回顾一下DDD分层架构分层架构有一个重要的原则就是每层只能与位于其下方的层发生耦合。
那根据耦合的紧密程度,分层架构可以分为两种:严格分层架构和松散分层架构。在严格分层架构中,任何层只能与位于其直接下方的层发生依赖。在松散分层架构中,任何层可以与其任意下方的层发生依赖。
下面我们来详细分析和比较一下这两种分层架构。
**松散分层架构的服务依赖**
我们看一下下面这张图,在松散分层架构中,领域层的实体方法和领域服务可以直接暴露给应用层和用户接口层。松散分层架构的服务依赖关系,无需逐级封装,可以快速暴露给上层。
但它存在一些问题,第一个是容易暴露领域层核心业务的实现逻辑;第二个是当实体方法或领域服务发生服务变更时,由于服务同时被多层服务调用和组合,不容易找出哪些上层服务调用和组合了它,不方便通知到所有的服务调用方。
<img src="https://static001.geekbang.org/resource/image/5e/a1/5e901b4f7fa964b349e4d6f344786ea1.png" alt="">
我们再来看一张图在松散分层架构中实体A的方法在应用层组合后暴露给用户接口层aFacade。abDomainService领域服务直接越过应用层暴露给用户接口层abFacade服务。松散分层架构中任意下层服务都可以暴露给上层服务。
<img src="https://static001.geekbang.org/resource/image/b3/a0/b35d6fed54e26423c0d61de040ab04a0.jpeg" alt="">
**严格分层架构的服务依赖**
我们看一下下面这张图,在严格分层架构中,每一层服务只能向紧邻的上一层提供服务。虽然实体、实体方法和领域服务都在领域层,但实体和实体方法只能暴露给领域服务,领域服务只能暴露给应用服务。
在严格分层架构中,服务如果需要跨层调用,下层服务需要在上层封装后,才可以提供跨层服务。比如实体方法需要向应用服务提供服务,它需要封装成领域服务。
这是因为通过封装你可以避免将核心业务逻辑的实现暴露给外部,将实体和方法封装成领域服务,也可以避免在应用层沉淀过多的本该属于领域层的核心业务逻辑,避免应用层变得臃肿。还有就是当服务发生变更时,由于服务只被紧邻上层的服务调用和组合,你只需要逐级告知紧邻上层就可以了,服务可管理性比松散分层架构要好是一定的。
<img src="https://static001.geekbang.org/resource/image/ab/07/ab304d69ee174b5e69cb63d79864ca07.png" alt="">
我们还是看图A实体方法需封装成领域服务aDomainService才能暴露给应用服务aAppService。abDomainService领域服务组合和封装A和B实体的方法后暴露给应用服务abAppService。
<img src="https://static001.geekbang.org/resource/image/34/f9/348d60eac28c9dbf7d120d1b7159cdf9.png" alt="">
## 数据对象视图
在DDD中有很多的数据对象这些对象分布在不同的层里。它们在不同的阶段有不同的形态。你可以再回顾一下 [[第 04 讲]](https://time.geekbang.org/column/article/152677),这一讲有详细的讲解。
我们先来看一下微服务内有哪些类型的数据对象?它们是如何协作和转换的?
- 数据持久化对象PO(Persistent Object),与数据库结构一一映射,是数据持久化过程中的数据载体。
- 领域对象DODomain Object微服务运行时的实体是核心业务的载体。
- 数据传输对象DTOData Transfer Object用于前端与应用层或者微服务之间的数据组装和传输是应用之间数据传输的载体。
- 视图对象VOView Object用于封装展示层指定页面或组件的数据。
我们结合下面这张图,看看微服务各层数据对象的职责和转换过程。
<img src="https://static001.geekbang.org/resource/image/26/13/26dec215ba4359bdc30a1e2cc6007213.png" alt="">
**基础层**
基础层的主要对象是PO对象。我们需要先建立DO和PO的映射关系。当DO数据需要持久化时仓储服务会将DO转换为PO对象完成数据库持久化操作。当DO数据需要初始化时仓储服务从数据库获取数据形成PO对象并将PO转换为DO完成数据初始化。
大多数情况下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对象进行数据交互。
## 总结
今天我们分析了DDD分层架构下微服务的服务和数据的协作关系。为了实现聚合之间以及微服务各层之间的解耦我们在每层定义了不同职责的服务和数据对象。在软件开发过程中我们需要严格遵守各层服务和数据的职责要求各据其位各司其职。这样才能保证核心领域模型的稳定同时也可以灵活应对外部需求的快速变化。
## 思考题
你知道在微服务内为什么要设计不同的服务和不同的数据对象吗?它体现的是一种什么样的设计思想?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,157 @@
<audio id="audio" title="17 | 从后端到前端:微服务后,前端如何设计?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/f5/e66533b9b16aa0144e16588e60aa9af5.mp3"></audio>
你好,我是欧创新。
微服务架构通常采用前后端分离的设计方式。作为企业级的中台,在完成单体应用拆分和微服务建设后,前端项目团队会同时面对多个中台微服务项目团队,这时候的前端人员就犹如维修电工一样了。
面对如此多的微服务暴露出来的API服务如何进行正确的连接和拼装才能保证不出错这显然不是一件很容易的事情。而当服务出现变更时又如何通知所有受影响的项目团队这里面的沟通成本相信也不小。
相应的要从一定程度上解决上述问题我们是不是可以考虑先有效降低前端集成的复杂度呢先做到前端聚合后端解耦——这是一个很有意思的话题。今天我们就一起来聊聊微前端Micro Frontend的设计思想探讨一下中台微服务后前后端的设计和集成方式。
## 单体前端的困境
传统企业在完成中台转型后虽然后台的业务完成了微服务架构的升级但前端仍然是单体模式由一个团队创建并维护一个前端应用。随着时间推移和业务发展前端会变得越来越臃肿越来越难维护。而随着5G和移动互联技术的应用企业业务活动将会进一步移动化和线上化。过去很多企业的做法是为不同的业务开发出独立的APP。但很显然用户并不想装那么多的APP
为了提高用户体验实现统一运营很多企业开始缩减和整合APP将企业内所有的业务能力都尽量集中到一个APP中。试想如果仍然沿用单体前端的设计模式。前端项目团队将面对多个中台微服务团队需要集成成千上万的API服务这就需要相当高的沟通成本和技术要求。这绝对会是一场灾难。
相对互联网企业而言传统企业的渠道应用更加多样化有面向内部人员的门店类应用、面向外部客户的互联网电商平台或移动APP还有面向第三方的API集成。由于渠道的差异前端将更加多样化和复杂化。那如何有效降低前端集成的复杂度呢
## 从单体前端到微前端
为了解决单体前端的问题,我们可以借鉴微服务的设计思想,引入微前端概念。将微服务理念扩展到前端,解决中台微服务化后,前端由于仍为单体而存在的逻辑复杂和臃肿的问题。
在前端设计时我们需要遵循单一职责和复用原则按照领域模型和微服务边界将前端页面进行拆分。同时构建多个可以独立部署、完全自治、松耦合的页面组合其中每个组合只负责特定业务单元的UI元素和功能这些页面组合就是微前端。
微前端与微服务一样,都是希望将单体应用,按照规则拆分,并重组为多个可以独立开发、独立测试、独立部署和独立运维,松耦合的微前端或者微服务。以适应业务快速变化及分布式多团队并行开发的要求。
微前端页面只包括业务单元前端操作必需的页面要素,它只是企业级完整业务流程中的一个业务拼图块,不包含页面导航等内容。微前端除了可以实现前端页面的解耦外,还可实现页面复用,这也与中台服务共享理念是一脉相承的。
## 业务单元的组合形态
我们可以参照领域模型和微服务边界,建立与微服务对应的前端操作界面,将它与微服务组成业务单元,以业务组件的方式对外提供服务。业务单元包括微前端和微服务,可以独立开发、测试、部署和运维,可以自包含地完成领域模型中部分或全部的业务功能。
我们看一下下面这个图。一个虚框就是一个业务单元,微前端和微服务独立部署,业务单元内的微前端和微服务已完成前后端集成。你可以将这个业务单元理解为一个特定业务领域的组件。业务单元可以有多种组合方式,以实现不同的业务目标。
<img src="https://static001.geekbang.org/resource/image/35/6f/35d5b6465f6978b16a5ddfa49a910d6f.jpg" alt="">
### 1. 单一业务单元
一个微前端和一个微服务组成单一业务单元。微前端和微服务分别实现同一个领域模型从前端到后端的功能。
### 2. 组合业务单元
一个微前端与多个微服务组成组合业务单元。微前端具有多个微服务的前端功能,完成较复杂的页面和操作。多个微服务实现各自领域模型的功能,向微前端提供可组合的服务。
记住一点:微前端不宜与过多的微服务组合,否则容易变成单体前端。
### 3. 通用共享业务单元
一个微前端与一个或多个通用中台微服务组合为通用共享业务单元。通用共享微前端以共享页面的方式与其它微前端页面协作,完成业务流程。很多通用中台微服务的微前端是共享的,比如订单和支付等微服务对应的订单和支付微前端界面。
所有业务单元的功能都应该自包含,业务单元之间的边界清晰。业务单元之间要避免功能交叉而出现耦合,一旦出现就会影响项目团队职责边界,进而影响到业务单元独立开发、测试、部署和运维等。
## 微前端的集成方式
我们看一下下面这个图,微前端位于前端主页面和微服务之间,它需要与两者完成集成。
<img src="https://static001.geekbang.org/resource/image/44/5a/44a99543af27faabeda4f7fa959b875a.jpg" alt="">
### 1. 微前端与前端主页面的集成
前端主页面是企业级的前端页面,微前端是业务单元的前端页面。微前端通过主页面的微前端加载器,利用页面路由和动态加载等技术,将特定业务单元的微前端页面动态加载到前端主页面,实现前端主页面与微前端页面的“拼图式”集成。
微前端完成开发、集成和部署后,在前端主页面完成微前端注册以及页面路由配置,即可实现动态加载微前端页面。
### 2. 微前端与微服务的集成
微前端与微服务独立开发独立部署。在微前端注册到前端主页面前微前端需要与微服务完成集成。它的集成方式与传统前后端分离的集成方式没有差异。微服务将服务发布到API网关微前端调用发布在API网关中的服务即完成业务单元内的前后端集成。
## 团队职责边界
当你采用业务单元化的开发方式后,前后端项目团队职责和应用边界会更清晰,可以降低前后端集成的复杂度。我们看一下前中台团队的职责分工。
前端项目团队专注于前端集成主页面与微前端的集成,完成前端主页面的企业级主流程的页面和流程编排以及微前端页面的动态加载,确保主流程业务逻辑和流程正确。前端项目除了要负责企业内页面风格的整体风格设计、业务流程的流转和控制外,还需要负责微前端页面动态加载、微前端注册、页面路由和页面数据共享等前端技术的实现。
中台项目团队完成业务单元组件的开发、测试和集成,确保业务单元内的业务逻辑、页面和流程正确,向外提供包含页面逻辑和业务逻辑的业务单元组件。
这样,前端项目团队只需要完成企业级前端主页面与业务单元的融合,前端只关注前端主页面与微前端页面之间的集成。这样就可以降低前端团队的技术敏感度、团队的沟通成本和集成复杂度,提高交付效率和用户体验。
中台项目团队关注业务单元功能的完整性和自包含能力,完成业务单元内微服务和微前端开发、集成和部署,提供业务单元组件。这样,业务单元的微前端与微服务的集成就会由一个中台团队完成,熟悉的人干熟悉的事情,可以降低集成过程中的沟通和技术成本,加快开发效率。
## 一个有关保险微前端设计的案例
保险公司有很多面向不同场景的保险产品,由于业务场景不同,其核心领域模型就会有差异,在页面要素、业务规则和流程等方面前端界面也会不同。为了避免领域模型差异较大的产品之间的相互影响和干扰,我们可以将相似的领域模型的保险产品聚合在一起,完成核心中台设计。
那有的保险集团为了统一运营,会实现寿险、财险等集团化的全险种销售。这样前端项目团队就需要用一个前端应用,集成非常多的不同产品的核心中台微服务,前端应用与中台微服务之间的集成将会更复杂。
**如果仍然采用传统的单体前端模式,将会面临比较大的困难。**
第一是前端页面开发和设计的复杂性。以录单前端为例,如果用一个前端页面来适配全险种,由于不同产品的前端页面要素不同,需要妥协并兼容所有产品界面的差异,这会增加前端开发的复杂度,也影响用户体验。而如果为每类产品开发不同的前端,前端项目团队需要在页面开发和设计上,投入巨大的工作量。
第二是前端与微服务集成的复杂性。在前端与微服务集成时前端项目团队需要了解所有产品的API详细信息完成前端与微服务的集成还要根据主页面流程实现不同产品的API服务路由。大量的API服务集成和服务路由会增加系统集成的复杂度和出错的概率。
第三是前后端软件版本的协同发布。关联的应用多了以后一旦某一个中台微服务的API服务出现重大调整就需要协调所有受影响的应用同时完成版本发布频繁的版本发布会影响不同产品的正常运营。
那如何用一个前端应用实现全险种产品销售呢?怎样设计才能降低集成的复杂度,实现前端界面融合,后端中台解耦呢?
我们看一下下面这个图。我们借鉴了电商的订单模式实现保险产品的全险种订单化销售,在一个前端主页面可以将所有业务流程和业务操作无缝串联起来。虽然后端有很多业务单元(包含微服务和微前端),但用户始终感觉是在一个前端应用中操作。
要在一个前端应用中实现全险种销售,需要完成以下内容的设计。
<img src="https://static001.geekbang.org/resource/image/7d/d4/7d0eff75e60913a01aadfc7c6b24dad4.jpg" alt="">
### 1. 微服务
微服务分为两类,一类是核心中台微服务,包括:投保微服务,实现核心出单业务逻辑;另一类是通用中台微服务,包括如:商品、订单、购物车和支付等微服务,实现通用共享业务逻辑。
### 2. 微前端
每个微服务都有自己的微前端页面,实现领域模型的微服务前端页面操作。核心中台投保微服务有出单微前端。订单、商品以及支付微服务都有自己的微前端页面。
### 3. 业务单元
微服务与微前端组合为一个业务单元。由一个中台团队完成业务单元的开发、集成、测试和部署,确保业务单元内页面操作和业务逻辑正确。比如:投保微服务和出单微前端组合为投保业务单元,独立完成保险产品从前端到后端的投保业务。
### 4. 前端主页面
前端主页面类似门户,包括页面导航以及部分通用的常驻主页面的共享页面,比如购物车。前端主页面和所有微前端应统一界面风格,符合统一的前端集成规范。按照正确的业务逻辑和规则,动态加载不同业务单元的微前端页面。前端主页面作为一个整体,协调核心和通用业务单元的微前端页面,完成业务操作和业务流程,提供全险种销售接触界面,包括商品目录、录单、购物车、订单、支付等操作。
### 5. 业务流程说明
我来简要说明一下用户在前端主页面的投保的主要业务流程。
- 第1步用户在前端主页面从商品目录微前端页面选择保险产品。
- 第2步前端主页面根据选择的产品从主页面配置数据中获取产品出单微前端路由地址。加载出单微前端页面完成录单投保微服务实现投保业务逻辑在业务单元内生成投保单。
- 第3步加载购物车微前端将投保单加入购物车。
- 第4步重复1-3步生成多个投保单。
- 第5步从购物车微前端中选择多个投保单加载订单微前端生成订单。
- 第6步加载支付微前端完成支付。
- 第7步在投保微服务中将订单中的投保单生成保单。
虽然后端有很多业务单元在支持,但用户所有的页面操作和流转是在一个前端主页面完成的。在进行全险种的订单化销售时,用户始终感觉是在操作一个系统。这种设计方式很好地体现了前端的融合和中台的解耦。
## 总结
今天我们主要探讨了微前端的设计方法。虽然微前端和微服务也采用前后端分离的设计方式,但在业务单元内,它们是在同一个领域模型下,分别实现前端和后端的业务逻辑,对外提供组件化的服务。
微前端和业务单元化的设计模式可以减轻企业级中台,前后端应用开发和集成的复杂度,真正实现前端融合和中台解耦。它的主要价值和意义如下:
**1. 前端集成简单:**前端项目只需关注前端集成主页面与微前端的集成,实现模块化集成和拼图式的开发,降低前端集成的复杂度和成本。
**2. 项目职责专一:**中台项目从数据库、中台微服务到微前端界面,端到端地完成领域逻辑功能开发,以业务组件的方式整体提供服务。在业务单元内,由团队自己完成前后端集成,可以降低开发和集成团队的沟通成本和集成复杂度。
**3. 隔离和依赖性:**业务单元在代码、逻辑和物理边界都是隔离的,可降低应用之间的依赖性。出现问题时可快速定位和修复,问题可以控制在一个业务单元内。业务单元之间相互无影响。
**4. 降低沟通和测试成本:**中台团队实现从微前端页面到中台微服务的业务单元逻辑,实现业务单元的开发、测试、集成和部署的全流程和全生命周期管理,降低前后端集成的测试和沟通成本。
**5. 更敏捷地发布:**业务单元之间有很好的隔离性和依赖性低,业务单元的变化都可以被控制在业务单元内。项目团队可以独立按照自己的步调进行迭代开发,实现更快的发布周期。版本发布时不会影响其它业务单元的正常运行。
**6. 降低技术敏感性:**前端项目关注前端主页面与微前端的集成。降低了前端项目团队对中台微服务技术的敏感性。中台项目团队可以更独立地尝试新技术和架构,实现架构的演进。
**7. 高度复用性:**微前端和中台微服务都有高度的复用性。微前端可快速加载到多个APP还可以将一个微前端直接发布为APP或微信小程序实现灵活的前端组合、复用和快速发布。
## 思考题
结合你公司的业务场景,思考一下是否可以采用微前端的设计,降低前后端集成的复杂度?期待你的分享!
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,223 @@
<audio id="audio" title="18 | 知识点串讲基于DDD的微服务设计实例" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f5/1d/f56794153ce3d9c39b14cadae6506e1d.mp3"></audio>
你好,我是欧创新。
为了更好地理解DDD的设计流程今天我会用一个项目来带你了解DDD的战略设计和战术设计走一遍从领域建模到微服务设计的全过程一起掌握DDD的主要设计流程和关键点。
## 项目基本信息
项目的目标是实现在线请假和考勤管理。功能描述如下:
1. 请假人填写请假单提交审批,根据请假人身份、请假类型和请假天数进行校验,根据审批规则逐级递交上级审批,逐级核批通过则完成审批,否则审批不通过退回申请人。
1. 根据考勤规则,核销请假数据后,对考勤数据进行校验,输出考勤统计。
## 战略设计
战略设计是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。
战略设计采用的方法是事件风暴,包括:产品愿景、场景分析、领域建模和微服务拆分等几个主要过程。
战略设计阶段建议参与人员:领域专家、业务需求方、产品经理、架构师、项目经理、开发经理和测试经理。
### 1. 产品愿景
产品愿景是对产品顶层价值设计,对产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
事件风暴时,所有参与者针对每一个要点,在贴纸上写出自己的意见,贴到白板上。事件风暴主持者会对每个贴纸,讨论并对发散的意见进行收敛和统一,形成下面的产品愿景图。
<img src="https://static001.geekbang.org/resource/image/ef/b2/ef218e23ea2a8dc756af885ae06e61b2.jpg" alt="">
**我们把这个产品愿景图整理成一段文字就是:**为了满足内外部人员他们的在线请假、自动考勤统计和外部人员管理的需求我们建设这个在线请假考勤系统它是一个在线请假平台可以自动考勤统计。它可以同时支持内外网请假同时管理内外部人员请假和定期考勤分析而不像HR系统只管理内部人员且只能内网使用。我们的产品内外网皆可使用可实现内外部人员无差异管理。
通过产品愿景分析项目团队统一了系统名称——在线请假考勤系统明确了项目目标和关键功能与竞品HR的关键差异以及自己的优势和核心竞争力等。
产品愿景分析对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。但如果你的系统目标和需求非常清晰,这一步可以忽略。
### 2. 场景分析
场景分析是从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。
项目团队成员一起用事件风暴分析请假和考勤的用户旅程。根据不同角色的旅程和场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系等信息。
**下面我就以请假和人员两个场景作为示例。**
第一个场景:请假<br>
用户:请假人
- 请假人登录系统:从权限微服务获取请假人信息和权限数据,完成登录认证。
- 创建请假单:打开请假页面,选择请假类型和起始时间,录入请假信息。保存并创建请假单,提交请假审批。
- 修改请假单:查询请假单,打开请假页面,修改请假单,提交请假审批。
- 提交审批:获取审批规则,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。
第二个场景:审批<br>
用户:审批人
- 审批人登录系统:从权限微服务获取审批人信息和权限数据,完成登录认证。
- 获取请假单:获取审批人名下请假单,选择请假单。
- 审批:填写审批意见。
- 逐级审批如果还需要上级审批根据审批规则从人员组织关系中获取审批人给请假单分配审批人。重复以上4步。
- 最后审批人完成审批。
完成审批后,产生请假审批已通过领域事件。后续有两个进一步的业务操作:发送请假审批已通过的通知,通知邮件系统告知请假人;将请假数据发送到考勤以便核销。
<img src="https://static001.geekbang.org/resource/image/33/c5/33cbdd0f30a400a0cb9a2bd7ca5d54c5.jpg" alt="">
下面这个图是人员组织关系场景分析结果图,详细的分析过程以及考勤的场景分析就不描述了。
<img src="https://static001.geekbang.org/resource/image/18/74/18acc5f5681c61a37957e5036f176274.jpg" alt="">
### 3. 领域建模
领域建模是通过对业务和问题域进行分析,建立领域模型。向上通过限界上下文指导微服务边界设计,向下通过聚合指导实体对象设计。
领域建模是一个收敛的过程,分三步:
- 第一步找出领域实体和值对象等领域对象;
- 第二步找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合;
- 第三步根据业务及语义边界等因素,定义限界上下文。
下面我们就逐步详细讲解一下。
**第一步:找出实体和值对象等领域对象**
根据场景分析,分析并找出发起或产生这些命令或领域事件的实体和值对象,将与实体或值对象有关的命令和事件聚集到实体。
下面这个图是分析后的实体与命令的关系。通过分析,我们找到了:请假单、审批意见、审批规则、人员、组织关系、刷卡明细、考勤明细以及考勤统计等实体和值对象。
<img src="https://static001.geekbang.org/resource/image/97/28/97c049d18f7e7032f6feef70992a4828.jpg" alt="">
**第二步:定义聚合**
定义聚合前,先找出聚合根。从上面的实体中,我们可以找出“请假单”和“人员”两个聚合根。然后找出与聚合根紧密依赖的实体和值对象。我们发现审批意见、审批规则和请假单紧密关联,组织关系和人员紧密关联。
找出这些实体的关系后,我们发现还有刷卡明细、考勤明细和考勤统计,这几个实体没有聚合根。这种情形在领域建模时你会经常遇到,对于这类场景我们需要分情况特殊处理。
刷卡明细、考勤明细和考勤统计这几个实体它们之间相互独立找不出聚合根不是富领域模型但它们一起完成考勤业务逻辑具有很高的业务内聚性。我们将这几个业务关联紧密的实体放在一个考勤聚合内。在微服务设计时我们依然采用DDD的设计和分析方法。由于没有聚合根来管理聚合内的实体我们可以用传统的方法来管理实体。
经过分析,我们建立了请假、人员组织关系和考勤三个聚合。其中请假聚合有请假单、审批意见实体和审批规则等值对象。人员组织关系聚合有人员和组织关系等实体。考勤聚合有刷卡明细、考勤明细和考勤统计等实体。
<img src="https://static001.geekbang.org/resource/image/bb/76/bb9f2a7095da0c72504e0195dca34376.jpg" alt="">
**第三步:定义限界上下文**
由于人员组织关系聚合与请假聚合,共同完成请假的业务功能,两者在请假的限界上下文内。考勤聚合则单独构成考勤统计限界上下文。因此我们为业务划分请假和考勤统计两个限界上下文,建立请假和考勤两个领域模型。
### 4. 微服务的拆分
理论上一个限界上下文就可以设计为一个微服务,但还需要综合考虑多种外部因素,比如:职责单一性、敏态与稳态业务分离、非功能性需求(如弹性伸缩、版本发布频率和安全等要求)、软件包大小、团队沟通效率和技术异构等非业务要素。
在这个项目,我们划分微服务主要考虑职责单一性原则。因此根据限界上下文就可以拆分为请假和考勤两个微服务。其中请假微服务包含人员组织关系和请假两个聚合,考勤微服务包含考勤聚合。
到这里,战略设计就结束了。通过战略设计,我们建立了领域模型,划分了微服务边界。下一步就是战术设计了,也就是微服务设计。下面我们以请假微服务为例,讲解其设计过程。
## 战术设计
战术设计是根据领域模型进行微服务设计的过程。这个阶段主要梳理微服务内的领域对象,梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系。
战术设计阶段建议参与人员:领域专家、产品经理、架构师、项目经理、开发经理和测试经理等。
战术设计包括以下两个阶段:分析微服务领域对象和设计微服务代码结构。
### 1. 分析微服务领域对象
领域模型有很多领域对象,但是这些对象带有比较重的业务属性。要完成从领域模型到微服务的落地,还需要进一步的分析和设计。在事件风暴基础上,我们进一步细化领域对象以及它们的关系,补充事件风暴可能遗漏的业务和技术细节。
我们分析微服务内应该有哪些服务?服务的分层?应用服务由哪些服务组合和编排完成?领域服务包括哪些实体和实体方法?哪个实体是聚合根?实体有哪些属性和方法?哪些对象应该设计为值对象等。
**服务的识别和设计**
事件风暴的命令是外部的一些操作和业务行为,也是微服务对外提供的能力。它往往与微服务的应用服务或者领域服务对应。我们可以将命令作为服务识别和设计的起点。具体步骤如下:
- 根据命令设计应用服务,确定应用服务的功能,服务集合,组合和编排方式。服务集合中的服务包括领域服务或其它微服务的应用服务。
- 根据应用服务功能要求设计领域服务,定义领域服务。这里需要注意:应用服务可能是由多个聚合的领域服务组合而成的。
- 根据领域服务的功能,确定领域服务内的实体以及功能。
- 设计实体基本属性和方法。
另外,我们还要考虑领域事件的异步化处理。
我以提交审批这个动作为例,来说明服务的识别和设计。提交审批的大体流程是:
- 根据请假类型和时长,查询请假审批规则,获取下一步审批人的角色。
- 根据审批角色从人员组织关系中查询下一审批人。
- 为请假单分配审批人,并将审批规则保存至请假单。
- 通过分析,我们需要在应用层和领域层设计以下服务和方法。
**应用层:**提交审批应用服务。
**领域层:**领域服务有查询审批规则、修改请假流程信息服务以及根据审批规则查询审批人服务,分别位于请假和人员组织关系聚合。请假单实体有修改请假流程信息方法,审批规则值对象有查询审批规则方法。人员实体有根据审批规则查询审批人方法。下图是我们分析出来的服务以及它们之间的依赖关系。
<img src="https://static001.geekbang.org/resource/image/ec/f7/eca31d653a3171a8272c6b1f25140bf7.png" alt="">
服务的识别和设计过程就是这样了,我们再来设计一下聚合内的对象。
**聚合中的对象**
在请假单聚合中,聚合根是请假单。
请假单经多级审核后,会产生多条审批意见,为了方便查询,我们可以将审批意见设计为实体。请假审批通过后,会产生请假审批通过的领域事件,因此还会有请假事件实体。请假聚合有以下实体:审批意见(记录审批人、审批状态和审批意见)和请假事件实体。
我们再来分析一下请假单聚合的值对象。请假人和下一审批人数据来源于人员组织关系聚合中的人员实体,可设计为值对象。人员类型、请假类型和审批状态是枚举值类型,可设计为值对象。确定请假审批规则后,审批规则也可作为请假单的值对象。请假单聚合将包含以下值对象:请假人、人员类型、请假类型、下一审批人、审批状态和审批规则。
综上,我们就可以画出请假聚合对象关系图了。
<img src="https://static001.geekbang.org/resource/image/af/be/af21beade34a5f121f673c25a7c979be.jpg" alt="">
在人员组织关系聚合中,我们可以建立人员之间的组织关系,通过组织关系类型找到上级审批领导。它的聚合根是人员,实体有组织关系(包括组织关系类型和上级审批领导),其中组织关系类型(如项目经理、处长、总经理等)是值对象。上级审批领导来源于人员聚合根,可设计为值对象。人员组织关系聚合将包含以下值对象:组织关系类型、上级审批领导。
综上,我们又可以画出人员组织关系聚合对象关系图了。
<img src="https://static001.geekbang.org/resource/image/b5/76/b56e20cd47d161eccbd86d014f9c6e76.jpg" alt="">
**微服务内的对象清单**
在确定各领域对象的属性后,我们就可以设计各领域对象在代码模型中的代码对象(包括代码对象的包名、类名和方法名),建立领域对象与代码对象的一一映射关系了。根据这种映射关系,相关人员可快速定位到业务逻辑所在的代码位置。在经过以上分析后,我们在微服务内就可以分析出如下图的对象清单。
<img src="https://static001.geekbang.org/resource/image/89/bc/89a65daccac054e97591c7259a60e4bc.jpg" alt="">
### 2. 设计微服务代码结构
根据DDD的代码模型和各领域对象所在的包、类和方法我们可以定义出请假微服务的代码结构设计代码对象。
**应用层代码结构**
应用层包括应用服务、DTO以及事件发布相关代码。在LeaveApplicationService类内实现与聚合相关的应用服务在LoginApplicationService封装外部微服务认证和权限的应用服务。
这里提醒一下:如果应用服务逻辑复杂的话,一个应用服务就可以构建一个类,这样可以避免一个类的代码过于庞大,不利于维护。
<img src="https://static001.geekbang.org/resource/image/ed/03/ed9da0faabb9c756a0067dbd3f75d103.png" alt="">
**领域层代码结构**
领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码。一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调。
请假微服务领域层包含请假和人员两个聚合。人员和请假代码都放在各自的聚合所在目录结构的代码包中。如果随着业务发展,人员相关功能需要从请假微服务中拆分出来,我们只需将人员聚合代码包稍加改造,独立部署,即可快速发布为人员微服务。到这里,微服务内的领域对象,分层以及依赖关系就梳理清晰了。微服务的总体架构和代码模型也基本搭建完成了。
<img src="https://static001.geekbang.org/resource/image/a7/66/a7fa9314002372f6ddad1c1b54573a66.png" alt="">
## 后续的工作
### 1. 详细设计
在完成领域模型和微服务设计后,我们还需要对微服务进行详细的设计。主要设计以下内容:实体属性、数据库表和字段、实体与数据库表映射、服务参数规约及功能实现等。
### 2. 代码开发和测试
开发人员只需要按照详细的设计文档和功能要求,找到业务功能对应的代码位置,完成代码开发就可以了。代码开发完成后,开发人员要编写单元测试用例,基于挡板模拟依赖对象完成服务测试。
## 总结
今天我们通过在线请假考勤项目把DDD设计过程完整地走了一遍。
DDD战略设计从事件风暴开始然后我们要找出实体等领域对象找出聚合根构建聚合划分限界上下文建立领域模型。
战术设计从事件风暴的命令开始,识别和设计服务,建立各层服务的依赖关系,设计微服务内的实体和值对象,找出微服务中所有的领域对象,并建立领域对象与代码对象的映射关系。
这样就可以很好地指导项目团队进行微服务开发和测试了。总结完毕到这你是否已经清楚DDD全部的设计过程了呢有疑问欢迎留言讨论。
## 思考题
你现在采用的是什么样的微服务设计方法?你认为有什么需要特别注意的事项呢?目前有何难点痛点?分享出来,也许我能给你一些有效的建议。
最后,如果今天的实战项目,可以让你举一反三、有所收获,欢迎分享给你的朋友,邀请他加入学习。

View File

@@ -0,0 +1,147 @@
<audio id="audio" title="19 | 总结(一):微服务设计和拆分要坚持哪些原则?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/0a/d023d2a2d3ececee48d8cfeea572200a.mp3"></audio>
你好,我是欧创新。
我们前面已经讲了很多DDD的设计方法和实践案例。虽然DDD的设计思想和方法很好但由于企业发展历程以及企业技术和文化的不同DDD和微服务的实施策略也会有差异。那么面对这种差异我们应该如何落地DDD和微服务呢今天我们就来聊聊微服务的设计原则和演进策略。
## 微服务的演进策略
在从单体向微服务演进时,演进策略大体分为两种:绞杀者策略和修缮者策略。
### 1. 绞杀者策略
绞杀者策略是一种逐步剥离业务能力,用微服务逐步替代原有单体系统的策略。它对单体系统进行领域建模,根据领域边界,在单体系统之外,将新功能和部分业务能力独立出来,建设独立的微服务。新微服务与单体系统保持松耦合关系。
随着时间的推移,大部分单体系统的功能将被独立为微服务,这样就慢慢绞杀掉了原来的单体系统。绞杀者策略类似建筑拆迁,完成部分新建筑物后,然后拆除部分旧建筑物。
### 2. 修缮者策略
修缮者策略是一种维持原有系统整体能力不变,逐步优化系统整体能力的策略。它是在现有系统的基础上,剥离影响整体业务的部分功能,独立为微服务,比如高性能要求的功能,代码质量不高或者版本发布频率不一致的功能等。
通过这些功能的剥离,我们就可以兼顾整体和局部,解决系统整体不协调的问题。修缮者策略类似古建筑修复,将存在问题的部分功能重建或者修复后,重新加入到原有的建筑中,保持建筑原貌和功能不变。一般人从外表感觉不到这个变化,但是建筑物质量却得到了很大的提升。
其实还有第三种策略,就是另起炉灶,顾名思义就是将原有的系统推倒重做。建设期间,原有单体系统照常运行,一般会停止开发新需求。而新系统则会组织新的项目团队,按照原有系统的功能域,重新做领域建模,开发新的微服务。在完成数据迁移后,进行新旧系统切换。
对于大型核心系统我一般不建议采用这种策略,这是因为系统重构后的不稳定性、大量未知的潜在技术风险和新的开发模式下项目团队磨合等不确定性因素,会导致项目实施难度大大增加。
## 不同场景下的领域建模策略
由于企业内情况千差万别,发展历程也不一样,有遗留单体系统的微服务改造,也有全新未知领域的业务建模和系统设计,还有遗留系统局部优化的情况。不同场景下,领域建模的策略也会有差异。下面我们就分几类场景来看看如何进行领域建模。
### 1. 新建系统
新建系统又分为简单和复杂领域建模两种场景。
**简单领域建模**
简单的业务领域,一个领域就是一个小的子域。在这个小的问题域内,领域建模过程相对简单,直接采用事件风暴的方法构建领域模型就可以了。
**复杂领域建模**
对于复杂的业务领域,领域可能需要多级拆分后才能开始领域建模。领域拆分为子域,甚至子域还需要进一步拆分。比如:保险它需要拆分为承保、理赔、收付费和再保等子域,承保子域再拆分为投保、保单管理等子子域。复杂领域如果不做进一步细分,由于问题域太大,领域建模的工程量会非常浩大。你不太容易通过事件风暴,完成一个很大的领域建模,即使勉强完成,效果也不一定好。
**对于复杂领域,我们可以分三步来完成领域建模和微服务设计。**
第一步,拆分子域建立领域模型
根据业务领域的特点,参考流程节点边界或功能聚合模块等边界因素。结合领域专家和项目团队的讨论,将领域逐级分解为大小合适的子域,针对子域采用事件风暴,划分聚合和限界上下文,初步确定子域内的领域模型。
第二步,领域模型微调
梳理领域内所有子域的领域模型,对各子域领域模型进行微调。微调的过程重点考虑不同领域模型中聚合的重组。同步考虑领域模型和聚合的边界,服务以及事件之间的依赖关系,确定最终的领域模型。
第三步,微服务的设计和拆分
根据领域模型和微服务拆分原则,完成微服务的拆分和设计。
### 2. 单体遗留系统
如果我们面对的是一个单体遗留系统,只需要将部分功能独立为微服务,而其余仍为单体,整体保持不变,比如将面临性能瓶颈的模块拆分为微服务。我们只需要将这一特定功能,理解为一个简单子领域,参考简单领域建模的方式就可以了。在微服务设计中,我们还要考虑新老系统之间服务和业务的兼容,必要时可引入防腐层。
## DDD使用的误区
很多人在接触微服务后但凡是系统一概都想设计成微服务架构。其实有些业务场景单体架构的开发成本会更低开发效率更高采用单体架构也不失为好的选择。同样虽然DDD很好但有些传统设计方法在微服务设计时依然有它的用武之地。下面我们就来聊聊DDD使用的几个误区。
### 1. 所有的领域都用DDD
很多人在学会DDD后可能会将其用在所有业务域即全部使用DDD来设计。DDD从战略设计到战术设计是一个相对复杂的过程首先企业内要培养DDD的文化其次对团队成员的设计和技术能力要求相对比较高。在资源有限的情况下应聚焦核心域建议你先从富领域模型的核心域开始而不必一下就在全业务域推开。
### 2. 全部采用DDD战术设计方法
不同的设计方法有它的适用环境我们应选择它最擅长的场景。DDD有很多的概念和战术设计方法比如聚合根和值对象等。聚合根利用仓储管理聚合内实体数据之间的一致性这种方法对于管理新建和修改数据非常有效比如在修改订单数据时它可以保证订单总金额与所有商品明细金额的一致但它并不擅长较大数据量的查询处理甚至有延迟加载进而影响效率的问题。
而传统的设计方法可能一条简单的SQL语句就可以很快地解决问题。而很多贫领域模型的业务比如数据统计和分析DDD很多方法可能都用不上或用得并不顺手而传统的方法很容易就解决了。
因此在遵守领域边界和微服务分层等大原则下在进行战术层面设计时我们应该选择最适合的方法不只是DDD设计方法当然还应该包括传统的设计方法。这里要以快速、高效解决实际问题为最佳不要为做DDD而做DDD。
### 3. 重战术设计而轻战略设计
很多DDD初学者学习DDD的主要目的可能是为了开发微服务因此更看重DDD的战术设计实现。殊不知DDD是一种从领域建模到微服务落地的全方位的解决方案。
战略设计时构建的领域模型是微服务设计和开发的输入它确定了微服务的边界、聚合、代码对象以及服务等关键领域对象。领域模型边界划分得清不清晰领域对象定义得明不明确会决定微服务的设计和开发质量。没有领域模型的输入基于DDD的微服务的设计和开发将无从谈起。因此我们不仅要重视战术设计更要重视战略设计。
### 4. DDD只适用于微服务
DDD是在微服务出现后才真正火爆起来的很多人会认为DDD只适用于微服务。在DDD沉默的二十多年里其实它一直也被应用在单体应用的设计中。
具体项目实施时要吸取DDD的核心设计思想和理念结合具体的业务场景和团队技术特点多种方法组合灵活运用用正确的方式解决实际问题。
## 微服务设计原则
微服务设计原则中,如高内聚低耦合、复用、单一职责等这些常见的设计原则在此就不赘述了,我主要强调下面这几条:
**第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。**
微服务设计首先应建立领域模型,确定逻辑和物理边界以及领域对象后,然后才开始微服务的拆分和设计。而不是先定义数据模型和库表结构,也不是前端界面需要什么,就去调整核心领域逻辑代码。在设计时应该将外部需求从外到内逐级消化,尽量降低对核心领域层逻辑的影响。
**第二条:要边界清晰的微服务,而不是泥球小单体。**
微服务上线后其功能和代码也不是一成不变的。随着需求或设计变化,领域模型会迭代,微服务的代码也会分分合合。边界清晰的微服务,可快速实现微服务代码的重组。微服务内聚合之间的领域服务和数据库实体原则上应杜绝相互依赖。你可通过应用服务编排或者事件驱动,实现聚合之间的解耦,以便微服务的架构演进。
**第三条:要职能清晰的分层,而不是什么都放的大箩筐。**
分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,也就是说只能从外层调用内层服务,内层通过封装、组合或编排对外逐层暴露,服务粒度也由细到粗。应用层负责服务的组合和编排,不应有太多的核心业务逻辑,领域层负责核心领域业务逻辑的实现。各层应各司其职,职责边界不要混乱。在服务演进时,应尽量将可复用的能力向下层沉淀。
**第四条要做自己能hold住的微服务而不是过度拆分的微服务。**
微服务过度拆分必然会带来软件维护成本的上升比如集成成本、运维成本、监控和定位问题的成本。企业在微服务转型过程中还需要有云计算、DevOps、自动化监控等能力而一般企业很难在短时间内提升这些能力如果项目团队没有这些能力将很难hold住这些微服务。
如果在微服务设计之初按照DDD的战略设计方法定义好了微服务内的逻辑边界做好了架构的分层其实我们不必拆分太多的微服务即使是单体也未尝不可。随着技术积累和能力提升当我们有了这些能力后由于应用内有清晰的逻辑边界我们可以随时轻松地重组出新的微服务而这个过程不会花费太多的时间和精力。
## 微服务拆分需要考虑哪些因素?
理论上一个限界上下文内的领域模型可以被设计为微服务,但是由于领域建模主要从业务视角出发,没有考虑非业务因素,比如需求变更频率、高性能、安全、团队以及技术异构等因素,而这些非业务因素对于领域模型的系统落地也会起到决定性作用,因此在微服务拆分时我们需要重点考虑它们。我列出了以下主要因素供你参考。
**1. 基于领域模型**
基于领域模型进行拆分,围绕业务领域按职责单一性、功能完整性拆分。
**2. 基于业务需求变化频率**
识别领域模型中的业务需求变动频繁的功能,考虑业务变更频率与相关度,将业务需求变动较高和功能相对稳定的业务进行分离。这是因为需求的经常性变动必然会导致代码的频繁修改和版本发布,这种分离可以有效降低频繁变动的敏态业务对稳态业务的影响。
**3. 基于应用性能**
识别领域模型中性能压力较大的功能。因为性能要求高的功能可能会拖累其它功能,在资源要求上也会有区别,为了避免对整体性能和资源的影响,我们可以把在性能方面有较高要求的功能拆分出去。
**4. 基于组织架构和团队规模**
除非有意识地优化组织架构否则微服务的拆分应尽量避免带来团队和组织架构的调整避免由于功能的重新划分而增加大量且不必要的团队之间的沟通成本。拆分后的微服务项目团队规模保持在1012人左右为宜。
**5. 基于安全边界**
有特殊安全要求的功能,应从领域模型中拆分独立,避免相互影响。
**6. 基于技术异构等因素**
领域模型中有些功能虽然在同一个业务域内,但在技术实现时可能会存在较大的差异,也就是说领域模型内部不同的功能存在技术异构的问题。由于业务场景或者技术条件的限制,有的可能用.NET有的则是Java有的甚至大数据架构。对于这些存在技术异构的功能可以考虑按照技术边界进行拆分。
## 总结
相信你在微服务落地的时候会有很多的收获和感悟。对于DDD和微服务我想总结的就是深刻理解DDD的设计思想和内涵把握好边界和分层这个大原则结合企业文化和技术特点灵活运用战术设计方法选择最适合的技术和方法解决实际问题切勿为了DDD而做DDD
## 思考题
谈谈你在用DDD实践过程中踩过什么样的坑有什么好的建议可以分享给大家期待你的留言

View File

@@ -0,0 +1,135 @@
<audio id="audio" title="20 | 总结分布式架构关键设计10问" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/f6/2dd7bc98801b5b46a97dbe714a89fff6.mp3"></audio>
你好,我是欧创新。
前面我们重点讲述了领域建模、微服务设计和前端设计方法,它们组合在一起就可以形成中台建设的整体解决方案。而中台大多基于分布式微服务架构,这种企业级的数字化转型有很多地方值得我们关注和思考。
我们不仅要关注企业商业模式、业务边界以及前中台的融合,还要关注数据技术体系、微服务设计、多活等多领域的设计和协同。结合实施经验和思考,今天我们就来聊聊分布式架构下的几个关键问题。
## 一、选择什么样的分布式数据库?
分布式架构下的数据应用场景远比集中式架构复杂,会产生很多数据相关的问题。谈到数据,首先就是要选择合适的分布式数据库。
分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。目前主要有三种不同的分布式数据库解决方案。它们的主要差异是数据多副本的处理方式和数据库中间件。
### 1. 一体化分布式数据库方案
它支持数据多副本、高可用。多采用Paxos协议一次写入多数据副本多数副本写入成功即算成功。代表产品是OceanBase和高斯数据库。
### 2. 集中式数据库+数据库中间件方案
它是集中式数据库与数据库中间件结合的方案通过数据库中间件实现数据路由和全局数据管理。数据库中间件和数据库独立部署采用数据库自身的同步机制实现主副本数据的一致性。集中式数据库主要有MySQL和PostgreSQL数据库基于这两种数据库衍生出了很多的解决方案比如开源数据库中间件MyCat+MySQL方案TBase基于PostgreSQL但做了比较大的封装和改动等方案。
### 3. 集中式数据库+分库类库方案
它是一种轻量级的数据库中间件方案分库类库实际上是一个基础JAR包与应用软件部署在一起实现数据路由和数据归集。它适合比较简单的读写交易场景在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有ShardingSphere。
**小结:**这三种方案实施成本不一样,业务支持能力差异也比较大。一体化分布式数据库主要由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。集中式数据库+数据库中间件方案,实施成本和技术能力要求适中,可满足中大型企业业务要求。第三种分库类库的方案可处理简单的业务场景,成本和技能要求相对较低。在选择数据库的时候,我们要考虑自身能力、成本以及业务需要,从而选择合适的方案。
## 二、如何设计数据库分库主键?
选择了分布式数据库,第二步就要考虑数据分库,这时分库主键的设计就很关键了。
与客户接触的关键业务我建议你以客户ID作为分库主键。这样可以确保同一个客户的数据分布在同一个数据单元内避免出现跨数据单元的频繁数据访问。跨数据中心的频繁服务调用或跨数据单元的查询会对系统性能造成致命的影响。
将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务。而对企业来说,“以客户为中心”的业务能力,首先就要做到数据上的“以客户为中心”。
当然,你也可以根据业务需要用其它的业务属性作为分库主键,比如机构、用户等。
## 三、数据库的数据同步和复制
在微服务架构中,数据被进一步分割。为了实现数据的整合,数据库之间批量数据同步与复制是必不可少的。数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制、以及不同主题数据的整合等。
传统的数据传输方式有ETL工具和定时提数程序但数据在时效性方面存在短板。分布式架构一般采用基于数据库逻辑日志增量数据捕获CDC技术它可以实现准实时的数据复制和传输实现数据处理与应用逻辑解耦使用起来更加简单便捷。
现在主流的PostgreSQL和MySQL数据库外围有很多数据库日志捕获技术组件。CDC也可以用在领域事件驱动设计中作为领域事件增量数据的获取技术。
## 四、跨库关联查询如何处理?
跨库关联查询是分布式数据库的一个短板,会影响查询性能。在领域建模时,很多实体会分散到不同的微服务中,但很多时候会因为业务需求,它们之间需要关联查询。
关联查询的业务场景包括两类:第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,这种查询会跨多个业务线的微服务;第二类是表与表之间的关联查询,比如机构表与业务表的联表查询,但机构表和业务表分散在不同的微服务。
**如何解决这两类关联查询呢?**
对于第一类场景,由于数据分散在不同微服务里,我们无法跨多个微服务来统计这些数据。你可以建立面向主题的分布式数据库,它的数据来源于不同业务的微服务。采用数据库日志捕获技术,从各业务端微服务将数据准实时汇集到主题数据库。在数据汇集时,提前做好数据关联(如将多表数据合并为一个宽表)或者建立数据模型。面向主题数据库建设查询微服务。这样一次查询你就可以获取客户所有维度的业务数据了。你还可以根据主题或场景设计合适的分库主键,提高查询效率。
对于第二类场景,对于不在同一个数据库的表与表之间的关联查询场景,你可以采用小表广播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,你可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还可以提高数据的查询效率。
## 五、如何处理高频热点数据?
对于高频热点数据,比如商品、机构等代码类数据,它们同时面向多个应用,要有很高的并发响应能力。它们会给数据库带来巨大的访问压力,影响系统的性能。
常见的做法是将这些高频热点数据从数据库加载到如Redis等缓存中通过缓存提供数据访问服务。这样既可以降低数据库的压力还可以提高数据的访问性能。
另外对需要模糊查询的高频数据你也可以选用ElasticSearch等搜索引擎。
缓存就像调味料一样,投入小、见效快,用户体验提升快。
## 六、前后序业务数据的处理
在微服务设计时你会经常发现,某些数据需要关联前序微服务的数据。比如:在保险业务中,投保微服务生成投保单后,保单会关联前序投保单数据等。在电商业务中,货物运输单会关联前序订单数据。由于关联的数据分散在业务的前序微服务中,你无法通过不同微服务的数据库来给它们建立数据关联。
**如何解决这种前后序的实体关联呢?**
一般来说,前后序的数据都跟领域事件有关。你可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。
你可以将前序数据设计为实体或者值对象,并被当前实体引用。在设计时你需要关注以下内容:如果前序数据在当前微服务只可整体修改,并且不会对它做查询和统计分析,你可以将它设计为值对象;当前序数据是多条,并且需要做查询和统计分析,你可以将它设计为实体。
这样,你可以在货物运输微服务,一次获取前序订单的清单数据和货物运输单数据,将所有数据一次反馈给前端应用,降低跨微服务的调用。如果前序数据被设计为实体,你还可以将前序数据作为查询条件,在本地微服务完成多维度的综合数据查询。只有必要时才从前序微服务,获取前序实体的明细数据。这样,既可以保证数据的完整性,还可以降低微服务的依赖,减少跨微服务调用,提升系统性能。
## 七、数据中台与企业级数据集成
分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分而形成很多数据孤岛,增加数据集成和企业级数据使用的难度。你可以通过数据中台来实现数据融合,解决分布式架构下的数据应用和集成问题。
**你可以分三步来建设数据中台。**
第一,按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。
第二,建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道视图等。
第三,建立业务需求驱动的数据体系,支持业务和商业模式创新。
数据中台不仅限于分析场景,也适用于交易型场景。你可以建立在数据仓库和数据平台上,将数据平台化之后提供给前台业务使用,为交易场景提供支持。
## 八、BFF与企业级业务编排和协同
企业级业务流程往往是多个微服务一起协作完成的,每个单一职责的微服务就像积木块,它们只完成自己特定的功能。那如何组织这些微服务,完成企业级业务编排和协同呢?
你可以在微服务和前端应用之间增加一层BFF微服务Backend for Frontends。**BFF主要职责是处理微服务之间的服务组合和编排**,微服务内的应用服务也是处理服务的组合和编排,那这二者有什么差异呢?
BFF位于中台微服务之上主要职责是微服务之间的服务协调**应用服务主要处理微服务内的服务组合和编排。**在设计时我们应尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用。
BFF像齿轮一样来适配前端应用与微服务之间的步调。它通过Façade服务适配不同的前端通过服务组合和编排组织和协调微服务。BFF微服务可根据需求和流程变化与前端应用版本协同发布避免中台微服务为适配前端需求的变化而频繁地修改和发布版本从而保证微服务核心领域逻辑的稳定。
如果你的BFF做得足够强大它就是一个集成了不同中台微服务能力、面向多渠道应用的业务能力平台。
## 九、分布式事务还是事件驱动机制?
分布式架构下,原来单体的内部调用,会变成分布式调用。如果一个操作涉及多个微服务的数据修改,就会产生数据一致性的问题。数据一致性有强一致性和最终一致性两种,它们实现方案不一样,实施代价也不一样。
对于实时性要求高的强一致性业务场景,你可以采用分布式事务,但分布式事务有性能代价,在设计时我们需平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。
领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时场景的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好地解耦微服务。通过削峰填谷,可以减轻数据库实时访问压力,提高业务吞吐量和处理能力。你还可以通过事件驱动实现读写分离,提高数据库访问性能。对最终一致性的场景,我建议你采用领域事件驱动的设计方法。
## 十、多中心多活的设计
分布式架构的高可用主要通过多活设计来实现,多中心多活是一个非常复杂的工程,下面我主要列出以下几个关键的设计。
1.选择合适的分布式数据库。数据库应该支持多数据中心部署,满足数据多副本以及数据底层复制和同步技术要求,以及数据恢复的时效性要求。
2.单元化架构设计。将若干个应用组成的业务单元作为部署的基本单位,实现同城和异地多活部署,以及跨中心弹性扩容。各单元业务功能自包含,所有业务流程都可在本单元完成;任意单元的数据在多个数据中心有副本,不会因故障而造成数据丢失;任何单元故障不影响其它同类单元的正常运行。单元化设计时我们要尽量避免跨数据中心和单元的调用。
3.访问路由。访问路由包括接入层、应用层和数据层的路由,确保前端访问能够按照路由准确到达数据中心和业务单元,准确写入或获取业务数据所在的数据库。
4.全局配置数据管理。实现各数据中心全局配置数据的统一管理,每个数据中心全局配置数据实时同步,保证数据的一致性。
## 总结
企业级分布式架构的实施是一个非常复杂的系统工程涉及到非常多的技术体系和方法。今天我罗列了10个关键的设计领域每个领域其实都非常复杂需要很多的投入和研究。在实施的时候你和你的公司要结合自身情况来选择合适的技术组件和实施方案。
## 思考题
在分布式架构设计时,你遇到过哪些问题?有什么样的解决方案?一起来分享一下。