基于大仓库的微服务差异化构建工具

前言

微服务架构已经成为了各个公司项目的标配,项目不用微服务架构,都不好意思说自己是做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计划的正式版本中,将仅会报告订单服务受到了影响。

项目地址: github.com/bootun/vero…

推荐阅读

  • 《用Go语言自制解释器》
  • 极客时间《编译原理实战课》
  • 《Go语言定制指南》

本文首发于微信公众号:梦真日记 ,欢迎扫码关注

image.png

猜你喜欢

转载自juejin.im/post/7124529099187912734