Close

切换到 Git 时处理 Maven 依赖关系

Nicola Paolucci 头像
Matt Shelton

开发人员推广人员


所以我们要转向 Git,我们喜欢 git-flow。现在要做什么?我们全部测试一下!我的团队很棒。他们汇总了 Confluence 中开发人员工作流程的热门列表,所有这些都是基于我们作为一个团队所做的事情以及他们认为我们将来可能要做的所有奇怪的事情。然后,在镜像我们自己的项目结构中(但里面没有代码,只有 pom.xml),尝试了所有工作流程。

Maven 依赖关系旨在证明自己是我们这一切中最大的问题。

Maven 版本编号 Maven 生成了 1.0.0-SNAPSHOT 构建直到发布。当您发布时,-SNAPSHOT 会删除,您的版本就是 1.0.0。您的构建流程需要能够支持在事后增加次要版本,这样您下次工作的后续工作才能生成像 1.1.0-SNAPSHOT 这样的构建。您不一定要使用三位数,您可以在启动项目时定义这个数字,我们是使用的三位数。无论如何,了解 -SNAPSHOT 部分真的很重要。这始终代表一个项目的最新预发布剪辑。

工件


所有这些工作流程中,我们最关心的是如何确保我们的项目版本和项目间依赖关系得到妥善管理。

默认情况下,每次为构建检索 Maven 依赖关系时,它都会从互联网中拉取这些依赖关系。这些工件存储在本地,以便可以更快地执行后续构建。减轻这种痛苦的一种解决方案是使用本地网络上的工件存储库作为这些外部依赖关系的本地缓存。即使是最快的 CDN,局域网检索也几乎总是比下载更快。我们使用 Artifactory Pro 作为工件存储库。此外,由于我们具有多模块结构,因此我们也将自己的构建工件存储在 Artifactory 中。构建常用包时,我们可以通过 maven 依赖关系解析拉取该特定版本,然后直接从工件存储库中检索工件。

这很顺利。Artifactory 还允许您在实例之间同步工件,因此,比如说,如果您想使用它来将发布存储库复制到数据中心进行生产部署,则无需构建单独的流程即可完成此操作。

数据库
相关资料

如何移动完整的 Git 存储库

Bitbucket 徽标
查看解决方案

了解 Bitbucket Cloud 的 Git

Maven 依赖关系、功能分支和拉取请求


我们所有的构建都进入了 Artifactory。在 SVN 中,我们一直在使用快照存储库来保存最新的两个快照构建,一个暂存存储库用于保存所有尚未批准的版本构建,并使用一个发布存储库仅用于投入生产的构建。[1]这些构建的编号与我之前描述的一样,并且可以通过基于存储库和版本的可预测 URL 模式进行检索。

每个开发人员的主要工作流程是从开发分支为他们的工作创建一个功能分支,完成该分支,然后提出拉取请求,将该工作合并回开发分支中。对于单个项目来说,这基本上没有问题,但我来介绍一下我们首先遇到的第一个问题,也是让我们认真重新考虑整个迁移的问题:

正如我之前所说,我们的项目之间存在多层依赖关系。无论是在历史方面还是在战略方面,对于我们的产品来说,这都是有充分理由的。我们已经考虑过可以消除这个问题的替代架构,但它们会引入其他架构。我们可以让我们的生活更轻松(我们做到了,这在后面会介绍),但就目前而言,保持结构原样对我们来说具有战略意义。

因此,开发人员 A,我们称她为 Angela,开始在 Jira 中开发一个功能。这需要两个分支:一个来自我们的通用项目,另一个来自产品 X。通用版本是 2.1.0-SNAPSHOT。产品 X 的版本是 2.4.0-SNAPSHOT。她在本地工作了一段时间,最后又推送回 Bitbucket Server。Bamboo 收集了这些变更,构建了通用软件包并上传了 common-2.1.0-SNAPSHOT 到 Artifactory,然后依赖 common-2.1.0-SNAPSHOT 构建产品 X,还要上传 productX-2.4.0-SNAPSHOT。单元测试通过!

开发人员 B,我们称他为 Bruce,他开始在 Jira 中开发另一个功能,用于另一款产品 Y。这也需要两个分支:一个来自我们的通用项目,另一个来自产品 Y。如上所述,通用版本是 2.1.0-SNAPSHOT。产品 Y 的版本是 2.7.0-SNAPSHOT。他在本地工作了一段时间,最终将自己的变更推送到 Bitbucket Server。Bamboo 收集了这些变更,构建了通用软件包并上传了 common-2.1.0-SNAPSHOT 到 Artifactory,然后依赖 common-2.1.0-SNAPSHOT 构建产品 X,还要上传 productX-2.4.0-SNAPSHOT。单元测试通过!

同时,Angela 在她的产品 X 代码中发现了一个小缺陷,并编写了单元测试来验证她的修复方法。她在本地运行修复并通过了测试。她将自己的变更推送到 Bitbucket,然后 Bamboo 选择变更并构建了产品 X。结果构建成功,但有部分单元测试未通过。这不是她写的新代码,而是她最初更改该功能后第一次写的代码。不知何故,Bamboo 构建竟发现了她本地构建没有发现的回归。这是怎么实现的?

因为她的共同依赖关系,即 Bamboo 在构建产品 X 时引入的那个,已不再是她的副本。当他的功能构建完成时,Bruce 覆盖了 artifactory 中的 common-2.1.0-SNAPSHOT。没有源代码冲突——两个开发人员都在各自的分支上单独工作,但是 Maven 的工件检索数据源已损坏

真是令人头疼。

在我们发现这个问题后的大约一个月里,我们竭尽全力解决这个问题。通过我们的 TAM[2],我们与 Bamboo 团队中使用 git-flow 的人员进行了交谈,并与维护 git-flow(git-flow 的 java 实现)的开发人员进行了交谈。它们都非常有用,但是如果没有一个要求每个开发人员每次开发功能时列出手动步骤的流程,我们找不到可以接受的解决方案。

如果您对我们考虑过的解决方案感到好奇,以下是我们尝试过的所有内容:

1. 在创建分支时或之后立即修改版本号。

  • 我们可以用 mvn jgitflow:feature-start 创建分支来做到这一点。
  • 我们可以使用 Bitbucket Server 钩子或本地 githook。
  • 创建分支后,我们可以使用 mvn version:set-version 手动设置。
  • 我们可以使用 [maven-external-version] 插件自动进行变更。

2. 在完成分支并合并回开发分支时修改版本号。

  • 我们可以用 mvn jgitflow:feature-finish 来完成分支。
  • 使用 git 合并驱动程序来处理 pom 冲突。
  • 在 Bitbucket Server 中使用异步接收后钩子

3. 手动完成所有操作。(开个玩笑,我们在这个选项方面并没有纠结太久。)

可能的工作流程


通过记住这个核心概念并对其进行反思,您可以理解 submodule 很好地支持某些工作流程,而对其他工作流程的支持则不太理想。至少在三种情况下,子模块是合理的选择:

  • 当组件或子项目变更过快或即将发生的变更将破坏 API 时,为了您自己的安全,您可以将代码锁定到特定提交。

  • 当您有一个不经常更新的组件,而您想将其作为供应商依赖关系进行跟踪时。例如,我为我的 vim 插件这样做。

  • 当您将项目的一部分委托给第三方,并且想要在特定时间或发布时整合他们的工作时。同样,这在更新不太频繁的情况下有效。

详细解释的场景要归功Finch

这些选项中的每一个都有某种负面的副作用。主要是开发人员每次需要功能分支时的手动步骤。我们希望他们一直创建功能分支。此外,在大多数情况下,我们无法有效地使用拉取请求,这会破坏交易。

这需要一两个人持续将近两个月,直到最后,对于我们为什么从错误的方向处理这个问题,我们有了一个(令人震惊的)启示。

一个版本可以统治所有人


Hindsight 是 20/20,我可以清楚地看出,我们最大的错误是我们将注意力集中在 git-flow 工具上,而不是使用我们已经拥有的工具来实现我们想要的工作流程。我们有:

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory

事实证明,这些都是我们需要的工具。

我们的一位工程师有一个非常聪明的主意,即既然问题不在于构建管理本身,而是构件被覆盖,我们应该改为修复 Artifactory。他的想法是使用 Maven 属性将快照存储库 URL 设置为包含 Jira 事务 ID 的自定义 URL,然后使用自定义模板将其工件写入 Artifactory 中动态创建的存储库。如果我们不需要对工件进行分支,例如,如果我们只开发一个产品且不常用,Maven 的依赖关系解析器会在开发快照存储库中找到它们。

我们在构建设置文件中设置了这个方便的小属性变量,并编写了一个 Maven 插件来在 maven 构建生命周期的最早阶段进行填充。从表面上看,这听起来令人难以置信,并激发了团队更加努力解决这个问题的动力。麻烦的是我们实际上做不到。maven 生命周期的最早阶段是“验证”。运行要验证的插件时,存储库 URL 已经解决了。因此,我们的变量从未填充,URL 毕竟不是分支命名的。尽管我们一直在与开发快照分开的存储库中使用布局,但它不会被隔离用于并行开发。

还是令人头疼。

喝完啤酒后,前面提到的工程师又做了一些挖掘和研究,研究了为 maven 添加功能的另一种方法:扩展。

“敬啤酒:生活中所有问题的根源和解决方案。”- Homer Simpson

插件等扩展为您提供了增强您的 Maven 工作流程的强大功能,但是它们是在生命周期目标之前执行的,并且可以更方便地获取 Maven 内部结构。通过使用 RepositoryUtils 软件包,我们强制 Maven 使用自定义解析器重新评估其 URL,然后使用更新后的值对其进行重置。[3]

扩展到位并经过测试,我们开始一个接一个地取消迁移前的任务,从“这个永远不会发生”到“这个星期一就会发生...所以现在我需要在明天之前写十页文档”。我稍后会写更多关于这些工具如何协同工作以实现我们新的开发工作流程的文章,以及我们在这个流程中学到的经验教训。

[1]:这里有一个缺点,就是我不得不使用自己写的脚本来点击 Artifactory REST API,将构建从暂存阶段“提升”到发布阶段。速度足够快,但需要更多的自动化。

[2]:大客户技术经理。单击此处了解更多信息。

[3]:在最初的开发工作后,我们发现我们必须做更多的工作才能使它百分之百地发挥作用,比如当 Artifactory 中的快照(来自另一位工程师)比您的本地快照更新时,Maven 抓取远程工件是会觉得“嘿,它比较新,所以一定更好,对吧?”


Footnotes

[1]: One downside here was that I had to use a script I wrote to hit the Artifactory REST API to "promote" builds from staging to release. It's fast enough, but begging for more automation.

[2]: Technical Account Manager. More information here.

[3]: After the initial development efforts, we found that we had to do even more to make this work 100% of the time, like when a snapshot is newer in Artifactory (from another engineer) than your local snapshot, Maven grabs the remote artifact because hey, it's NEWER, so it must be BETTER, right?

Matt Shelton
Matt Shelton

Matt Shelton 是 DevOps 倡导者和从业者,统管 Atlassian DevOps(和其他相关)服务在美洲的交付。他如今已经很少写博客,而是专注于培养新生代倡导者,倡导更好的工作方式。


分享此文章

推荐阅读

将这些资源加入书签,以了解 DevOps 团队的类型,或获取 Atlassian 关于 DevOps 的持续更新。

人们通过满是工具的墙进行协作

Bitbucket 博客

Devops 示意图

DevOps 学习路径

与 Atlassian 专家一起进行 Den 功能演示

Bitbucket Cloud 与 Atlassian Open DevOps 如何协同工作

注册以获取我们的 DevOps 新闻资讯

Thank you for signing up