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

前言

微服务架构已经成为了各个公司项目的标配,项目不用微服务架构,都不好意思说自己是做Web开发的,似乎使用微服务架构已经成为了一种政治正确。事实上,如果项目结构设计得当,是否使用微服务架构仅仅是实现上的细节罢了。而随着项目规模的扩大,各种问题也会接踵而至…

背景

公司项目使用Monorepo[1]的形式来组织代码,项目内分成了11个不同的微服务。每次开发完新功能,发测试环境时,CI[2]都会将这11个不同的微服务全部重新构建打包成Docker镜像,然后再由开发人员手动点击“发布”按钮,将镜像部署到测试环境。整个过程耗费10-15分钟左右。

其实这样的速度目前来说是没什么问题的,又不是抢着去投胎(误)。但实际上总会有那么一小部分场景,想要尽快发版看到结果。

由于我司规模不大,负责前端的同学就坐在我旁边,有时候对接接口的时候,发现个BUG,或者临时改了个什么东西,而前端的同学又等着接。于是代码推上去之后,我就在那干瞪眼等着CI跑好,赶快点个【发布】,等发好了,最后告诉前端同学:“发好了,可以继续接了”。

由于每次构建会影响所有微服务,因此在发布的整个过程中,服务是完全不可用的。

缘起

记得第一次产生解决问题的想法是在疫情时,忘记什么原因了,和leader一起,在屏幕前干瞪眼等了好久CI,那时跟leader感叹了下:“有空一定要撸个根据改动的文件来构建服务的工具。”

虽然我本科读的是“软件工程”,但我却越发痴迷于语言细节和底层原理(在此之前其实我更喜欢偏宏观一些的软件工程、架构设计等领域)。恰巧前段时间粗略翻了几页柴大的《Go语言定制指南》[3],就想到通过构建项目的依赖树,分析本次修改产生的影响,顺着依赖树往上,就能确定影响到了哪些服务的构建。

破局

有了想法之后,就该行动了。我创建了一个名为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[4],将veronica的报告级别控制到源代码层面:

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

如图所示,当前的veronica只能根据文件的ImportDecls将依赖控制到目录级别。假设有如下两个服务:

用户服务:
import "pkg/util/idutil"

func main() {
    idutil.GenUserID()
}
订单服务
// 订单服务
import "pkg/util/idutil"

func main() {
    idutil.GenOrderId()
}

它们都共同依赖了idutil这个package,当我们修改了GenOrderId的实现后,当前版本的veronica会报告两个服务都受到了影响,而在veronica计划的正式版本中,将仅会报告订单服务受到了影响。

项目地址: https://github.com/bootun/veronica

推荐阅读

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

参考资料

[1]

monorepo: https://en.wikipedia.org/wiki/Monorepo

[2]

CI: 持续集成

[3]

《Go语言定制指南》: https://book.douban.com/subject/35852237/

[4]

AST: 抽象语法树


原文始发于微信公众号(梦真日记):基于大仓库的微服务差异化构建工具

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/167828.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!