Git Flow局限性
笔者在自己日常工作中常使用Git Flow模型管理Git代码分支,而随实际项目发展越发感受到Git Flow的局限性,认为它不适合解决一些特定场景问题,可由于Git Flow其流行过于广泛,深深印在每一位软件开发者的脑中,导致很多开发者认为Git Flow是解决Git分支管理混乱的唯一方式,似乎这灵丹妙药用了后再无烦恼。
当笔者想写篇文章聊聊这个话题,开始从网上搜索一些Git Flow文章,希望通过温习Git Flow知识减少自己理解偏差,过程中发生了较戏剧的一幕。Git Flow是某位大神10年一篇博客提出来的一个Git分支管理模型,笔者找到这篇博客原文时发现置顶的一段补充说明即是自己写此文的初衷,将原文此段内容摘入至此。
Note of reflection (March 5, 2020)
This model was conceived in 2010, now more than 10 years ago, and not very long after Git itself came into being. In those 10 years, git-flow (the branching model laid out in this article) has become hugely popular in many a software team to the point where people have started treating it like a standard of sorts — but unfortunately also as a dogma or panacea.
During those 10 years, Git itself has taken the world by a storm, and the most popular type of software that is being developed with Git is shifting more towards web apps — at least in my filter bubble. Web apps are typically continuously delivered, not rolled back, and you don’t have to support multiple versions of the software running in the wild.
This is not the class of software that I had in mind when I wrote the blog post 10 years ago. If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team.
If, however, you are building software that is explicitly versioned, or if you need to support multiple versions of your software in the wild, then git-flow may still be as good of a fit to your team as it has been to people in the last 10 years. In that case, please read on.
To conclude, always remember that panaceas don’t exist. Consider your own context. Don’t be hating. Decide for yourself.
看到这段说明后笔者已可停笔,但想着既然原文置顶描述了Git Flow存在局限的一面,作为写博客的一员似乎有某种义务去传播Git Flow有局限性这样一个观点,而不是让一些经验不足的开发者始终坚信Git Flow是唯一正确的Git分支管理模型。
Git Flow
一图胜千言,对于使用过Git Flow的开发者,不做过多解释一般也都理解。
各分支说明:
- master是无限期主分支,当前生产环境运行的代码版本
- develop是无期限主分支,当前开发环境的集成代码版本
- feature类分支用于特性开发,从develop checkout,完成后合并到develop
- release类分支用于完成从版本相对稳定到发布所需的一系列动作,从develop checkout,完成后分别合并到develop与master
- hotfix类分支用于完成生产环境版本的BUG修复,从master checkout,完成后分别合并到develop与master
以上内容,建议阅读原文以加深理解。
局限性
事物都有局限性,Git Flow也不例外,笔者所感知的表现有如下几点:
- 缺少对非PROD环境如何与分支管理结合的相关描述,如DEV、SIT、UAT、PET等环境
- 部分场景快速迭代的灵活性不容易被满足,如相同代码库多模块并行开发与版本发布
- develop与master的双主维护,可能出现人为失误导致“一致性”问题,如合并到master没有合并回develop,或冲突解决不一致
需要特别说明,以上几点是特定现状后端开发使用Git Flow模型可能面临的问题,而面向客户端开发不一定会遇到以上几点。
软件工程演变
多特性并行开发场景,笔者认为这是系统复杂化之后的一个常见问题,但对于迭代周期长的软件,这种现象较少形成问题同时对Git分支管理带来的影响较弱。如开源软件通常有多个特性在并行开发,这些特性本身是面向版本而进行的开发,如规划在6个月后发布某个版本而后确定该版本里面应该包含哪些内容,接着完成这些开发工作且严谨对待发布流程。
商业化公司的应用系统开发也是从这样一个模式开始,产研团队所有人目标对齐版本发布进行迭代,而版本发布频率受限于团队规模以及效率。为了提高团队上下游配合效率一般选择滚动2个版本,团队内成员基于不同岗位职责,有人在进行下一个版本前期工作如规划、设计、开发等,还有一部分人在进行当前版本的后期工作如开发、验收、测试、发布等。
这种团队所有人对齐版本的迭代模式是受认可的,但不足在于随团队规模变大版本应该包含的内容随之增加,发版间隔周期也会增加。20人团队与200人团队如果都是2周迭代一个版本,在集中式开发模式下若要保持效率相同显然前者更容易实现,后者除了在沟通协同方面临挑战,同时也面临软件开发方面的工程问题,最终导致难以达成。后文所讲的环境部署Git Flow模型即是这种面向版本发布的软件工程方法的局部表现。
软件工程方法论在自我演变,其中微服务架构某种程度上是满足效率提升的产物,它对200人团队2周发布一个版本的效率问题给出了一个答案
环境部署Git Flow模型
Git Flow中master分支表示PROD所运行的代码版本,但缺少其他环境与分支对应关系描述,所以不同开发者团队为了解决开发与QA协同问题,一般会对Git Flow做出一些扩展,笔者称为“环境部署Git Flow模型”:
- 沿用develop分支,对应DEV环境运行的代码
- 增加sit分支,对应SIT环境运行代码
- 增加uat、pet等分支,对应UAT、PDT等环境运行代码
这样之后看似环境与分支管理较好融合并实现了统一,但真相并非如此,我们拆解一个常见的软件迭代过程可能是这样:
- feature/x开发基本完成,合并到develop,并在DEV环境进行开发自测
- 开发团队自测完成后提测到QA团队,按照分支管理约定将develop分支合并到sit分支,部署SIT环境代码(一般由CICD工具自动完成)
- QA团队在SIT环境进行功能验证与测试,提出BUG并指派给开发团队修复
- 开发团队可以选择继续使用feature/x分支或重新从develop checkout新的分支修复BUG,重复1-3步骤
- QA团队确认BUG均已修复,将sit分支合并到uat分支,部署UAT环境代码
- 产品经理与需求方等相关人员在UAT环境进行功能体验,如果产生一些细节优化建议则重复1-5步骤
- UAT环境得到所有人一致认可后,在按照Git Flow进入release过程
- release分支代码合并到master,功能正式发布完成,同时合并回develop
以上1-8个步骤是笔者按实际工作所总结的内容,因公司或团队不同与大家理解可能有所差异,但也应有些共通之处,如果确实按照如上8个步骤完成一次开发迭代,这里面需要思考的问题有:
- 环境分支跟踪环境部署,是否也是无期限的主分支,每次版本发布后是否进行分支整理如reset到develop,这类分支合并过程的冲突应该怎么面对
笔者观点,环境分支不应该是无期限的主分支,后文讲另一种分支管理思路时会再具体说明环境分支的定位与管理,秉承环境分支不是无期限的,则分支应该在每次发版后reset到develop
- 当迭代的特性有多个且并行时如何处理,比如有feature/x、feature/y、feature/z并行开发其软件迭代过程相互交织会面临的问题
多个非相关或弱相关特性并行开发,从软件管理角度上我们不希望出现紧耦合而导致测试、验证、发布依赖,在当前软件工程所鼓励的敏捷快速方法论上,这一点尤其重要
比如某系统迭代分别由3位开发与QA人员协同进行x、y、z的特性开发,这3个特性的重要度、复杂度等可能所区别,当某一周内3位开发人员陆续提测了功能,将代码合并到develop并预期在1周后能够完成所有后续流程进行版本发布,这时意外出现了,其中一个特性可能由于质量问题、需求变化、人员问题等原因导致延期2周才能发布,这将拖累另外2个特性不能发布。进一步,这2周内又有可能出现其他特性需要提测,导致develop代码是一个非稳定状态,无法正常按照流程做版本上线
在多特性并行开发时,这种Git管理模型对我们所期望的敏捷灵活迭代将产生非常大的影响,因此使用此Git管理模型一定要避免出现多特性并行开发,或者找到其他解决方案,比如模块拆分成不同代码库、改变软件迭代风格(对抗或改变时代力量)、新的Git管理模型等
- 环境分支的管理是开发团队负责还是QA团队负责,这个不重要但又很重要
这个不重要但是又重要,原因是笔者自己没有正确答案但心中却认为应该有正确答案,说的有点绕,但根本原因可能是现实约束了理应。软件工程希望迭代更敏捷更灵活,Git管理模型这个点上也相应变复杂,因而导致熟练掌握成本更高,最初的Git Flow相对简单,在加入上述环境与分支关联性之后,掌握的难度变大,QA团队在这方面与开发团队相比有天然的不足(针对一般公司的人才密度),导致理应QA团队完成现实却无法完成
环境部署Git Flow模型演变
前文针对软件工程中集中式版本迭代风格描述了环境部署Git Flow模型的适应性与不足,但对于微服务演变的Git管理模型没有讨论,为了避免集中式版本迭代影响效率和扩展,将系统拆大为小模块化、服务化,各模块服务在开发流程管理上保持了解耦独立性,单个服务内会较少遇到多特性并行开发问题。
创业公司项目立项时不一定有足够资源完全按照微服务准则做技术架构实施,5-10个不同岗位的同事通过紧密合作几个月捣鼓出来一个基本可用的版本并成功发布,而后可能需要几年的发展,项目产研团队才有可能成长为几百人规模,受到外部因素影响这种成长大概率不是线性的,而是具有一定跳跃性。笔者在这过程中反复思考一个问题:“有没有一种工程架构能将这种项目演变史考虑进去,成为一种有生命力的架构”。思考的价值似乎不大,因为是否有这样一种技术架构在公司的发展历程中影响甚微,做技术做研发看到这种局限性,是幸运也是不幸。
收回题外话回到Git管理模型上,笔者描述一下经历过的某阶段项目核心特征以及所应用的Git管理模型,核心特征:
- 项目整体微服务化,部分模块却在同一个代码库开发
不同开发人员在同一个代码库中维护8个微服务模块,各模块独立部署到K8S集群,部署环境有DEV、SIT、UAT、PET、PROD、ALPHA等
- 独立并行特性开发
不同模块需求并行进行迭代与发布,单个模块迭代过程与环境部署Git Flow模型所描述的基本相同
形成上述两个特征前项目还有前一个状态,同一个代码库下区分多模块但部署是一个整体,那时团队使用的是环境部署Git Flow模型,因而遇到了前文提及的那些问题。
现阶段不同模块服务在同一个代码库开发但独立部署,为满足迭代需要,经过实践探索与演变,新的模型与环境部署Git Flow模型有如下几点重要区别:
- 不再有两个主分支,只有一个主分支,这个主分支包含所有模块的PROD代码
- 所有模块与环境分别有对应的环境游离分支,游离分支定位是随意移动的分支(指针),不会用于跟踪任何有价值代码
- 没有release或者相同用途的分支,只有主分支、feature分支、环境分支(PROD也由受保护的环境分支跟踪)
有以上三点区别后新的Git管理模型将按照如下过程进行开发迭代:
- 模块a的feature/x开发基本完成,开发将feature/x分支rebase最新主分支,并将模块DEV环境游离分支移动到feature/x触发环境部署并进行功能自测,
git push origin feature/x:dev.a -f
- 开发团队自测完成正式提测到QA团队,将模块SIT环境游离分支移动到feature/x触发环境部署,
git push origin feature/x:sit.a -f
- QA团队在SIT环境进行功能验证与测试,提出BUG并指派给开发团队修复
- 开发团队基于feature/x进行代码修复,重复1-3步骤
- QA团队确认BUG均已修复,将模块UAT环境游离分支移动到feature/x触发环境部署,
git push origin feature/x:uat.a -f
- 产品经理与需求方等人员在UAT环境进行功能体验,如果产生一些细节优化建议,重复1-5步骤
- UAT环境得到所有人一致认可后,通过merge request与code review合并到主分支(review可提前进行),完成后可选择环境分支移动过来做回归测试
- 在主分支上移动PROD环境分支触发部署,正式部署过程有权限管理,
git push origin main:prod.a
在这个新的模型中,发版较频繁为追求效率去掉了Git Flow模型中的一个主分支以及release分支,增加极为灵活的通过CICD与环境跟踪的游离分支,这些游离分支存在的目的仅仅是为了不同环境部署。开始测试前通过rebase最新的主分支代码,可以提前解决冲突并尽量保持所测试代码与未来PROD一致。
这个分支管理模型是一次结合实际项目阶段性情况的产物,基于一定的前提条件和约束,比如:
- 同一个代码库有多个模块且模块开发相对独立
- 微服务部署模式
- 健全的CICD工具
为何一个代码库有8个微服务模块,因为笔者认为应该如此,其中由来以后细说,也许下一个阶段应重构成独立代码库。有没有一种工程架构能将项目演变史考虑进去,成为一种有生命力的架构,同代码库的多微服务模块即是笔者对此问题的一次实践与探索。
写这篇文章有一个巧合,也是一种隐喻,笔者前段时间在Gitlab上创建了一个新的代码库,发现默认分支名是main而不是master,希望大家不把master作为Git中的“教条”,它只是一个名称。