07 在线订餐场景中是如何开事件风暴会议的h3>
微服务设计最核心的难题是微服务的拆分,不合理的微服务拆分不仅不能提高研发效率,反倒还使得研发效率更低,因此要讲究“小而专”的设计。“小而专”的设计意味着微服务的设计不是简单拆分,而是对设计提出了更高的要求,要“低耦合、高内聚”。那么,如何做到“低耦合、高内聚”,实现微服务的“小而专”呢需要“领域驱动设计”作为方法论,来指导我们的开发。
用“领域驱动设计”是业界普遍认可的解决方案,也就是解决微服务如何拆分,以及实现微服务的高内聚与单一职责的问题。但是,领域驱动设计应当怎样进行呢从需求分析到软件设计,用正确的方式一步一步设计微服务呢我们用一个在线订餐系统实战演练一下微服务的设计过程。
在线订餐系统项目实战
相信我们都使用过在线订餐系统,比如美团、大众点评、百度外卖等,具体的业务流程如下图所示:
研发不懂客户、客户也不懂研发
在这个过程中,对于客户来说:
-
客户十分清楚他的业务领域知识,以及他亟待解决的业务痛点;
-
然而,客户不清楚技术能如何解决他的业务痛点。
因此,用户在提需求时,是在用他有限的认知,想象技术如何解决他的业务痛点。所以这样提出的业务需求往往不太靠谱,要么技术难于实现,要么并非最优的方案。
与此同时,在需求分析过程中,对于研发人员来说:
-
非常清楚技术以及能解决哪些业务问题,同时也清楚它是如何解决的;
-
然而,欠缺的是对客户所在的业务领域知识的掌握,使得无法准确理解客户的业务痛点。
这就局限了我们的设计,进而所做的系统不能完美地解决用户痛点。
因此,在需求分析的过程中,不论是客户还是我们,都不能掌握准确理解需求所需的所有知识,这就导致,不论是谁都不能准确地理解与描述软件需求。在需求分析中常常会出现,客户以为他描述清楚需求了,我们也以为我们听清楚了。但当软件开发出来以后,客户才发现这并不是他需要的软件,而我们也发现我们并没有真正理解需求。尽管如此,客户依然没有想清楚他想要什么,而我们还是不知道该怎样做,这就是软件开发之殇。
事件风暴会议图
当开始事件风暴会议以后,通常分为这样几个步骤。
首先,在产品经理的引导下,与业务专家开始梳理当前的业务中有哪些领域事件,即已经发生并需要保存下来的那些事实。这时,是按照业务流程依次去梳理领域事件的。例如,在本案例中,整个在线订餐过程分为:已下单、已接单、已就绪、已派送和已送达,这几个领域事件。注意,领域事件是已发生的事实,因此,在命名的时候应当采用过去时态。
这里有一个十分有趣的问题值得探讨。在用户下单之前,用户首先是选餐。那么,“用户选餐”是不是领域事件呢,领域事件是那些已经发生并且需要保存的重要事实。这里,“用户选餐”仅仅是一个查询操作,并不需要数据库保存,因此不能算领域事件。那么,难道这些查询功能不在需求分析的过程中吗p>
注意,DDD 有自己的适用范围,它往往应用于系统增删改的业务场景中,而查询场景的分析往往不用 DDD,而是通过其他方式进行分析。分析清楚了领域事件以后,就用橘黄色便笺纸,将所有的领域事件罗列在白板上,确保领域中所有事件都已经被覆盖。
紧接着,针对每一个领域事件,项目组成员开始不断地围绕着它进行业务分析,增加各种命令与事件,进而思考与之相关的资源、外部系统与时间。例如,在本案例中,首先分析“已下单”事件,分析它触发的命令、与之相关的人与事儿,以及发生的时间。命令使用蓝色便笺,人和事儿使用黄色便笺,如下图所示:
“已下单”的限界上下文分析图
通过这样的设计,就能将“用户下单”限界上下文的范围,与之相关的上下文地图以及如何接口,分析清楚了。有了这些设计,就可以按照限界上下文进行微服务拆分。按照这样的设计拆分的微服务,所有与用户下单相关的需求变更都在“用户下单”微服务中实现。但是,订单在读取用户信息的时候,不是直接去 join 用户信息表,而是调用“用户注册”微服务的接口。这样,当用户信息发生变更时,与“用户下单”微服务无关,只需要在“用户注册”微服务中独立开发、独立升级,从而使系统维护的成本得到降低。
在线订餐系统的微服务设计
这里可以看到,前端微服务与后端微服务的设计是不一致的。前面讲的都是后端微服务的设计,而前端微服务的设计与用户 UI 是密切关联的,因此通过不同角色的规划,将前端微服务划分为用户 App、饭店 Web 与骑士 App。在用户 App 中,所有面对用户的诸如“用户注册”“用户下单”“用户选购”等功能都设计在用户 App 中。它相当于一个聚合服务,用于接收用户请求:
-
“用户注册”时,调用“用户注册”微服务;
-
“用户选购”时,查询“饭店管理”微服务;
-
“用户下单”时,调用“用户下单”微服务。
总结
采用 DDD 进行需求的分析建模,可以帮助微服务的设计质量提高,实现“低耦合、高内聚”,进而充分发挥微服务的优势。然而,在微服务的设计实现还要解决诸多的难题。本讲一一拆解了微服务设计实现的这些难题,及其解决思路。然而,要更加完美地解决以上问题,不是让每个微服务都去见招拆招,而是应当有一个微服务的技术中台统一去解决。这些方面的设计将在后面微服务技术中台建设的相关章节进行讲解。
下一讲我们将演练在以上领域模型与微服务设计的基础上,如何落实每一个微服务的设计,以及可能面临的设计难题。
09 DDD 是如何落地微服务设计实现的h3>
自本专栏上线以来,有许多小伙伴跟我交流了很多相关的 DDD 知识。我发现,当大家看到贫血模型、充血模型、策略模式、装饰者模式时,发出这样的感慨:“难道这就是 DDD 吗们平时的开发没有什么不同啊。”殊不知,其实你还没有 Get 到 DDD 的真谛 。
DDD 的真谛
什么是 DDD 的真谛呢是领域建模,它改变了我们过去对软件开发的认知。如图 1 所示,DDD 的精髓是:
-
首先深刻理解业务;
-
然后将我们对业务的理解绘制成领域模型;
-
再通过领域模型指导数据库和程序的设计。
在这样的基础上开始划分限界上下文,用户与用户地址属于“用户注册”上下文,饭店与菜单属于“饭店管理”上下文。它们对于“用户下单”上下文来说都是支撑域,即给“用户下单”上下文提供接口调用的。真正属于“用户下单”上下文的,就只有订单、菜品明细、支付、发票这几个类,它们最终形成了“用户下单”微服务及其数据库设计。由于用户姓名、地址、电话等信息,都在“用户注册”上下文中,每次都需要远程接口调用来获得。这时就需要从系统优化的角度,适当将它们冗余到“订单”领域对象中,以提升查询效率。同样,“菜品名称”也进行了冗余,设计更新如图 3 所示:
同样的思路,通过领域事件通知“骑士派送”上下文,完成“骑士派送”的领域建模。
通过以上设计,就将上一讲的微服务拆分,进一步落实到每一个微服务的设计。紧接着,将每一个微服务的设计,按照第 03 讲的思路落实数据库设计,按照第 04 讲的思路落实贫血模型与充血模型的设计。
特别值得注意的是,订单与菜品明细是一对聚合。过去按照贫血模型的设计,分别为它们设计订单值对象、Service 与 Dao,菜品明细值对象、Service 与 Dao;现在按照充血模型的设计,只有订单领域对象、Service、仓库、工厂与菜品明细包含在订单对象中,而订单 Dao 被包含在订单仓库中。贫血模型与充血模型在设计上有明显的差别。关于聚合的实现,下一讲再详细探讨。
深入理解业务与模型重构
前面讲了,我们不可能一步到位深刻理解业务,它是一个逐步深入的过程。譬如,在设计“用户地址”时,起初没有“联系人”与“手机 ”,因为通过关联用户就可以获得。然而,随着业务的不断深入,我们发现,当用户下单的时候,最终派送的不一定是给他本人,可能是另一个人,这是起初没有想到的真实业务场景。为此,在“用户地址”中果断增加了“联系人”与“手机 ”,问题得到解决。
此外,如果用户下单以后又需要取消订单,这样的业务场景又该如何设计呢与客户的沟通,确定了该业务的需求:
-
如果饭店还未接单,可以直接取消;
-
如果饭店已经接单了,需要经过饭店的确认方可取消;
-
如果饭店已经就绪了,就不可取消了。
这样,首先需要“饭店接单”上下文提供一个状态查询的接口,以及饭店确认取消的接口。接着,订单取消以后需要记录一个取消时间,并形成一个“订单取消”领域事件,通知“饭店接单”上下文。为此,“用户下单”上下文需要在订单中增加一个“取消时间”。
然而,当“用户下单”上下文对“订单”对象更新以后,“饭店接单”与“骑士派送”上下文是否也要跟着更新呢提到,对微服务的设计,是希望:
-
每次变更的时候尽可能只更新一个微服务,以降低微服务的维护成本;
-
即使不能,也应当尽可能缩小更新的范围。
增加“取消时间”这个字段,对“饭店接单”上下文是有意义的,它的相应变更无可厚非。但对于“骑士派送”上下文来说,“取消时间”对它没有一毛钱关系,因此不希望对它进行更新。微服务间的调用是基于 RESTful 的接口调用,参数是通过 JSON 对象传递,是一种松耦合调用。因此,在“饭店接单”与“骑士派送”上下文中,即使“订单”对象的数据结构不一致,也不影响它们的调用。因此,在“骑士派送”上下文不需要更新,更新范围就缩小了,维护成本降低了。
在完成了以上设计以后,还有一个难题就是订单状态的跟踪。
订单状态的跟踪
当用户下单后,往往会不断地跟踪订单状态是“已下单”“已接单”“已就绪”还是“已派送”。然而,这些状态信息被分散到了各个微服务中,就不可能在“用户下单”上下文中实现了。如何从这些微服务中采集订单的状态信息,又可以保持微服务间的松耦合呢思路还是领域事件的通知。
通过消息队列,每个微服务在执行完某个领域事件的操作以后,就将领域事件封装成消息发送到消息队列中。比如,“用户下单”微服务在完成用户下单以后,将下单事件放到消息队列中。这样,不仅“饭店接单”微服务可以接收这个消息,完成后续的接单操作;而且“订单查询”微服务也可以接收这个消息,实现订单的跟踪。如图 5 所示。

图 5 订单状态的跟踪图
通过领域事件的通知与消息队列的设计,使微服务间调用的设计松耦合,“订单查询”微服务可以像外挂一样采集各种订单状态,同时不影响原有的微服务设计,使得微服务之间实现解耦,降低系统维护的成本。而“订单查询”微服务通过冗余,将“下单时间”“取消时间”“接单时间”“就绪时间”等订单在不同状态下的时间,以及其他相关信息,都保存到订单表中,甚至增加一个“订单状态”记录当前状态,并增加 Redis 缓存的功能。这样的设计就保障了订单跟踪查询的高效。要知道,面对大数据的高效查询,通常都是通过冗余来实现的。
总结
DDD 的真谛是领域建模,即深入理解业务。只有深入理解业务,将对业务的深入理解设计到领域模型中,设计出来的软件才更加专业,让用户的使用更满意。因此,基于每个限界上下文进行领域建模,不断地将每个功能加入模型中,落地每个微服务的设计。当业务越来越复杂,理解越来越深入的时候,适时地调整原有的模型,就能适应新的功能,使设计始终高质量。
下一讲将现有的微服务设计进一步落实技术实现上,做“去中心化的数据管理”,解决跨库查询等技术难题。
文章知识点与官方知识档案匹配,可进一步学习相关知识云原生入门技能树首页概览8832 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!