中台和微服务架构规划-模块划分和接口服务识别定义(201123)
对于传统企业微服务架构转型,基于中台和微服务思想进行传统IT系统的改造和优化是一个重要的趋势,特别是在企业IT架构逐步走向云原生技术的时候,微服务本身也是关键的要素。
而对于微服务整体的治理框架,我在前面给出一个大的框架图,如下:
整个微服务治理框架覆盖了微服务全生命周期管理,其中本身又分为微服务架构规划和微服务开发和运维两个关键的阶段。而在微服务架构规划阶段最重要的又是两件事情,即微服务模块的拆分和拆分后的微服务模块应该提供的API接口服务识别和定义。而这个本身也是后续多个微服务进行并行开发和持续集成的基础。
微服务划分大原则
注意这里讲的是大原则不是绝对原则。对于传统的单体应用在转到微服务的时候我们也是给出实际实践总结的一些大的原则做为参考。
原则1:划分为<10个微服务模块
一个传统的单体应用,在进行划分的时候最好不要超过10个微服务模块。注意这里指的业务功能模块不包括底层的系统管理,流程引擎等技术模块。
大家可能也会看到传统的单体应用一般也不会超过10个以上的一级功能菜单,因此在划分微服务的时候也可以参考一级功能菜单进行划分。
注:这个原则不绝对,比如一些类似OA,IT运维流程管理类系统,所有的业务功能流程之间基本没有任何耦合性,这种业务系统可以将微服务划分的更细也没有大的影响。
原则2:强数据关联模块不要拆分
简单来说就是有些系统基本就是围绕一个核心数据或业务对象展开的管理系统,比如我们说的资源管理系统,资产管理系统等。
对于这些系统的资源,资产等核心数据,建议都不要再进行微服务拆分,这些数据之间本身存在强数据关联和一致性要求,如果进行微服务拆分后续很难保证事务一致性要求。
原则3:以数据聚合驱动业务功能聚合
这个理解起来困难,简单来说就是微服务划分不仅仅是上层业务功能模块划分,而且包括了数据库的拆分,因此在划分微服务的时候首先要考虑数据域的拆分,基于数据和功能的CRUD分析来考虑数据的聚合关联要求。先拆分好数据域,再来考虑数据域里面有哪些业务功能。
原则4:从纵向功能划分思路到横向分层思路转变
在微服务划分里面,同样需要结合SOA的横向分层思想,即:
传统的单体应用你会发现在进行微服务划分的时候,本身微服务也体现出分层属性,即有些属于底层的业务对象,实体,数据提供类微服务;有些数据业务功能和规则类微服务;而还有些属于上层的流程和服务功能组合类微服务。
因此在进行微服务划分的时候应该从数据-》功能规则-》流程的分层模型维度综合考虑。
原则5:高内聚,松耦合的基础原则
当然,在进行微服务拆分的时候高内聚,松耦合的原则不变。而如何确保拆分的数据库,拆分的业务功能模块满足高内聚松耦合的原则。
在前面做企业架构规划分析的时候就经常谈到,在进行业务流程分析,业务架构和数据架构规划的时候,核心会产生业务流程,业务功能,业务数据对象等关键的信息,而我们要做的就是对这些关键进行交互矩阵分析来识别业务模块,业务数据之间的内聚特征。
微服务模块拆分
在IT应用架构规划大中台的概念下,实际上中台包括了技术中台和业务中台,对于技术中台重点是各个技术组件和技术服务能力的实现,而对于业务中台则主要包括了数据能力和业务规则处理能力的提供。
对于业务中台本身也是分层,包括了最基本的数据类中心和业务处理类中心。
数据类中心本身又包括了基础主数据类和核心共享业务单据类,比如我们看到的产品中心,用户中心,属于基础主数据类。而对于订单中心,库存中心则属于业务共享数据类。
业务处理类则主要是进行核心业务功能和逻辑的处理,类似的包括了交易中心,结算中心,计费中心等,都可以看做是业务处理类的中台模块。当然,业务处理类中台也包括关键的领域服务提供组件,即这类组件需要调用底层更多基础原子模块的核心接口能力,经常组合后再提供出整合的组合能力。
技术中台模块的划分-完全垂直彻底解耦
技术中台主要负责提供各类的技术服务能力,其中包括了消息,缓存,存储(内存,结构化和非结构化),文件,日志,通知,规则引擎,流程等各种通用的和业务无关的技术能力。可以看到对于各种技术服务之间,本身没有任何的耦合性,完全是垂直独立,因此对于技术中台中的微服务模块划分粒度,完全可以一个技术服务一个微服务模块,完全独立管理和部署,相互之间没有任何影响。
技术中台属于我们传统的PaaS技术平台必须要管控和治理的一部分内容,对于技术服务的接入,消费和调用,日志等都需要在PaaS管控治理平台能够查询和监控。
业务中台模块的划分-围绕数据仍然是核心
注意这里的业务中台模块,就是一个独立的微服务模块,一个独立的有数据库,逻辑层到前端界面展现的小应用。只是这个业务中台本身还将自己的关键能力以API接口服务的方式开放给前台应用调用。
业务中台模块划分粒度相当重要,粒度太细后期集成和管控都相当复杂,同时由于模块划分太细也容易引入更多前台应用开发带来的分布式事务处理场景。而粒度太粗的话本身又无法起到独立自治,后期易于变更运维,本身灵活敏捷的作用。
如果说做互联网电商,由于有类似阿里等的成功实践,可能大家闭着眼都知道应该包括类似产品中心,库存中心,用户中心,订单中心,结算中心等各类的中台模块。但是如果是全新的业务类型,或者说针对企业内部的管理信息化,究竟应该如何去划分中台。
1. 基础主数据类模块划分
如果一个基础主数据就一个微服务模块,那么会导致我们的微服务模块的量极大,比如用户中心,组织中心,人员中心,客户中心,物料中心,供应商中心等。那么这种划分方法在企业内信息化上并不太合理。
如果是按标准的主数据管理系统的业务模块划分,实际上它不是按照数据维度进行划分,一般我们说主数据系统会包括了元数据和数据对象管理,数据集成和调度,数据清理,流程引擎,数据内容管理等各个模块。而无法真正体现出以数据为中心的思想。
因此对于基础主数据类微服务模块,最好的方式是按数据域进行拆分,比如对于用户,组织,岗位,人员等划分到一个人力域数据中心,对于供应链,物料编码,采购类别等划分到采购或供应链域微服务模块。这样划分的好处就是既实现了一定程度上的模块拆分和松耦合,同时按域划分后,同一个域的基础主数据本身在一个数据库中进行管理,这些基础主数据本身可能存在一定的耦合关系,那么这些耦合关系的处理将不再需要提升到分布式事务层面去处理。
2.业务功能类模块划分
谈业务功能类模块的划分,实际上又回到我们在传统进行单个业务系统架构设计的时候如何进行组件划分这个话题。如何划分组件,简单来说就是要高内聚和松耦合,确保每个组件尽可能地独立自治,组件和组件之间的干扰最小。
当时我们在做单系统的时候,往往对于组件划分考虑得并不彻底,就是说在代码层面确实划分了组件,组件也可以进行独立打包,但是对于各个组件仍然对应一个数据库。那么我们就经常犯一个错误,即虽然在业务和逻辑层两个组件看似松耦合了,但是很多耦合性转变到了数据库层了。举个简单的例子,数据库的一个关联查询很容易就实现了,但是涉及到关联的两个表,其核心Owner却是不同的两个组件模块。
所以对于上层的业务组件划分,本身原来我们也谈两个方法,这里拿采购管理系统举例。
- 方法一:按大阶段划分,可以分为采购请求,采购订单,采购执行,采购绩效评估,供应商,物料等模块。
- 方法二:按数据维度划分,可以划分招投标中心,采购中心,绩效中心,供应商中心,物料中心等。
不论是哪种方法,我们看到在进行微服务模块划分的时候,关键还是要把基础和共享数据找出来,然后才是去处理上层的业务。再来考虑业务流程,业务规则处理等还需要单独设置哪些微服务模块。即使是用方法一,我们也看到每个模块都一定有自己关键的主属Owner数据。
划分微服务模块,实际上相对关键的一个步骤就是拆分数据库的过程。要把原来我们一个数据库的内容,拆分并对应到各个微服务模块上。主属Owner,对该数据具备了完全的CRUD能力。而其它模块更多的是单纯的数据导入或数据查询。
大中台更多的是体现其核心数据集中和共享能力,而不是业务流程和规则处理能力,因此更多的业务流程处理都应该归结到前台更加合适。一个大中台的订单中心,可以和各种各样的前台微服务模块对接,不管是网页还是APP应用,不管是渠道,政企还是电商,但是最终都是将正式流程处理完成后生效的订单数据导入到订单中心。然后订单中心再提供完整的订单能力开放。
接口服务识别
在这里回顾下在SOA架构规划方法中的服务识别方法。
在SOA实施中服务识别是相关重要的一个环节,如何基于业务驱动IT的思路,识别出真正粗粒度,高度自治,可复用的服务是后期服务治理管控能否顺利的一个关键。否则SOA建设到后期很容易就变成一个数据集成平台,而非是服务共享平台。
要知道服务的本质还是接口和交互,因此对于服务的识别总体来说两种方法:
- 自顶朝下:基于业务流程分析入手,分析跨系统业务流程交互,识别出集成点和接口点,再转化为服务
- 自底朝上:基于当前遗留系统已有的系统间接口情况,分析接口对应的业务场景,再进行接口转服务
可以看到不论是哪种方法,这里面都有两个重点,其一是必须要明确的清楚每一个服务的业务含义,对应的业务场景和业务交互点在哪里?其次就是需要做接口转服务这个关键动作。
1. 自顶朝下的服务识别
如果各个业务系统之间没有业务和数据的交互,那么跟根本不应该存在任何的接口或服务。而对于服务的存在原因主要还是我们的端到端业务流程往往是跨了多个业务系统的交互才能实现的,而这些跨业务系统的交互点要么进行业务规则的校验,要么进行关键业务数据和单据的传递。
这些交互点和集成点都是潜在的接口服务识别点,通过这种方式识别出来的服务我们就很清楚服务对应的业务流程和场景。比如采购系统和ERP之间有一个采购订单导入的接口服务,我们就很清楚整个供应链流程中,采购订单的创建和生效是在采购系统里面,但是基于采购订单的采购接收和入库是在ERP系统,因此需要将采购订单同步到ERP系统中。
这种跨系统流程分析基本会把横向的所有关键接口全部梳理出来,但是容易遗漏掉纵向的一些基础数据共享接口,距离来说我们在拟制采购订单的时候,是需要输入和选择项目信息,选择对应的库存组织信息的。而这些基础数据需要从项目管理系统和ERP系统中同步过来。但是在流程图中往往只会有采购订单创建节点,因此容易遗漏这些基础数据共享接口。
还有就是某一个业务功能在实现过程中,可能涉及到调用外部的业务规则和逻辑,这种交互点也容易遗漏需要特别注意。举例来说,采购订单在创建完成的时候并提交的时候需要检查预算是否足够,而这个检查规则逻辑是预算管理系统实现的,因此在这里也存在采购系统和预算系统间的业务接口和服务。
2. 自底朝上的服务识别
特别遗留系统环境进行SOA架构改造和服务接入的时候,我们要意识到必须要将遗留系统间的当前已有接口全部分析和梳理清楚。因为既然当前遗留系统能够正常运作,也能完成跨系统的业务交互,那么原来的接口从面上是完全能够覆盖业务需求的,我们唯一要考虑的是接口本身的定义和设计是否合理的问题。
当整理出完整的接口清单后,我们要做的就是搞清楚每一个接口对应的业务场景究竟是如何的?基于这种反向思路我们只关注关系和接口相关的业务交互场景,而不是关注全部业务流程。搞清楚了具体的业务场景后,接着要做的重点工作就是对接口进行评估。
对于接口的评估主要包括了如下内容:
a) 接口当前使用频度如何?是实时还是定时同步?同步频率能否满足业务的要求?
b) 接口传递的数据量如何?当前在数据传递的时候有无性能问题?
c) 接口本身的实现是如何的?比如Dblink,存储过程,WS接口,还是原始的JDBC接口等。
d) 接口本身的复用度如何?相关类似的接口有哪些?相互之间有哪些差异?
做了接口评估最主要的仍然是要考虑如何进行接口去重和接口合并,对于接口转服务的方法,我前面有专门一篇文章再谈,在这里就不再展开。
本身相互独立的两个业务系统,比如A系统和B系统,按道理两个系统应该完全独立,包括数据库,中间件,应用部署包等。两个系统之间只能通过标准的接口进行业务和数据的交互。但是实际在项目实施中发现一种普遍存在的现象,即A系统将自己的数据库账户完全开放为B系统,而B系统在拿到数据库连接串信息后,通过自己编码实现完全可以对数据库A中的数据库表进行各种任意的CRUD操作。
在这种情况下可以看到,A和B两个系统之间已经完全耦合在一起,这个时候A系统要做数据库层面的迁移和数据库表结构的变更往往全部会影响到B系统。如果要把这些接口点找到,必须要把B系统中的所有代码进行遍历查找和分析,往往才能够找到具体的接口点有哪些。这些不规范的使用方式都对后续的SOA服务改造和迁移造成相对重大的影响。
微服务下的接口服务识别
在跨系统间的接口集成中服务的识别和定义方法,可以总结为:
- 基于流程架构和业务架构,从跨系统交互流程出发,分析业务交互接口识别业务服务
- 基于数据架构和主数据建模分析,识别关键的数据服务能力。
- 基于技术架构和共性平台层分析和定义,以能力开放原则识别关键技术服务能力。
因此对于跨系统间的集成,对于服务识别和定义思路是相对清晰的。那么在传统方法中业务系统的划分和定义粒度又是如何?在前面企业架构分析中,我曾经谈到过,通过跨系统交互流程分析,识别出最细粒度的业务功能模块和功能单元,然后再从底向上进行聚合,以CRUD分析为主要方法,多次迭代出最佳的满足高内聚,松耦合条件的业务系统划分。这里面没有一个精确方法,但是却有该大原则下的指导方法。
不管是传统的跨业务系统间交互的接口,还是微服务模块间的交互API接口,我一直强调的一个关键就是接口一定要保证粗粒度特性,实现业务规则和逻辑的高度内聚。接口面对的应该是核心的业务对象,领域对象或业务规则能力暴露,而不是微服务模块内部的数据库表的CRUD操作的暴露。如果将数据库表CRUD操作暴露为Rest API接口并在微服务模块间相互调用。一个是耦合性增加,一个是完全没有实现高内聚的基本要求。
基于以上基础原则,我们在进行微服务接口识别和定义的时候,仍然需要从业务流程出发,梳理清楚完成一个完整的业务流程各个微服务模块之间有哪些业务交互接口,然后将这些接口识别出来后,才进行接口的拆分或合并,最终形成微服务API接口,只有这样最终的微服务API接口才是可以复用的。
由于我们已经将基础数据管理独立到一个基础模块,因此可以基于数据能力开放和暴露的原则将这些基础数据的能力以查询服务方式暴露为独立的数据服务能力接口。要求仍然是领域对象级而不是数据库表级别。
每一个微服务模块在开发和实现的时候,如果都是基于领域驱动架构设计的思路进行的,那么只有微服务模块的领域对象定义完整,完全可以将领域对象的能力以API接口的方式暴露出去,这里既包括了查询类接口,也包括了导入或数据插入类接口,其次对于核心的业务规则的实现可以独立暴露为接口服务。
在前面微服务架构咨询里面我曾经谈到过,在多个微服务模块之上,还可能有一个微服务能力组合层,实现类似流程服务和组合服务类的能力。如果存在这种情况,那么最好也是独立的微服务模块来实现,这个微服务模块本身可能并不对应具体的数据库,而是将底层的微服务模块之间服务能力进行组合,形成新的接口服务能力。
由于在微服务架构设计中,我们更加强调数据不落地的方式进行后续的开发和实现,由于数据不落地,我们就可以更好的以能力开放的思路来进行接口的识别和暴露。简单来说有哪些数据或业务对象在你这,有哪些业务规则属于你管理?这些都在经过粗粒度聚合后,都可以识别和定位为微服务API接口。
接口服务定义
对于中台构建,实际上两个关键点,第一个就是划分微服务模块粒度,第二个就是在模块划分清楚后确定服务识别和定义的粒度。
在前面谈模块拆分的时候谈到,尽可能以数据的维度来进行模块拆分,数据包括了基础主数据和核心共享数据,在数据驱动下拆分模块,那么模块底层对应的数据库本身如何拆分基本也就清楚了。一定要知道,微服务架构下,我们底层数据库也是拆分了的。
底层数据库没有拆分,但是仍然用SpringCloud框架开发,可以拆分为多个JAR包,在这种模式下只能认为是一个微服务模块,而不是独立,因为其不存在独立自治能力。我们看到很多上层开发采用SpringCloud框架,但是数据库仍然采用一个数据库的情况,再次说明,对于这种架构设计,不是标准意义上的微服务架构,没有做到彻底解耦。
面向资源,面向对象和领域驱动设计
对于Http Rest接口说得最多的就是面向资源的设计,对于资源有标准的Http Put, Post, Delte, Get等操作。因此你需要定义好相应的资源。资源可以放在计算机上并体现为比特流的事物,可以是结构化的数据或对象集合,也可以是图片或文件流,这些都是可以处理和操作的资源。
面向资源和领域驱动本身部署一种新的软件工程设计方法,真正的方法只有传统的面向结构设计和面向对象设计两种。因此对于面向资源本身可以面向传统结构化设计,也可以按面向对象设计。当然最好的方式仍然是面向对象进行设计。
资源即实体,实体即对象,这个对象代表的是业务对象,有明确的业务含义,类似供应商,采购订单,产品,合同等。同时这些对象本身存在关联和递进的层次结构,比如供应商有对应的联系人,有对应的银行账号。产品可能有对应的维修记录等。
而这些业务对象正是我们在领域驱动设计时候经常会识别的领域对象,这个领域对象本身涉及到多个子类,是否归结到一个大的领域对象最关键的还是是否共属一个生命周期。面向资源设计,完全可以采用面向领域设计方法,首先定义领域对象,将领域对象建模为对应的资源,然后再考虑看这个资源应该暴露哪些能力接口出来。
所以在微服务架构下,首先要了解清楚,一个核心是面向资源进行Rest接口能力设计,资源的识别和定义可以参考领域设计的思路进行,识别和定义领域对象,再转为资源定义。
接口服务的粗粒度是关键
如果我们不按上面的方法按领域对象方式来定义资源,那么我们最容易犯的错误就是将所有的数据库表对象都全部定义为一个个独立的资源,将这些资源的CRUD操作,全部暴露为Get,Put,Post和Delete接口方法。那么这样暴露出来的Http Rest接口方法就全部是细粒度的接口。
这种方法很省事,一个模块有100张表,你只需要暴露100个接口,每个接口都含标准的上述操作就完事了。但是这种接口服务识别和定义没有任何意义,也不符合我们粗粒度的要求。
那么问题的关键点在哪里?
其关键就是本身应该是粗粒度的体现业务价值的接口服务全部都变成了细粒度的DAO访问类细粒度接口服务。失去了接口本身的意义,同时又将本身应该完全内聚在微服务模块内部的业务逻辑全部暴露到外层去解决。这个有点类似我们在进行领域驱动设计的时候,经常谈到的贫血的领域服务层,即我们的Http Rest接口应该是粗粒度的,应该是满血的领域服务层的能力暴露,而不是底层数据库CRUD操作的暴露。
通过识别的资源来识别和定义接口
只要确定了资源,那么我们就很容易来确定资源应该提供哪些接口。
在我们进行接口设计的时候,如果一个资源完全不需要和外部微服务模块或外部应用打交道,那么这个资源完全不用开放任何接口。这个是我一直强调的原则,即在微服务模块内部最好是走传统API接口交付方法进行调用,而不是走Http Rest接口服务,这一方面是提升性能,一方面是减少各类难以应对的分布式事务问题。
在资源定义清楚后,往往资源都是一个复合对象,比如采购订单资源,涉及到采购订单头或采购订单明显信息,之间还存在关联,但是这是一个完整的资源对象。
对于采购订单对象,根据业务场景,存在外部导入新的采购订单,存在外部对已有的采购订单进行变更后导入,存在外部需要查询采购订单集合,同时查看某一个特定key值的采购订单的详细明细数据。这可能是我们经常会遇到的接口需求场景,从这些场景可以看到,我们的设计完全可以基于采购订单资源展开。
创建新的采购订单: POST /Orders
修改一张ID为1的已有订单:PATCH /Orders/1
删除ID为1的已有订单: DELETE /Orders/1
查询所有采购订单: GET /Orders
查询ID为1的采购订单: GET /Orders/1
可以看到,第一种方法就是上面的,可以直接在资源后面增加不同的参数条件进行模糊查询。其次,我们可以将查询条件定义为一个查询实体类,同时将整个查询实体类的信息通过一个完整的实体对象传递过去进行查询,查询完成后再返回相应的结果。
比如我们定义一个OrderQueryExt 实体类。
基于特定条件的模糊查询:POST /Orders/OrderQueryExt
那是否会存在只查询一张采购订单的采购明细列表信息?如果存在这种情况,应该按照资源和资源层次关系进行设计,由资源逐层展开查询。
查询ID为1的订单的所有采购明细: GET /Orders/1/OrderDetails
查询ID为1的订单的流程审批记录信息: GET /Orders/1/ProcessDetails
查询ID为1的订单的所有附件信息: GET /Orders/1/Attaches
对于PUT和PATCH而言,如果涉及的情况一般是对实体的部分数据进行更新,同时还需要支持SaveAndUpdate操作,那么我们一般都采用PATCH方式,而不是PUT方式。即实际资源接口设计的时候,单纯的PUT场景往往现在已经很少发生。
如何业务规则和逻辑处理定义接口
这是我们遇到的第二大类,前面基于资源进行接口设计思路已经很明确。但是对于业务规则处理类往往比较难,比如我们经常遇到的提交报账单的时候需要进行预算校验和控制,这个就是典型的业务规则处理类,报账单提交需要,合同提交往往也需要。
那么这里的资源究竟是什么?
对于预算信息本身应该不是这里的资源,因为预算信息本身的录入和维护,才会涉及到预算信息本身开放接口。而这里的业务场景进行是对已有的预算信息进行规则计算和校验。
基于这类场景,我们看到比较好的设计方法是定义一个独立的规则类,将规则类映射为一个资源,比如这例子里面我们可以定义一个BudgetControl的规则类,这个类可以定义为一个资源对象。任何一个规则处理都涉及到有具体的输入和输出。
比如预算校验:
输入:具体的组织信息,预算科目信息,当前申请预算,年度或月度信息
输出:校验结果信息
你会看到对于预算校验,预算扣减,预算冻结,实际上他们的输入和输出都是相同的,那么我们可以划归到同一个规则处理类里面进行处理。那么规则类的定义,需要增加一个规则处理类型即可。
那么不论是预算校验,还是预算冻结,可以看到实际的接口调用都是:
POST /BudgetControls
当然也可以将预算检查,预算冻结等定义为预算控制类的子对象,比如预算检查Valid,那接口调用为:
POST /BudgetControls/Valid
所以对于业务规则的处理可以看到,最重要的是业务规则类的定义,业务规则类定义清楚了,业务规则类转为资源,形成对资源的Http操作接口。
对于业务规则类,一定是粗粒度服务接口,规则的处理逻辑都应该完全控制在模块内部而不是被暴露到外面去。因此对于规则类的定义也是,仅仅提供仅有的输入和输出,能够满足规则处理和计算要求即可。