【中】微服务拆分
服务的拆分相对偏理论,不像代码对错一眼明了,下面关于微服务的拆分也只是我个人的经历和见解。
一、微服务的目的
在开始的时候项目几乎都是单体,只有大单体和小单体之分。单体存在的主要问题是:牵一发而动全身。
想要避免这个问题,最好的解决办法就是分而治之。大部分时候的开发都是对于某个模块的某个功能进行开发,拆分成微服务之后就可以最大程度避免牵一发而动全身。(也没办法全然避免,服务之间也是有依赖的)
拆分成微服务之后就会带来其它的很多问题,比如服务注册、发现、服务之间调用、分布式事务,服务增多本身的维护也会变得更加困难。
微服务的拆分主要是目的是为了快速、便捷的开发。多服务的问题会有很多组件来解决(Dubbo、Nacos等等)。
这篇文章主要是来解决拆分成多个服务,怎么尽可能的减少服务之间的维护成本,这就涉及到服务的拆分。
并不是一个项目很大,它被拆分成了多个小的项目,就算是微服务。
二、说明
在上家公司和现在这家公司,都有几十个服务,维护模式完全不同,为了更好的说明服务合理拆分的必要性,我将说明在上家公司遇到的问题,以及现公司是如何解决的。
只是作为技术讨论并不能说明什么。因为两家公司的开发模式是不一样的一个是敏捷开发,一个是瀑布式开发。之所以模式不一样这其中的缘由也很复杂就不再这里讨论了。
我希望告诉大家的是,谁都想做正确的事,但不是谁都可以坚持做正确的事,这通常需要付出很大的代价和需要极大的支持,而我们——一个普通的打工仔要学会适应。
二、拆分之前
项目难以维护的最大问题是数据的混乱,使用MyBatis操作数据库,每一个XML里面想操作什么表就操作什么表,想 JOIN 什么表就 JOIN什么表。表对应的Mapper会出现在任何地方,同样一张表的Mapper会出现在十几个服务。
没人知道某张表或某个字段,会在何时何地插、更新,也没人知道这个字段会被多少个地方用到。当需要删除一个字段的时候,或要梳理某个字段的使用场景的时候这通常非常困难。
频繁的 JOIN也会导致性能极差,一个不慎就会导致数据库崩溃,服务都不可用了。
三、拆分之后
服务拆分的第一步就是要做到一个表只能由一个服务提供出入口。
所有的系统都有用户,就可以创建一个用户服务,在这个服务里面提供 用户、角色、权限的增删改查。首先创建三个Mapper和三个Service,提供对应的接口。
思考一下,对于用户,通常我们是要获取用户的基本信息、角色、权限,这是一体的。
所以可以创建一个BO,这个BO里面包含 用户基本信息、还包含角色BO、权限BO。如果在用户Service里面返回这样一个用户BO,这个数据在任何地方都可以被用到了。
再思考一下,数据对外的出口一般有2个,一个是外部通常是REST接口,一个是内部(可以用Dubbo)。以往我们通常是三层架构,Controller直接去调用Service,但这样Service就会显得很复杂,它同时要处理APP端、管理后台的一些个性化逻辑。如果我们在中间再架一层,逻辑就会变得清晰很多。
以往我们调用链路是 Controller > Service > Mapper
有了一层之后 AppController > AppService > Service > Mapper
思考一下这样的好处?Service和Mapper几乎是固定的,是和表绑定的,通常情况下是很稳定的,变的是业务,有了 AppService这一层,所有的定制化操作都在这一层来实现。
另一个要注意的是实体的返回,在Service里面返回BO、AppService里面返回VO,Dubbo里面返回DTO。返回的数据无非是单个对象和List对象,只需要维护几个Format进行转换就好了。
基于上面的改造再来分析一下好处
- 数据的出入口锚定了,只能由具体的服务提供
- 对于每个服务 Controller(Dubbo的Api),Service、Mapper 也都锚定了
- 变的只有 xxxService了,xxxService只对具体的端提供服务,也不会很复杂
现在我们再要去变动数据结构的时候就很清晰了,改动范围都是可控的。同时Service提供的都是公共方法可以被任何地方复用,各个端的逻辑都在具体的 xxxService里,逻辑清晰。
再来说一说Service,Service里面的返回尽量不要是 entity,返回BO扩展性会更高一些。比如有一个套餐表,套餐可以使用的店铺有很多,如果Service只返回 entity,在各种 xxxService里面就要先查询套餐,再查询店铺,把它们组装起来返回,这就很麻烦了,完全可以创建一个套餐BO,里面嵌套店铺BO,一起返回。 Service 里面返回尽量是通用数据
接口返回尽量不要定制,直接返回对应的资源,比如上述说到的套餐,不管是哪个接口想要获取套餐的什么数据,返回的都是一个 VO或 DTO,不存在说某个场景需要用到套餐里面的几个字段,我们再去创建一个新的VO。
不可避免的有极个别特殊的接口,需要的数据很奇特,这时可以创建一个新的VO、DTO,在 xxxService里面再去组装数据,几乎是不用写XML的。
可能有些人认为数据的获取通过RPC接口来,不如直接查表来的快。一般同一批资源的都会被封装到同一个服务里面去,并且使用Dubbo作服务通讯也要比HTTP要快很多。相较于无法维护的系统来讲,这点的损耗也是值得的。
总结
把相同资源封装到了一个微服务里面,对于这些表的操作,只能通过这个服务,其它服务想要获取这些数据只能通过接口请求。为了使Service更加通用,在Service层前面再加一层用来聚合具体的业务逻辑。
服务拆分的过程中还要注意服务之间互相依赖的问题,要尽可能的避免此类问题。对于有些特殊的接口,比如APP端有个复杂的需求,需要聚合很多服务,可以单独起一个APP聚合服务,这个服务可以调任何服务,但没有服务来调用它,从而避免服务的相互依赖。