Go 模块:v2 及更高版本
简介(Introduction)
翻译自 Go 官方博文 Go Modules: v2 and Beyond。
随着一个成功项目的成熟和新需求的增加,过去的特性和设计决策可能不再有意义。开发人员可能希望通过删除不推荐的函数、重命名类型或将复杂的包拆分成可管理的部分来集成他们学到的经验教训。这类更改需要下游用户努力将代码迁移到新的 API,因此,如果不仔细考虑好处大于成本,就不应该进行这些更改。
对于仍处于试验阶段的项目–在主要版本 v0,用户预期会出现偶尔的破坏性更改。对于声明为稳定的项目,在主要版本 v1 或更高版本时,在新的主要版本中必须进行破坏性更改。这篇文章探讨了主要版本的语义,如何创建和发布一个新的主要版本,以及如何维护一个模块的多个主要版本。
主要版本和模块路径(Major versions and module paths)
模块标准化了一个重要原则,导入兼容原则:
如果旧包和新包具有相同的导入路径,则新包必须向后兼容旧包
根据定义,包的新主版本与以前的版本不向后兼容。这意味着模块的新主版本必须具有与以前版本不同的模块路径。从 v2 开始,主版本必须出现在模块路径的末尾(在 go.mod 文件中的 module 语句中声明)。例如,当模块的作者 github.com/googleapis/gax-go
开发 v2 版本后,他们使用了新的模块路径 github.com/googleapis/gax-go/v2
。想要使用 v2 的用户必须将其包导入路径更改为github.com/googleapis/gax-go/v2。
对主要版本后缀的要求是 Go 模块与大多数其他依赖关系管理系统的不同之处之一。需要后缀来解决钻石依赖问题。在出现模块前,gopkg.in 允许包维护人员遵循我们现在所称的导入兼容性规则。使用 gopkg.in,如果你依赖一个包的导入路径为 gopkg.in/yaml.v1 和另一个包的导入导入路径为 gopkg.in/yaml.v2,这两个包并不会冲突,因为两个 yaml 包有不同的导入路径,它们使用一个版本后缀,就像 Go 模块一样。因为 gopkg.in 与 Go 模块共享相同的版本后缀方法,Go 命令接受 gopkg.in/yaml.v2 中的 .v2 作为有效的主版本后缀。这是与 gopkg.in 兼容的特例:托管在其他域的模块需要 /v2 这样的斜杠后缀。
主要版本策略(Major version strategies)
推荐的策略是在以主版本为后缀名的目录中开发 /v2 模块。
github.com/googleapis/gax-go @ master branch
/go.mod → module github.com/googleapis/gax-go
/v2/go.mod → module github.com/googleapis/gax-go/v2
这种方法与不知道模块的工具兼容:存储库中的文件路径与 GOPATH 模式下 go get 所期望的路径相匹配。这种策略还允许在不同的目录中共同开发所有主要版本。
其他策略可能会将主要版本保留在不同的分支上。但是,如果 v2+ 源代码位于存储库的默认分支(通常是 master)上,则不知道版本的工具,包括 GOPATH 模式下的 Go 命令,可能无法区分主要版本。
本文中的示例将遵循主版本子目录策略,因为它提供了最大的兼容性。我们建议模块作者遵循这一策略,只要他们有用户在 GOPATH 模式下进行开发。
发布 v2 及更高版本(Publishing v2 and beyond)
这篇文章以 github.com/googlevis/gax-go 为例:
$ pwd
/tmp/gax-go
$ ls
CODE_OF_CONDUCT.md call_option.go internal
CONTRIBUTING.md gax.go invoke.go
LICENSE go.mod tools.go
README.md go.sum RELEASING.md
header.go
$ cat go.mod
module github.com/googleapis/gax-go
go 1.9
require (
github.com/golang/protobuf v1.3.1
golang.org/x/exp v0.0.0-20190221220918-438050ddec5e
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b
google.golang.org/grpc v1.19.0
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099
)
$
为了开发 github.com/googlevis/gax-go 的 v2 版本,我们创建一个新的 v2/ 目录并将我们的包复制到其中。
$ mkdir v2
$ cp *.go v2/
building file list ... done
call_option.go
gax.go
header.go
invoke.go
tools.go
sent 10588 bytes received 130 bytes 21436.00 bytes/sec
total size is 10208 speedup is 0.95
$
现在,让我们通过复制当前 go.mod 文件并向模块路径添加 v2/ 后缀来创建 v2 go.mod 文件:
$ cp go.mod v2/go.mod
$ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod
$
请注意,v2 版本被视为与 v0/v1 版本不同的模块,两者可能在同一个构建中共存。因此,如果 v2+ 模块有多个包,则应该更新它们以使用新 /v2 导入路径,否则,v2+ 模块将依赖于 v0/v1 模块。例如,要将所有 github.com/my/project 引用更新为 github.com/my/project/v2,可以使用 find 和 sed:
$ find . -type f
-name '*.go'
-exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} ;
$
现在我们有了 v2 模块,但是我们希望在发布发行版之前进行实验和修改。在我们发布 v2.0.0(或任何没有预发布后缀的版本)之前,我们可以对新 API 进行破坏性的更改。如果我们希望用户能够在正式版本前对新 API 进行实验,我们可以发布 v2 预发布版本:
$ git tag v2.0.0-alpha.1
$ git push origin v2.0.0-alpha.1
$
旦我们对 v2 API 感到满意,并且确信我们不需要任何其他破坏性的更改,我们就可以标记 v2.0.0:
$ git tag v2.0.0
$ git push origin v2.0.0
$
截至目前,现在有两个主要的版本需要维护。向后兼容的更改和错误修复将导致新的次要版本和补丁版本(例如,v1.1.0、v2.0.1等)。
结论(Conclusion)
主要版本更改会导致开发和维护开销,并需要下游用户的变更迁移。项目越大,这些间接成本就越大。一个重大的版本更改应该在找出一个令人信服的原因之后才能进行。一旦确定了重大更改的令人信服的原因,我们建议在主分支中开发多个主要版本,因为它与更广泛的现有工具兼容。
对 v1+ 模块的破坏性更改应该总是发生在一个新的 vN+1 模块中。当一个新模块发布时,这意味着维护人员和需要迁移到新包的用户需要额外的工作。因此,在发布稳定版本之前,维护人员应该验证他们的 API,并仔细考虑在 v1 之后是否真的需要破坏性更改。
- Windows Server 2008R2 配置网络负载平衡(NLB)
- .Net 转战 Android 4.4 日常笔记(9)--常用组件的使用方法[附源码]
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(38)-Easyui-accordion+tree漂亮的菜单导航
- .Net 转战 Android 4.4 日常笔记(8)--常见事件响应及实现方式
- silverlight于javascript通信
- 微信上线小游戏:对流量基础入口应用商店革命
- Appium Desktop 使用
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(36)-文章发布系统③-kindeditor使用
- Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
- .Net 转战 Android 4.4 日常笔记(7)--apk的打包与反编译
- 丰富的silverlight控件
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(35)-文章发布系统②-构建项目
- .Net 转战 Android 4.4 日常笔记(6)--Android Studio DDMS用法
- 机器学习加密货币IOTA在机构支持下跃起
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 「Eclipse」生成能用命令行运行的jar包
- 「AndroidStudio」fastjson导包报错:Could not resolve com.alibaba:fastjson:1.1.56.android
- 「Android」通过注解自动生成类文件:APT实战(AbstractProcessor)
- 五、开始Github和码云之旅,新手如何上路
- 用 Shader 写个完美的波浪~
- K8s上的Go服务怎么扩容、发版更新、回滚、平滑重启?教你用Deployment全搞定!
- 图解Go内存管理器的内存分配策略
- why哥这里有一道Dubbo高频面试题,请查收。
- 「容器平台」Kubernetes网络策略101
- 架构师之路 - 服务器硬件扫盲
- 零基础Python教程045期 元组的增删改查测试实验
- 零基础Python教程044期 列表的函数方法,很耐用!
- 零基础Python教程047期 GUI窗体界面编程,迈出软件开发的第一步
- 零基础Python教程046期 矩阵行列互换算法,二维数组的典型应用
- 有一次小明传数据给我,把我弄哭了