微服务架构已经成为了各个公司项目的标配,项目不用微服务架构,都不好意思说自己是做Web开发的,似乎使用微服务架构已经成为了一种政治正确。事实上,如果项目结构设计得当,是否使用微服务架构仅仅是实现上的细节罢了 。而随着项目规模的扩大,各种问题也会接踵而至...
公司项目使用Monorepo的形式来组织代码,项目内分成了11个不同的微服务。每次开发完新功能,发测试环境时,CI都会将这11个不同的微服务全部重新构建打包成Docker镜像,然后再由开发人员手动点击“发布”按钮,将镜像部署到测试环境。整个过程耗费10-15分钟左右。
其实这样的速度目前来说是没什么问题的,又不是抢着去投胎(误)。但实际上总会有那么一小部分场景,想要尽快发版看到结果。
由于我司规模不大,负责前端的同学就坐在我旁边,有时候对接接口的时候,发现个BUG,或者临时改了个什么东西,而前端的同学又等着接。于是代码推上去之后,我就在那干瞪眼等着CI跑好,赶快点个【发布】,等发好了,最后告诉前端同学:“发好了,可以继续接了”。
由于每次构建会影响所有微服务,因此在发布的整个过程中,服务是完全不可用的。
记得第一次产生解决问题的想法是在疫情时,忘记什么原因了,和leader一起,在屏幕前干瞪眼等了好久CI,那时跟leader感叹了下:“有空一定要撸个 根据改动的文件来构建服务 的工具。”
虽然我本科读的是“软件工程”,但我却越发痴迷于语言细节和底层原理(在此之前其实我更喜欢偏宏观一些的软件工程、架构设计等领域)。恰巧前段时间粗略翻了几页柴大的《Go语言定制指南》,就想到 通过构建项目的依赖树,分析本次修改产生的影响,顺着依赖树往上,就能确定影响到了哪些服务的构建。
有了想法之后,就该行动了。我创建了一个名为Veronica(维罗妮卡)的仓库。是的,这个名字很中二,取自复仇者联盟2中钢铁侠的一套外太空支援系统——维罗妮卡。
veronica会分析并构建整个Go项目的依赖树, 现阶段veronica仅仅通过分析 .go
文件的import声明 ,以 package
(目录)为单位来组织依赖树。这样实现的优点就是简单、分析速度快。缺点也是显而易见的,粒度较粗,当修改了某个包下的某个文件后,所有依赖这个包的微服务都会被veronica标记为受影响。
要使用veronica,首先需要在项目根目录下放置 veronica.yaml
文件,该文件声明了项目的服务列表,以及其他的一些配置项:
- version: 0.1.1
- # service下的每个项都表示一个微服务
- services:
- api-gateway:
- # entrypoint 表示服务的入口,即服务的main包
- entrypoint: cmd/api-gateway
- # ignores 表示该服务忽略的文件列表,即使这些文件改动,veronica也不会判定它影响了当前的服务
- ignores:
- - 'pkg/**/*doc.go'
- # hooks 里的文件如果被改动,veronica会报告该服务收到影响,无论该服务是否真的依赖了这个文件
- hooks:
- - '**/Makefile'
- - 'go.mod'
- # 可配置多个服务,每个服务可以有各自的ignores和hooks
- assets-manager:
- entrypoint: cmd/assets-manager
- assets-cron:
- entrypoint: cmd/assets-cron
在正确配置了文件之后,使用以下命令告诉veronica,你本次提交修改了哪些文件:
git log --name-only -1 --pretty=format:"" | veronica
-1
是指输出最后一次commit提交的信息,这条git命令会将最后一次commit改动的文件传递给veronica,接着veronica会向你报告,这次改动的文件影响了哪些服务的构建:
- cmd/assets-cron
- cmd/api-gateway
这样我们就可以用shell脚本或其他方式,来单独构建受影响的微服务了。
当前veronica还处于积极开发的阶段,许多特性还不稳定,报告的粒度也比较粗。下一步计划优化当前的项目结构,并尝试构建整个项目的AST,将veronica的报告级别控制到源代码层面:
如图所示,当前的veronica只能根据文件的 ImportDecls
将依赖控制到目录级别。假设有如下两个服务:
用户服务:
- import "pkg/util/idutil"
-
- func main() {
- idutil.GenUserID()
- }
订单服务
- // 订单服务
- import "pkg/util/idutil"
-
- func main() {
- idutil.GenOrderId()
- }
它们都共同依赖了 idutil
这个 package
,当我们修改了 GenOrderId
的实现后,当前版本的veronica会报告两个服务都受到了影响,而在veronica计划的正式版本中,将 仅会报告订单服务 受到了影响。