v2 go module

Go Modules: v2 and Beyond

Go 里,v2+ 不是“同一个包的新版本”,而是“一个带新 import path 的新 module”。

也就是说:

v0: github.com/user/project // 不稳定版本,不加 /v0 
v1: github.com/user/project // 稳定版本,也不加 /v1
v2: github.com/user/project/v2
v3: github.com/user/project/v3

v2 开始,主版本号必须进入 module path。

1. 为什么 v2+ 要改 import path

Go Modules 正式化了 Go 的一个重要规则:

  • 如果旧 package 和新 package 使用相同 import path,
  • 那么新 package 必须向后兼容旧 package。

这叫 import compatibility rule

所以问题来了:

  • v2 的定义就是:允许不兼容 v1。

那它就不能继续使用 v1 的 import path。

因此:

// v1
import "github.com/googleapis/gax-go"

// v2
import "github.com/googleapis/gax-go/v2"

对应的 go.mod 也要改:

module github.com/googleapis/gax-go/v2

一句话:

  • 同一个 import path = 必须兼容;
  • 不兼容 = 必须换 import path。

2. v2+ 的核心规则

v2 开始,module path 末尾必须带主版本号:

module github.com/user/project/v2

用户安装时:

go get github.com/user/project/v2@v2.0.0

用户代码里:

import "github.com/user/project/v2"

不能写成:

module github.com/user/project

然后打:

git tag v2.0.0

这样是不符合 Go Modules 规则的。

3. 为什么 Go 要这么设计

这是为了解决依赖冲突,尤其是 diamond dependency problem。

假设:

如果 v1 和 v2 的 import path 一样,就会冲突。

Go 的解决方式是让它们路径不同:

github.com/user/lib
github.com/user/lib/v2

于是同一个程序里可以同时存在:

import "github.com/user/lib"
import libv2 "github.com/user/lib/v2"

这不是重复,而是两个不同 module path。

核心好处:用户可以渐进迁移,不必一次性把整个项目从 v1 改到 v2。

4. 推荐的 v2+ 目录结构

文章推荐的方式是:在仓库里新建一个 v2/ 子目录。

例如:

github.com/googleapis/gax-go

/go.mod       -> module github.com/googleapis/gax-go
/v2/go.mod    -> module github.com/googleapis/gax-go/v2

也就是:

  • v1 代码放根目录
  • v2 代码放 v2/ 子目录

这样同一个仓库可以同时维护多个 major version。

示例结构:

project/
  go.mod
  hello.go
  hello_test.go

  v2/
    go.mod
    hello.go
    hello_test.go

根目录 go.mod

module github.com/user/project

v2/go.mod

module github.com/user/project/v2

https://stackoverflow.com/a/70062338
Go 语言本身已采用主目录子目录方案, Ian Lance Taylor 在 “spec: language change review meeting minutes”:
Since the committee started Go has developed a standard way for handling incompatible Go library changes: a v2 release of the package.
The recent Go 1.22 release introduced the first incompatible change to the standard library, by adding math/rand/v2 as a replacement for math/rand.
(This is, of course, not really incompatible, as old programs continue to use math/rand and continue to work. Programs can update to the (slightly) incompatible math/rand/v2 at their leisure.)
src/math/rand 目录中可以看到 v2 子目录。

5. 创建 v2 module 的流程

假设已有 v1 module:

module github.com/user/project

创建 v2:

mkdir v2
cp *.go v2/
cp go.mod v2/go.mod

修改 v2/go.mod

go mod edit -module github.com/user/project/v2 v2/go.mod

得到:

module github.com/user/project/v2 // 这个命令只改 v2/go.mod 的 module 名,不会改任何 Go 源码 import

然后进入 v2

cd v2
go mod tidy
go test ./...

6. v2 内部 import 也要改

这是非常容易漏的一点。

如果 v2 module 里有多个 package,内部 import 也要从:

import "github.com/user/project/foo"

改成:

import "github.com/user/project/v2/foo"

否则会出现一个很隐蔽的问题:

v2 module 反过来依赖了 v1 module。

因为在 Go 看来:

github.com/user/project
github.com/user/project/v2

是两个不同 module。

所以迁移 v2 时,不只是改 go.mod,还要改所有内部 import。

可以批量替换:

find . -type f \
  -name '*.go' \
  -exec sed -i -e 's,github.com/user/project,github.com/user/project/v2,g' {} \;

实际项目中建议用 IDE、gofmt、测试和代码审查确认,不要盲目全局替换。

7. 发布 v2 预发布版本

如果 v2 API 还没最终稳定,可以先发预发布版本:

git tag v2.0.0-alpha.1
git push origin v2.0.0-alpha.1

用户可以显式安装:

go get github.com/user/project/v2@v2.0.0-alpha.1

预发布阶段可以继续调整 API。

注意:只要还没发布正式 v2.0.0,就还可以继续打磨 v2 API。

8. 发布正式 v2.0.0

当你确认 v2 API 稳定后:

cd v2
go mod tidy
go test ./...

cd ..
git add .
git commit -m "project: prepare v2.0.0"
git tag v2.0.0
git push origin main
git push origin v2.0.0

用户安装:

go get github.com/user/project/v2@v2.0.0

用户代码:

import "github.com/user/project/v2"

从这个时刻开始:

v2 也进入稳定承诺。

之后 v2.1.0v2.2.0v2.0.1 都应该保持 v2 内部向后兼容。

9. v1 和 v2 可以同时(也是必须)维护

发布 v2 后,不代表 v1 自动消失,v1 版本可能有用户在依赖,提出 issue。

你现在有两个 major version:

github.com/user/project       v1.x.x
github.com/user/project/v2    v2.x.x

后续可能要同时维护:

v1.1.0    给 v1 加兼容功能
v1.0.1    给 v1 修 bug

v2.1.0    给 v2 加兼容功能
v2.0.1    给 v2 修 bug

这就是 v2 的真实成本:不是打一个 v2 tag 就完事,而是从此多维护一条稳定线。

所以文章强调:重大版本升级必须有充分理由。

不要因为 API 有点不顺眼就发 v2。

评论