本文对于应用层单体拆分微服务的过程中面临的挑战进行了分析,提出一种基于重构思想的分拆微服务的方法和相应的工具链使用,较基于文本编辑的分拆方式在开发效率和软件质量上有明显优势。传统的拆分方法通常涉及人工搜索代码中跨模块边界的本地调用然后重构,这样的方式在效率和覆盖率上均不理想。
我们旨在探寻一种最大避免文本编辑方式修改代码的方式, 尽量通过IDE 的重构功能在语法结构层面进行修改,并通过选择技术框架为重构创造条件。
实践
对于使用静态类型及OO编程范式的程序,从类型的依赖关系角度,程序可以被视为一个有向图,节点vertex是类型,边edge是类型的依赖关系,本文主要关注分布最广泛的方法调用依赖关系。所以edge就是 ( caller -> callee)。
拆分就是把一个有向图 拆分/变换为两个子图,分别命名为mono(原单体应用)和ms(微服务)。目标是两个子图之间edge 的callee 都是RPCClient, 以SpringCloud框架为例,RPCClient就是FeignClient。对于不符合该定义的edge称为illegal edge。
拆分包含以下步骤:
-
识别出需要拆分的微服务代码模块,并借助 move功能 将这部分代码放到独立package下。
-
识别出两个子图之间的illegal edge。
-
消除illegal edge, 即用RPC/REST 调用代替illegal edge的本地调用。
-
将ms 从mono项目move到新的微服务工程
以单体spring boot工程为例,假设符合一般性的controller,service和reposiotory的代码结构。
下图描述了系统在步骤1、2之后的的中间状态:
下图描述了系统在步骤3、4之后的最终状态:
上述步骤中存在挑战的地方为步骤2、3。对于步骤2 一种方法可以在单体内部人肉搜索并消除illegal edge,另一种方法把代码先拆分依靠编译错误驱动解决。
这两种方法都有明显缺点,前者人肉的效率和覆盖均不理想;后者代码分开后两个应用均会有编译错误,则无法使用refactor工具,效率和出错概率很严重。
我们找到契合当前问题的方案是使用IDEA的dependency analysis和自定义scope功能。Scope 对应于一个代码集合,可以用于IDEA的静态代码分析的参数,IDEA预定义的Scope有Production class, test class。
1 定义如下scope
-
mono-feign-client 对应单体应用的RPCClient。
-
mono 对应单体其余代码。
-
ms-feign-client 对应微服务的RPCClient。
-
ms 对应微服务其余代码。
2 定义deny rules
不符合预期的模块间依赖,即illegal edge:
-
mono -> mon-feign-client,单体应用不应通过RPC调用自身。
-
mono -> ms,单体应用不应直接本地调用微服务。
-
ms -> ms-feign-client,微服务不应通过RPC调用自身。
-
ms -> mono,微服务不应直接本地调用单体。
3 运行analyze dependency
定义完成后运行analyze dependency功能所有illegaal edge都会高亮显示。
4 消除illegal edge
如上图2所示,在caller端需要构建一个RPCClient并创建和callee方法相同的method;在callee端需要构建从REST endpoint到原始callee的调用链。
延续上面的列子,illegal edge 的caller为mono的TxnService.doTxn(), calllee为 ms的AccountServicey.queryAccount(…)。需要做如下重构:
1. rename callee 方法
AccountServicey.queryAccount(…) -> AccountServicey.queryAccountWrapper(…)。
2.extract AccountServicey.queryAccountWrapper(…)的所有内容代码为 AccountServicey.queryAccount(…)
3. AccountServicey.queryAccountWrapper(…)方法中,写如下代码:
4. 上一步骤中AccountFeignClient.queryAccount() 不存在,Alt+Enter创建该方法 ,方法体中打通到AccountServicey.queryAccount 的调用.
5. inline AccountServicey.queryAccountWrapper,caller端对于AccountService 的edge 就会指向AccountFeignClient。
5 拆分FeignClient:
当所有的illegal edge 消除完成后需要把FeignClient 进行如下拆分:
具体重构步骤如下:
1. 对于AccountFeignClient 类添加Feign annotation, 并进行extract interface 得到IAccountFeignClient,注意replace reference 到新interface,AccountFeignClient成为无引用状态unused 。
2. 在新的interface上添加Spring Annotation @RequestMapping, 再次extract interface 得到 IAccountController4Internal.
3. Rename AccountFeignClient 得到class AccountController4Internal,添加@Controller annotation。
总结
通过上面的重构来拆分微服务,可较传统的拆分方法能极大的提高效率并极大的减少了拆分时引入新的问题的可能性。
此外,学术界已经在探索基于数据分析方法例如cluster自动实现即对于微服务代码模块的识别。另一方面可以看到把本地方法调用refactor称为远程调用的步骤还是比较繁琐,IDEA Plugin或许可以实现GUI 一键refactoring。
如果你对今天的话题有什么思考与解读,欢迎扫描二维码关注“架构之旅”微信公众号,给我留言,我们一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢您的支持与关注!