依赖管理 —— 了解 go 语言依赖管理的演进路线
- 依赖:各种开发包
在实际的开发过程中,依赖管理是一个重要的概念。依赖则是指各种开发包,利用已有的封装好的、经过验证的额开发组件或工具来提升自己的开发效率。
工程项目不可能基于标准库 0~1 编码搭建,而更多的关注业务逻辑的实现,而其他的一系列的涉及到框架、日志、driver 以及collection等一系列依赖都会通过SDK的方式引入。这样对依赖包的管理就显得尤为重要。
0.0 写在前面
推荐一篇文章:Go 语言 依赖管理 - 掘金 (juejin.cn)
现在来说,go语言的依赖管理已经普遍使用Go Module的方式了。然而怎么我们初学者该怎么使用似乎还是个问题,即假如我们仅知道后文提及的相关理论,往往还是一头雾水。笔者在学习的过程中,遇到过比如在代码里import了一个GitHub上的代理,代码却会报错的情况,使用go get命令去下载依赖库也无法下载成功。究其原因,需要在IDE里配置GoProxy,GoLand里的配置如下图所示,file->settings->Go->Go Modules进入,更多的细节可以参见上面的文章。
1.1 Go依赖管理演进
Go语言的依赖管理主要经历了3个阶段:GOPATH -> Go Vendor -> Go Module。整个的演进路线主要围绕以下的两个目标来迭代发展:
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
1.1.1 GOPATH
GOPATH是一个语言支持的环境变量,变量的 value 是Go项目的工作区。一个 Go 项目有如下的结构:
- bin 项目变异的二进制文件
- pkg 项目编译的中间产物,加速编译
- src 项目源码
GOPATH的弊端:
本地的两个项目:A和B,A和B依赖于某一package的不同版本,问题在于无法实现package的多版本控制
1.1.2 Go Vendor
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
- 项目目录下增加vendor文件,所有依赖包副本形式存放在$ProjectRoot/vendor
- 依赖寻址方式:vendor => GOPATH,vendor中没有再去GOPATH中寻找
Go Vendor的弊端:
-
无法控制依赖的版本,只标识依赖的源码,不能标注依赖的版本
-
更新项目又可能出现依赖冲突,导致编译出错
1.1.3 Go Module
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
- 终极目标: 定义版本规则和管理项目依赖关系
Go Module是Go语言官方推出的依赖管理系统,解决了之前依赖管理系统存在的诸如无法依赖同一个package的不同版本等问题。从Go 1.11版本开始实验性的引入Go Module,从Go 1.16默认开启,一般读作 go mod。
Go Module通过go.mod文件管理依赖包版本,通过 go get/go mod 指令工具管理依赖包。Go Module的最终目标是定义版本规则和管理项目依赖关系。
1.2 Go Module实践
如上所述,比较完备的依赖管理系统包括三要素:
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
1.2.1 依赖配置 —— go.mod
一个例子:
module x-tiktok //依赖管理基本单元
go 1.19 //表示go原生库依赖的版本号
require ( //描述单元依赖
// module path + 版本号
github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gavv/httpexpect/v2 v2.8.0
github.com/gin-gonic/gin v1.8.2
github.com/google/uuid v1.2.0
github.com/rabbitmq/amqp091-go v1.7.0
github.com/redis/go-redis/v9 v9.0.2
github.com/stretchr/testify v1.8.1
gorm.io/driver/mysql v1.4.5
gorm.io/gorm v1.24.3
)
1.2.2 依赖配置 —— version
gopath和go vendor都是源码副本方式依赖,没有版本规则概念,而 go mod 为了放方便管理则定义了版本规则,分为 语义化版本 和 基于commit伪版本,
其中语义化版本包括三个部分:MAJOR、MINOR、PATCH,不同的MAJOR 版本表示是不兼容的 API,所以即使是同一个库,MAJOR 版本不同也会被认为是不同的模;
MINOR 版本通第是新增函数或功能,向后兼容; 而patch 版本一般是修复 bug ;
而基于commit的为版本包括星 基础版本前缀是和语义化版本一样的;时间戳 (yyyymmddhhmmss),也就是提交 commit 的时间,最后是校验码 (abedefabdef),包含 12 位的希尔前缀,每次提交commit后 Go 都会默认生成一个位版本号。
- 语义化版本
${MAJOR}.${MINOR}.${PATCH}- V1.3.0
- 基于 commit 版本
-
vx.0.0-yyyymmddhhmmss-abcdefgh1234 -
v0.0.0-20220401081311-c38fb59326b7
-
1.2.3 依赖配置 —— indirect
indirect是 go mod 中的一种特殊标识符,indirect 后缀表示 go mod 对应的当前模块没有直接导入该依赖模块的包,也就是非直接依赖,表示间接依赖。
1.2.4 依赖配置 —— incompatible
下一个常见是的是incompatible,主版本2+模块会在模块路径增加N后,这能让 go module 按照不同的模块来处理同一个项目不同主版本的依赖。
由于go module是1.11 实验性引入所以这项规则提出之前已经有一些仓库打上了2或者更高版本的tag了,为了兼容这部分库,对于没有go.mod文件并且主版本在2或者以上的依赖,会在版本号后加上 +incompatible 的后缀