领域驱动设计和微服务「业务建模领域建模」
今天给大家普及一下领域驱动设计和微服务「业务建模领域建模」相关知识,最近很多在问领域驱动设计和微服务「业务建模领域建模」,希望能帮助到您。
注意这篇文章为我去年12月写过的一篇基于领域模型来划分微服务的文章的进一步思考和总结,大家可以先看下这篇文章。
对领域模型和上下文边界分析来划分微服务的再思考
今天这篇文章主要还是进一步对上篇文章里面没有说清楚的地方进一步描述。
微服务划分注意微服务划分涉及到两个关键部分内容。
哪些业务功能应该聚合在一起。哪些数据应该聚合在一起,Owner是该微服务。为何我不断在强调这点。
因为在微服务之间的传统单体应用阶段,也有架构设计,也有组件划分。但是一般来说只是应用层业务功能如何聚合,而不会涉及到数据库本身也要拆分的问题。
而微服务下的划分必须是数据和功能全部拆分开。
微服务A只能最自己Owner的数据库进行类似JDBC等直接连接操作,而对于其它微服务的数据库只能够通过应用层暴露的API接口服务进行。
如上图中红叉的地方是不允许的。
传统单体应用架构设计,虽然也进行了组件划分,但是一般数据库没有拆分。组件虽然也可以单独找APP Server独立部署,但是底层数据库还是耦合在一起的。
因此单个组件并没有实现从数据库到应用层的完全解耦。
虽然组件设计开发规范要求组件A需要通过API访问组件B提供的能力,但是实际大部分时候组件A是直接在访问后台数据库表。
因为这样做开发实现起来最简单。
所有很多规范约束实际很难真正落地,组件化后仍然是紧耦合状态。
因此微服务划分同时需要考虑功能和数据两个层面的聚合和拆分,这个划分实际比传统架构设计中的组件划分更难,考虑的点也需要更多。
领域模型来划分微服务领域驱动设计概念来源于2004年著名建模专家Eric Evans,也出了一本经典的领域驱动设计的建模书籍。但是DDD本身多年前发展并不是很火。
如果回到10多年前,主流的架构设计思路偏RUP UML,而DDD建模思路实际更加复杂,能够应用好本身不容易。其次,很多IT项目本身也不需要用到这么复杂的建模方法。
而最近5年,随着微服务架构的流行,DDD领域建模再次受到重视。
一个核心应用点就在于微服务究竟应该如何划分?
领域驱动设计中的限界上下文,子域划分给了很好的思路和方法。比如常用的基于事件风暴来划分上下文。
识别事件和命令
事件你可以理解为一个事物所编写出来的最终状态,例如订单已创建,支付已完成,商品已发货等即是关键的事件。而命令可以理解为具体的业务功能或操作,比如创建订单,检索商品,扣减库存等。
可以看到事件和命令的识别和分析中,都可以识别到具体的领域对象。
基于领域的对象进行聚合
如何进行聚合?简单来说仍然是通过识别和梳理出来的共性领域对象进行聚合。将对同一领域对象的所有命令和事件都聚合在一起。
进行上下文边界的划分
基于聚合完成的情况进行上下文边界的划分,这里实际上不同的领域对象如果属于同一个大类的业务场景,仍然是可以划分到一个上下文里面的。
也就是不是简单地按聚合完成的领域对象划分上下文边界,很多和核心领域对象相关的附属对象也需要划分到同一个上下文的。
比如电商里面的订单是一个大量的领域对象,可以划分为独立的上下文,但是对应的购物车也是我们识别的对象,购物车本身同样属于产品订购场景,订单的扩展附属对象,因此需要将购物车也划分到订单上下文里面。
再次总结下整个方法思路如下。
即仍然是业务场景驱动分析加头脑风暴,识别出关键的业务事件(如果采用Scrum方法,这个也可以理解为独立的UserStory)。任何一个业务事件都包括了业务动作,业务对象和对象状态三要素。
注意,常用的事件风暴法里面更加强调先找到狭义的事件,即对象状态。比如已发货,已付款,已冻结等状态事件,然后再去分析状态事件对应的对象和业务操作。在你不熟悉某个领域的时候大可不必按部就班的这样做,直接通过业务场景和流程梳理出核心的细粒度业务活动点即可。业务活动上自然会附着有对象和状态。
将事件分解为这三要素后,再次按业务对象进行聚类。比如订单是一个业务对象,将所有和订单相关的业务事件全部聚合在一起。
在聚类完成后,每一个聚类就是一个独立的限界上下文。
聚合,限界上下文和子域如果你按上面步骤将每个聚合划分为一个个独立的微服务。
那么最终的微服务一定粒度太细。
因为每一个核心的业务对象都变成了一个微服务。
拿一个传统的供应链系统来举例,你会看到采购采购单,采购订单,物料,供应商,框架协议,入库单,出库单,库存信息,采购计划等是核心的功能聚合点。那么一个传统的供应链系统需要划分出好几十个微服务?
按事件风暴梳理事件并基于对象进行功能聚合本身没有错。
但是每个聚合本身不能完全1对1映射到微服务上面。
因此应该是将多个相关的聚合归并到一起,并以此来划分限界上下文。限界上下文可以只包含一个聚合,也可以包含多个聚合。
那么子域和限界上下文什么关系?
一般来讲子域和限界上下文之间是对应的关系,但是子域本身也可能是将多个限界上下文合并在一起,形成一个大的子域。
但是不论什么情况。都应该意识到不能按单独的一个个聚合来划分微服务。
真正的问题关键不是聚合,而是如何将多个聚合划分到一个限界上下文里面。
问题关键-如何将多个聚合划分到一个上下文或子域在我自己以前做企业IT架构规划的时候,在进行业务架构规划的时候就经常会遇到业务域划分的问题。
其中一个常用的方法就是参考业界常用的模型,比如供应链领域有标准的Scor模型可以参考。整个企业完整业务你也可以参考价值链模型。如果你是做电信行业项目,则有标准的eTom模型可以参考。而产品研发则完全可以参考IPD模型。
所以参考业界标准模型是常用的一个划分业务域的方法。
其次,IT人员做架构一定不能脱离企业真实的业务组织架构和业务运作。
还是拿采购类业务来说,一个小点的企业就一个部门供应链部,会将所有招投标,供应商管理,采购询价,采购,采购执行,库存管理等一系列事情关闭搞定。但是对于大集团型企业,往往招投标,采购,物流,供应商管理等都是独立的部门。至少也是独立的科室。
为啥要去观察企业本身业务组织划分?
我们实际来观察下你会发现。
拿一个企业来说,这个企业的采购部内部可能经常开会或吵架,但是采购部和招投标部之间经常就是一些招投标结果信息的传递。也就是企业在划分业务部门或组织的时候,本身就在考虑如何减少跨部门的交互接口。
这和微服务拆分里面的微服务间要尽量解耦是一个道理。
所以再回到多个聚合如何进一步归类的问题也是同样道理。
当你观察的时候,你会发现供应商,物料的管理实际是同一个基础数据管理部门在做。那么这两个聚合完全是可以划分到一个限界上下文里面。类似还有采购请购,采购订单,采购执行都是一个部门在做,那么也可以归到一个上下文。
当然你可以从一个完整业务流程分析出发,看究竟分为哪些大的阶段,一般来说这些阶段点都可以作为微服务划分的边界点。
往往比单纯的事件风暴后,再聚合更加有效。比如电商核心业务场景和订单生命周期如下。
当你对核心领域对象的生命周期阶段梳理清楚后,实际上已经可以明确地识别出关键的上下文边界和关键的领域对象。比如上图可以看到商品,订单,供应商,用户,库存都是关键的领域对象,相关的业务操作也围绕这些业务对象展开。
进一步思考,从聚合到上下文和子域当谈到这里的时候,自然出现了一个新疑惑。
即采用传统的按业界参考模型,大的业务流程阶段边界,或者企业本身的业务组织划分等来划分微服务,这样划分出来的微服务粒度更加合适,也更加容易满足高内聚,松耦合的需求。那么为何还要采用领域建模去划分微服务?
因此从聚合到上下文,这个逻辑如何从技术上去走通成了问题的关键。
即各个聚合之间本身还有耦合性,我如何去分析哪些聚合应该进一步归类到一个限界上下文里面,这有无一种技术层面的分析方法?
在这里谈下我个人的一些思考。
基于核心业务活动进行聚合
核心业务活动一般指最终带来明确业务价值的活动。
举例来说采购订单是核心业务活动,而采购申请,框架协议等是辅助类活动,或前导类活动,最终目的都是形成可执行的采购订单。
付款是核心业务活动,而付款申请则是辅助活动,最终为付款服务。
基于这个思路就可以将诸多的辅助业务活动,支撑活动等围绕核心业务活动的多个聚合进行归类,形成上下文边界。
基于上层业务分析底层聚合之间的耦合性
拿供应商和物料两个聚合来说,为何建议放到一个上下文和微服务里面。
如上图,在分析业务场景的时候你会发现,对于其它聚合根和供应商,物料之间的依赖关系不是点对点的依赖,而是需要供应商和物料聚合后的信息。
供应商和物料两个对象本身没有耦合性。
当时上层的业务场景让两个对象之间产生了明显的耦合性。在这种情况下就需要将其划分到一个限界上下文里面去。
由于在一个上下文,后续微服务划分对应一个数据库,自然也就减少了上层的多次API接口服务调用后的应用层组装操作。
希望以上两点思路对你在划分上下文边界的时候有所帮助。
再次总结下本文想说明的一些关键内容如下。
1.微服务设计到功能和数据两个层面的划分
2.事件风暴到聚合,多个聚合如何划分到一个上下文是难点
3.基于核心业务活动和聚合间耦合性是常用方法
当然,这篇文章还有很多没有思考完善的地方,也欢迎大家提出不同意见并进一步探讨,方便我后续对微服务划分思路进一步优化改进。