软件项目的标准配置和自动检查更新

上一篇文章中介绍了如何使用利用 docker 容器创建微服务开发环境,解决的是怎么保证同一个软件项目在任何地方都能够快速复制出一个相同的环境用于开发和测试。但是,在使用了微服务架构的产品线中,我们还面临另外一个挑战。

挑战

系统由很多微服务构成,每个微服务都是一个独立的软件项目——单独的 git 代码仓库,各自的环境配置,独立的测试与发布流程。目前我们的系统中已经有了几十个这样的软件项目,而且数量还在不断增加。在我们每日的常规开发工作中,都不断的面临下面的问题:

  • 怎么快速创建出一个新的软件项目,包含所有必需的配置文件,并且符合产品线的标准开发测试流程和代码规范?
  • 怎么保证依赖的软件/库都及时更新到合适的版本?怎么防止开发人员使用了错误的版本而导致质量隐患?
  • 怎么防止开发人员使用了 license 不合适的第三方软件/库,导致法律风险?
  • 对于开发团队使用的公共基础设施的更改(例如 CI 服务器的某些变更),需要修改软件项目的配置的,如何快速的在所有软件项目中完成修改?
  • 怎么保证每个项目都应用了代码检查工具,并且及时更新到符合产品线统一定义的最新规范的配置?
  • 需要在软件的开发测试自动化流程中引入新的步骤或工具,怎么快速的部署到所有的软件项目中?

由于我们采用的开发平台和开发工具都使用了文件配置,以上的问题可以归纳为三个:

  • 首先是怎么简单快捷地创建出软件项目的一组文件;
  • 然后是对于现有的软件项目,如何检查文件的某些内容是否符合规范(比如依赖定义中是否包含正确的版本号,CI流程定义有没有被删除掉某些步骤);
  • 最后是怎么去更新已有的文件内容。

对于第一个问题,如何快速创建新的软件项目,业内已经有很多解决方案,通常使用称之为“脚手架” (scaffold) 工具。这些工具的本质上是基于模版创建出软件项目的一组基本文件。但是现有的脚手架工具都只能帮助新软件项目的初始化工作,并不能解决第三个问题,它们对于已经进入开发阶段的项目无能为力。而在开发实践中,根本不可能设置好项目模版就一成不变,技术在发展,流程在改进,开发流程和基础设施中也会有bug需要修复,经常会出现所有软件项目都需要做一些同样的改动的情况。

以前,遇到这样的情况,就得群发邮件,通知大家去修改。效果是可想而知的,有些项目忘记修改,有些改了但是改错了没有发现。而且大家习惯于遇到问题发现要修改的时候才找另外一个项目来抄,而不是按照邮件或文档里写的方法来改,结果抄的还是一个旧版本,或者是改漏的地方,要花大量的时间来排查问题。总之,在微服务化带来大量软件项目之后,这样的工作方式是相当低效的。

思路

厌倦了不停的发公告、催促大家做修改、帮助不同人解决重复出现的问题,得寻找一个根本解决问题的方法。也就是要做到:

  • 所有的规范要求应该通过代码(工具)强制检查或执行,而不是靠个人自觉
  • 所有与规范相关的代码更新都应该自动化进行,而不需要让每个项目的维护者手工去一次次做修改

方案

经过几个月的试用与改进,一套符合我们产品线最佳实践和规范的node.js项目通用设定以及自动配置工具最终实用化并开始全面推广,称为 SES Node.js Project Preset。

Preset 主要包含两部分:项目标准配置和自动检查更新工具。

项目标准配置

项目标准配置涵括了 node.js 项目从编辑环境、开发流程、代码检查、测试流程、CI设置到软件发布的软件开发周期各个环节。

开发人员日常需要执行的操作只有两个:

  • yarn docker - 启动 docker 开发环境,过程中会自动安装依赖,自动更新到最新preset
  • yarn test - 包括了代码编译、格式化、静态代码检查、执行单元测试、冗余代码检查等

代码提交到 GitLab 上,CI流程会自动执行所有检查流程,除了测试不通过外,任何规范性检查不通过都不能合并回主干(体现强制性)。

项目标准配置通过一系列的脚本、工具配置文件来实现。主要包括以下这些:

  • 项目基本的文件模版:README,LICENSE,CHANGELOG。
  • Git环境配置:.gitignore,.gitattributes。
  • 代码格式:编辑器风格定义文件 .editorconfig,TypeScript代码自动格式化工具 tsfmt、tslint。
  • TypeScript编译与代码检查:tsc配置文件tsconfig.json,代码检查工具 tslint,重复代码检查工具 jscpd。
  • 单元测试:mocha测试工具,nyc测试覆盖率报告。
  • 软件依赖版本:package.json 中的 dependencies、devDependencies、optionalDependencies。
  • Docker 开发环境配置:docker-compose.yaml。
  • 软件打包发布:rpm spec 以及 rpm build script。
  • GitLab配置:Merge Request模版、Issue模版、CI流程定义文件 .gitlab-ci.yml
  • NPM脚本:package.json中的scripts配置,包括 build,test,ci,rpm,docker,start,setup 等等。

自动检查与更新工具

这样一个工具的最底层操作都是文件的修改和拷贝。我没有自己从头构建,而是基于开源自动化项目配置工具mrm。Mrm缺省支持一些task,例如 LICENSE 文件、.gitignore、编辑器格式、ESLint规则等等。缺省task的设置是一般开源项目的通用配置,对于我们产品不太适用,但是没关系,mrm也支持自定制task,提供了一组API,可以比较方便的修改包括 JSON、YML、ini、纯文本等各种格式的文件。我开发维护了一个 preset 库,包含一组自定义task,涵括了上面一节里的所有内容。

这个 mrm preset 最重要的是它不仅仅是根据预定义模版创建文件,它还分析现有项目的配置,检查是否需要更新。某些更新并不是单纯的覆盖,而是根据项目的具体情况来生成的,例如对于 docker-compose.yaml,根据里面定义了那些测试依赖容器,自动配置被测试应用的环境变量;分析 package.json 中的依赖定义,自动更新旧版本,如果发现有未列入白名单中的依赖,则告警或报错。

虽然项目标准配置涉及的东西很多,但是有了这个工具,配置起来只需要执行一条命令:

1
$ npx -p mrm -p @ses/mrm-preset mrm all --preset @ses/mrm-preset

项目只需要配置一次,以后 preset 有新版本发布,在 docker 开发环境启动时就会自动安装并且更新。

当项目标准配置需要更改,仅需要更新并发布npm包@ses/mrm-preset ,也无需通知大家改动,任何开发者在进入项目开发环境时就会自动修改,他将修改提交到 GitLab 即可。

总结

规范必需强制性执行,才能保证代码质量。只要存在可以通融走捷径的地方,必然会形成技术债务累积。

要做到项目的配置能够全自动更新,环境的配置均应该以文件保存,与代码一同保存做 git 库里 (Infrastructure as code)。例如 Jenkins 的配置就没办法跟随项目代码一起变动,而 GitLab CI 配置就可以,这也是我们迁移到 GitLab 的其中一个原因。配置最好采用声明式风格,而非命令式,这样才方便自动修改。比如NPM script容易改,若项目用 gulp.js 做make工具,就很难改。(采用声明式风格,基于文件的配置,是现在很多平台和工具的趋势,例如 Docker,GitLab CI,Kubernetes,Vagrant等等)

综合上次介绍的 docker 标准环境和这里介绍的项目标准配置,利用自动化工具,可以有效提高开发效率,避免大量由于环境、配置因素出现的问题。