4

深入Go Module之讨厌的v2

 3 years ago
source link: https://colobu.com/2021/06/28/dive-into-go-module-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

深入Go Module之讨厌的v2

Go module不但遵循语义化版本规范 2.0.0,而且还更进一步,对语义化版本中的major还还赋予了更深的意义。

  • v0.X.X: 对于主版本号(major)是0的情况,隐含你当前的API还处于不稳定的状态,新的小版本可能不向下兼容
  • v1.X.X: 当前的API处于稳定状态,minor的增加只意味着新的feature的增加,API还是向下兼容的
  • v2.X.X: major的增加意味着API已经不向下兼容了

问题: 你知道在go module中,哪些版本号隐含当前API是不稳定的?

但是go module与众不同鹤立鸡群卓然不群的是,一旦你的major大于等于2, 你的module path必须加上v2后缀(如果tag是v3.X.X,那就是v3后缀,以此类推)。

而且,包引用路径也要加上v2,比如 go.etcd.io/etcd/client/v3

这是一个怪异的写法,相当于在正常的易于理解的module path上加了一个狗屁膏药,以提示这个引入的库是哪个版本的?

为什么要加上这个v2、v2后缀的,肯定有一定的考虑。

最主要的,Go的开发者(这里指Russ Cox)在import compatibility rule指出:

If an old package and a new package have the same import path,
the new package must be backwards compatible with the old package.

也就是相同module path应该保证新的版本向下兼容。

这种想法是好的。比如你在你的项目中可以使用同一个库的多个版本, v1版本处理以前遗留的逻辑,v2版本处理新的逻辑,v3版本试验未来的版本,同一套库的不同版本可以共存,并不会出现版本冲突的地方。

而且程序员看到这些module path,也很清楚的知道版本不兼容了,谁是更新的版本。

但是这种方式也是很有争议的,在实践中中也带来了很多问题,我在开发rpcx深受其害,又比如etcd,你可以看它的v3.4.X的版本,就是因为没有加上v3的后缀,导致go命令下载或者导入(get)这些package的时候根本就下载不了。

vX后缀污染了package path

本来正常的package path一般是仓库路径+package name,或者go module下 module path + package的方式,可是一旦版本大于等于2,就不得不加上一个后缀v2,v3等,将package path的含义改变了。

当然忍一忍我们还能接受,大不了闭着眼睛用呗,最痛苦的很多Go的初学者并不了解这种设置,不知道导入新的库的版本要加v2后缀,一脸茫然。

v0, v1和v2数据类型不兼容

在module path中增加了v2,v3等后缀后,也就以为着这些package都是不同的package,虽然它们中大部分的数据类型并没有做改变,还是向下兼容的,也不能直接赋值,还是需要强转一下。

比如你的项目依赖Auth 1.0.0, 也依赖Auth 2.0.0,那么即使A.Config在两个版本中没做任何改变,你也不能把Auth.Config赋值给Auth/v2.Config,而是需要在代码中加上强转的逻辑,两两互转。一旦发布了v3,那就得三三互转,很长的一个switch分支处理这种情况,如果发布v4,那么逻辑更复杂了。

给第三方库的开发者带来了很大的负担

虽然你觉得我也就发布v2,v3,v4等几个版本,版本路线很清晰,管理起来也不复杂,没什么大不了的。

但是,如果你的库是一个非常流行的库,很多开发者基于你的库开发了第三方的库的话,就非常痛苦了。

这意味着一旦你发布了一个新的版本,这些第三方的开发者就必须及时的更新他们的库,基于你的新的版本发布他们新的v2,v3版本。这就像病毒一样,初步扩展开来。给广大的开发者带来的很大的负担。

当然,见仁见智,这些情况可能你不会遇到,或者也不会给你带来困扰,所以它不是一个问题。而我,在开发rpcx,或者解答一些网友的问题的时候,深深被v2伤害到了,小小的心灵无法承受v2之重。

一些开源项目,为了避免版本号跳到v2,采用了其它的一些办法,比如protobuf-go, 正在做新的版本的重构,改动非常大,不和以前的版本兼容了,可以以前的版本都v1.X.X了,那怎么办呢?换module path名称。

  • github.com/golang/protobuf: 支持先前的protobuf go,目前最高版本v1.5.2
  • google.golang.org/protobuf: 新版本的module path,目前最高版本v1.27.0,初始版本v1.20.0

对于我开发的rpcx项目,因为在go module出来之前版本号已经发布到了v6.X.X。 我想回到从前,貌似回不去了。所以我采用了一个极端的做法,把tag重建,所有的版本号都定义在v1.X.X内。还好影响的用户比较少,所以也没有用户抱怨。

我这种做法比较极端,没造成用户抱怨的原因是我一直坚持go module和GOPATH并存的方式。发版的时候采用go module发版,master开发分支上采用GOPATH方式,绝大部分用户都使用master分支,或者自己fork了一个新的版本,所以造成的影响很小。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK