原文地址:www.ardanlabs.com/blog/2019/1…
引言
Modules提供了一套集成的解决方案,为go开发者解决了自从go初次发布以来的三个主要痛点:
- 在GOPATH外开发go能力
- 版本依赖和识别最兼容版本的能力
- 用原生的go 工具来解决依赖问题
随着go1.13的发布,这些痛点都已经成为历史。在这篇文章中,我主要关注从GOPATH到modules的转换以及modules解决的问题。我将提供足够多的概念来帮助你从比较高的层面上理解modules是如何工作的。或许理解设计的原因比懂得如何设计更重要
GOPATH
GOPATH提供了存储go工作空间的物理存储位置,GOPATH的使用很好地服务了go的开发人员。但这却成为非go开发人员的瓶颈,他们需要时不时地基于go项目来进行开发工作,但且没有设置go的工作空间。go team需要解决的问题是go repo可以被clone到物理磁盘的任何位置(包括GOPATH以外),并拥有一个工具能够定位,编译和测试代码。
图1

go get来clone一份这个repo到你的gopath下,并且使用该repo的名称(canonical)。
举一个使用modules之前的例子,如果你执行go get github.com/ardanlabs/conf,代码会被clone到你的磁盘,路径为$GOPATH/src/github.com/ardanlabs/conf。多亏了GOPATH,不管开发这的go的工作空间在哪里,go的工具能够找到这些这些代码。
List2
import "github.com/ardanlabs/conf"
// GOPATH mode: Physical location on disk matches the GOPATH
// and Canonical name of the repo.
$GOPATH/src/github.com/ardanlabs/conf
// Module mode: Physical location on disk doesn’t represent
// the Canonical name of the repo.
/users/bill/conf
list2表示将repo clone到硬盘的任何位置所带来的问题,当开发者可以将repo clone到它磁盘的任何位置上,所有用来解析import package的信息都失去了。
解决这个问题的方法就是有一个特殊的文件来记录这些repo的canonical名字。这个文章在硬盘上的位置是GOPATH的替代品,不论repo被clone到哪些位置,该文件中定义的repo的canonical name能够帮助go来解析import。
这个特殊的文件被称为go mod,这个文件中的canonical name所代表的实体被称为module
list3
01 module github.com/ardanlabs/conf
02
...
06
list3展示了go.mod文件中的第一行,这一行展示了module的名字,表示了开发者希望用来解析repo内的code的canonical名字。不论这个repo被clone到哪个位置,go都可以找到module的位置和名字来解析import的。
有了module,代码可以被clone到磁盘的任何位置,下一个问题是如何支持代码的打包和版本化。
打包和版本化
大多数的版本控制系统都支持在repo的最新的commit上打tag,这些tag被用来发布系统的功能,往被当做是不可变的。
使用版本控制系统的工具来指定特定的tag,开发者可以clone一个package的某个特定的版本到硬盘上。然后,必须先回答一下几个问题:
- 应该用哪个版本的package?
- 如何确认某个版本的package和我的code是兼容的呢?
- go如何发现我clone的repo
有更加糟糕的情况出现,除非你clone了conf package所依赖的所有code,否则你无法使用conf package。这个问题被称为依赖传递。
如果是GOPATH模式,解决方案是使用go get将所有的依赖clone到gopath下。但这并不是一个完美的解决方案,因为对于所有的依赖,go get只能clone某个repo的master分支的最新代码。在刚刚开发的时候,clone master分支的最新代码可能是一个好的选择。你的project所依赖的code可能会独立的演化,一段时间之后,这部分代码可能和你的project的代码不再兼容。这是因为你的project并没有对依赖指定版本,或许每一个修改都可能是一个breaking change。
在module模式下,不再使用go get来clone所有依赖的方式。需要发现一种能够引用兼容版本依赖的工具。对于同一个依赖,能够指定不同的版本。 解决方案是用版本来维护一个直接或者间接的依赖来重用module文件。将任何版本的依赖看做是不可变的代码集合。这些不可变的代码集被称为是module。
集成解决方案
图3

一旦有了module,许多工程化的机会就会出现。
- 你可以为全世界的开发者提供module
- 可以为不同的版本管理系统提供代理服务
- 不管module编译多少次,从哪里获得的,谁提供的,你都能够验证这些代码真实性