依赖管理 - Gradle 版本目录条目命名的最佳实践
目录
引言
版本目录 是 Gradle 构建工具中相对较新的功能。它们通过提供一种标准化的方式来定义和访问项目中使用的依赖项目录,从而帮助管理依赖项——确保团队中的所有开发人员在依赖项名称和定义上保持一致,从而为每个人节省时间和认知负荷。像大多数 Gradle 功能一样,它们非常灵活,因此用户必须提出自己的约定来决定如何使用它们。
在这篇博文中,我们将分享我们在 Develocity 团队中使用版本目录管理依赖项时采用的一些最佳实践。特别是,我们将研究我们的约定,即如何从特定依赖项的 GAV(Group, Artifact ID, Version 的缩写,即组、工件 ID、版本)坐标派生版本目录条目名称。
版本目录 #
版本目录是 Gradle 依赖管理功能的一部分。它们提供了一种方便、标准化的方式来定义项目中的工程师可使用的一组依赖项。版本目录可以直接在 Gradle 的设置脚本中定义,也可以使用单独的 TOML 文件定义。TOML 目录的默认位置是 gradle/libs.versions.toml
。这将创建一个 libs
目录,以便在构建中使用。TOML 格式的示例条目可能如下所示
commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.14.0" }
当 Gradle 找到版本目录时,它会生成访问器,这些访问器可以在构建脚本中使用,以编译安全的方式定义依赖项。因此,我们不必编写
implementation("org.apache.commons:commons-lang3:3.14.0")
并在我们需要此依赖项的每个构建脚本中重复 GAV 坐标字符串,我们现在可以编写
implementation(libs.commons.lang3)
正如我们从这个例子中看到的,Gradle 在从目录条目 commons-lang3
转换为访问器 libs.commons.lang3
时应用了一些规则。此外,我们可能会问的一个问题是,为什么我将条目称为 commons-lang3
而不是例如 apache-commonsLang3
或 org_apache_commons_commons-lang3
(这两者也是有效的目录条目名称)。
目录条目命名约定 #
由于 Gradle 除了必须是有效的 TOML 条目名称之外,没有强制执行任何关于条目名称的命名约定,我们发现自己经常讨论为什么我们以这种或那种方式命名目录条目。在某个时候,我们决定与团队坐下来,并列出一份关于我们希望如何命名未来版本目录条目的约定列表。我们的目标是停止在每个 PR 上讨论这个问题。此外,我们希望目录中具有一致性,并为需要添加新条目的工程师提供指导。在本节中,我们将分享这份约定列表。对于每个约定,我们将提供一个组和工件 ID (GA) 元组,然后提供一个或多个关于如何 不 定义目录条目的示例,以及根据约定定义条目名称的正确方法。
条目中的段由破折号分隔 #
可以使用破折号或下划线来分隔条目中的段。根据 Gradle 文档的建议,使用破折号。
- GA:
org.apache.commons:commons-lang3
- 错误:
commons_lang3
- 正确:
commons-lang3
第一个段应从工件的项目组派生 #
使用唯一标识生成工件的项目组的内容。有时项目组与 GAV 的组 ID 匹配。例如,对于 org.slf4j:slf4-api
,组织和项目组是相同的 – slf4j
。在其他情况下,项目组是较大组织的一部分,例如 commons
项目组是 org.apache
组织的一部分。
- GA:
org.apache.commons:commons-lang3
- 错误:
apache-commonsLang
,org-apacheCommonsLang
- 正确:
commons-lang3
如果第一个段会被重复,则可以省略 #
我们喜欢保持简单并遵循 DRY 原则。因此,如果项目组与工件相同,我们不会重复它。
- GA:
dev.failsafe:failsafe
- 错误:
dev-failsafe
,failsafe-failsafe
- 正确:
failsafe
组或工件 ID 中的破折号转换为驼峰式命名 #
当 Gradle 生成访问器时,破折号会转换为点,而驼峰式命名保持不变。这会对在构建脚本中插入依赖项时的代码完成产生影响。因此,如果组或工件包含破折号,我们希望将该部分作为一个单元进行代码完成。因此,我们将破折号替换为驼峰式命名。
- GA:
com.networknt:json-schema-validator
- 错误:
networknt-json-schema-validator
- 正确:
networknt-jsonSchemaValidator
不要包含隐式术语 #
当构建 JVM 代码时,某些术语(如 java
或 sdk
)是隐式的,因此应省略。不要添加对于项目版本目录的上下文来说显而易见的内容。
- GA:
com.amazonaws:aws-java-sdk-core
- 错误:
aws-javaSdkCore
- 正确:
aws-core
当插件依赖项用作库时,始终以后缀 -plugin
结尾 #
这是我们团队中构建工程师的特殊规则,因为我们有时会在其他 Gradle 插件之上构建自定义插件。在这些情况下,我们定义插件工件的实现依赖项。为了将它们与“普通”库依赖项区分开来,我们添加了 -plugin
后缀。当它们在目录的 [plugins]
部分中管理时,此规则不适用,[plugins]
部分是版本目录中的专用部分,我们在这篇博文中不涉及。
- GA:
com.bmuschko:gradle-docker-plugin
- 错误:
bmuschko-gradleDockerPlugin
,plugin-bmuschko-docker
- 正确:
bmuschko-docker-plugin
整合在一起 #
现在我们已经讨论了从 GAV 派生版本目录条目名称的约定,让我们将它们应用于一些真实世界的示例。
示例:SLF4J #
SLF4J 附带一个 API 工件和几个可供选择的实现。我们将查看 API 工件,它的坐标为 org.slf4j4:slf4-api
。为此工件应用约定,我们…
- 确定项目组为
slf4j
- 我们删除组的 org TLD 部分
- 我们不想重复自己,所以我们从
slf4j-api
中删除slf4j
这为我们提供了 slf4j-api
作为此依赖项的版本目录条目名称。将其放入 libs.versions.toml
文件中如下所示
[libraries]
slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.13" }
在构建脚本中使用此条目如下所示
dependencies {
implementation(libs.slf4j.api)
}
示例:Jackson #
Jackson 是一个广泛使用的序列化库,包含许多组件。这次我们来看两个例子。首先,我们为 com.fasterxml.jackson.core:jackson-databind
派生一个条目名称
- 项目组是
jackson
- 我们删除组 ID 中的所有其余部分
- 我们不重复自己,所以我们从工件名称中删除
jackson
。
结果名称为 jackson-databind
。
现在让我们看看另一个 Jackson 工件:com.fasterxml.jackson.dataformat:jackson-dataformat-csv
。在这种情况下
- 项目组再次是
jackson
- 我们删除组 ID 中的其余部分
- 我们不重复工件中的
jackson
- 我们将
dataformat-csv
中的破折号转换为驼峰式命名
我们最终得到 jackson-dataformatCsv
。正如您所看到的,我在这里决定不将 core
或 dataformat
作为名称中的单独段包含。其他选项是使用 jackson-core-databind
和 jackson-dataformat-csv
,或 jacksonCore-databind
和 jacksonDataformat-csv
。我认为这真的只是个人品味问题,以及您希望依赖项的代码完成如何工作。
让我们再次将其放入 TOML 文件中
[libraries]
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.17.1" }
jackson-dataformatCsv = { module = "com.fasterxml.jackson.core:jackson-dataformat-csv", version = "2.17.1" }
并在构建脚本中
dependencies {
implementation(libs.jackson.databind)
implementation(libs.jackson.dataformatCsv)
}
总结 #
版本目录是 Gradle 的依赖管理功能之一。它们有助于集中依赖项 GAV 坐标,并在构建脚本中提供对这些坐标的编译安全访问。但是,Gradle 没有强制执行任何关于如何命名版本目录条目的约定。在这篇博文中,我们分享了 Develocity 工程团队为从 GAV 坐标派生版本目录条目名称而应用的一些最佳实践。我们展示了如何使用两个示例(SLF4J API 以及 Jackson 项目中的两个依赖项)应用这些约定。