
本文首先介绍模块化开发(Modular Programming)的一些基本概念,然后围绕Go1.11引入的模块系统介绍如何使用Go语言进行模块化开发,最后简要介绍模Go模块系统在Tendermint/Cosmos-SDK项目中的应用。
-
模块化开发介绍
1.1 包 vs 模块
1.2 语义版本号
1.3 Registry
1.4 清单文件
1.5 锁定文件
-
Go模块系统进化过程
2.1 GOPATH
2.2 vendor目录
2.3 vgo
2.4 Go1.11+
-
Go模块系统介绍
3.1 模块
3.2 Registry
3.3 语义版本号
3.4 清单文件
3.5 锁定文件
-
Tendermint/Cosmos-SDK应用
-
总结
1. 模块化开发介绍
模块化开发是编写复杂软件的有效方式,因此大部分现代编程语言都会内置相应语法特性来支持模块化开发。以Java和Go语言为例,这两种语言都内置了包(Package)的概念,可以把代码按包进行组织。对于简单的工程来说,只使用语言内置的标准库就可以了。然而对于复杂的工程,只有标准库肯定远远不够。因此,如何把代码发布出去让别人使用,以及如何使用别人的代码,就显得尤其重要。所以很自然的,在包的基础之上,衍生出了模块(Module)的概念。如果说包是代码组织的单位,那么模块就是代码发布的单位。多个相互关联的包可以构成一个模块,统一发布。以Java语言为例,Java很早就开始支持JAR(其实就是ZIP文件)文件,为后来Maven、Gradle等工具的流行提供了基础。Java9则更进一步,对模块和模块化开发提供了内置支持。
1.1 包 vs 模块
如前所述,似乎包和模块的概念已经很清晰了。然而并不是这样!现实是:不同的编程语言对于包和模块的叫法并没有达成一致。比如C++和C#语言就没有包的概念,取而代之的是命名空间(Namespace)。更让人郁闷的是,在一些编程语言里,包和模块的含义和我们前面描述的正好相反。换句话说,在有些编程语言里,包更大一些,由多个模块组成。为了描述清晰起见,在后文中我们统一使用包和模块这两个术语,二者中模块更大一些。下表列出了在一些主流的编程语言里包和模块的命名以及含义:
Language | Package | Module |
---|---|---|
Java (Maven) | package | jar |
Go1.11+ | package | module |
Rust | module/mod | package/crate |
Python | module (file) | package (directory) |
Node.js | module | package |
1.2 语义版本号
凡事不可一蹴而就。同理,任何软件也不可能一下子开发完毕并且不出任何bug,所以按版本一步一步迭代就非常必要。包和模块的概念已经澄清了,接下来要聊一聊模块的版本号。长久以来,各种系统的版本号可谓一片混乱,各式各样五花八门的版本号规则都有。不过随着时间的推移和开发经验的积累,现在大家普遍都认同语义版本号(Semantic Versioning,简称SemVer)。简单来说,语义版本号看起来是这样:MAJOR.MINOR.PATCH
。其中:
-
补丁号 如果新版本只是修复前一版本的bug,不引入任何其他变化,那么补丁号+1
-
小版本号 如果新版本有功能上的改进,但是改进完全向后兼容(Backwards Compatible),那么小版本号+1
-
大版本号 如果新版本有较为大的改进,并且不能完全向后兼容,那么大版本号+1
基本规则就这么简单,关于语义版本号的更多细节可以参考其当前的2.0规范。
1.3 Registry
要想便捷的使用模块系统,一个中央化的模块registry(不知道中文翻译成什么好,干脆不翻译了)必不可少。有了这个registry,任何人都可以把自己开发好的模块发布上去,也可以从上面下载其他人发布的模块。当然了,发布模块的时候,需要指定版本号(一般而言,模块不能覆盖已有版本,只能发布新版本)。下载模块的时候,也需要指定版本号。能够帮助用户解析并且从registry下载依赖的工具叫做模块管理器(Module Manager),或者包管理器(Package Manager),后文统称为模块管理器。下表列出了一些主流编程语言的模块管理器和registry:
Language | Module Manager | Module Registry |
---|---|---|
Java | Maven、Gradle | Maven Central、JCenter等 |
Rust | Cargo | https://crates.io/ |
Python | pip | Python Package Index |
Node.js | npm | https://www.npmjs.com/ |
1.4 清单文件
如果一个模块需要依赖其他的模块,那么就需要一个清单文件(Manifest)来列出这些模块,并描述这些模块的版本号等信息。大部分的模块管理器都可以自动处理传递性依赖(Transitive Dependency)。比如说模块A依赖模块B、模块B又依赖模块C,那么模块A的清单文件里只要指定模块B即可,不需要指定模块C。清单文件的格式因模块管理器的不同而异,常见的格式有JSON(比如npm的package.json)、XML(比如Maven的pom.xml)、TOML(比如Cargo的Cargo.toml)、YAML等。
1.5 锁定文件
如前所述,版本号使得模块可以渐进的开发和完善。另一个方面,版本号也使得模块可以稳定的构造。假设我们正在开发模块M,它依赖了模块A、B、C。在明确升级这些依赖之前,我们希望每次构建出的M都是一样的,并且在不同的机器上构建出的M也是一样的。这就是可重复构建(Reproducible Builds)。如果可以在清单文件中精确指定依赖的版本号,按说自然就可以实现可重复构建(假设模块不会随意覆盖或删除已发布的版本)。不过很多模块管理器都允许在清单文件中指定依赖的版本号范围,这就使得构建不一定可重复。以Rust Cargo为例,当我们在清单文件里指定某依赖的版本号为“1.2.3”时,实际指定的是“^1.2.3”。这样就允许Cargo在[1.2.3, 2.0.0)区间内下载该依赖的最新的版本。关于Cargo清单文件里依赖版本号的更多介绍请看这里。
为了解决这个问题,让构建变得可重复,支持版本号区间的模块管理工具一般会使用锁定文件(Lock File)来锁定依赖的版本号。除非明确要求包管理工具升级依赖(进而更新锁定文件),否则每次构建都会使用锁定文件中指定的依赖版本号。比如Cargo使用的锁定文件是Cargo.lock,npm使用的锁定文件是package-lock.json。
2. Go模块系统进化过程
以上简单介绍了模块化开发的一些基本概念,下面介绍Go语言模块系统的进化过程。
2.1 GOPATH
在Go 1.5之前,所有的源代码都挤在一个路径下,这个路径就是GOPATH。简单来说,GOPATH是个环境变量,指向一个目录,这个目录下面必须有src/子目录,里面放置Go源代码。这个目录也就是我们常说的工作空间(Workspace),Go的各种命令(比如get、build、test、install等)均以这个目录为基础进行工作。由于所有的源代码(包括本地的和第三方的)都在同一个src/目录下,那么就需要一种机制来划分src/目录,避免不同的项目因根目录名相同而产生冲突。Go采取的解决办法非常简单:对于开源项目,按照源代码托管网站的域名在src/目录下创建子目录;对于本地项目,必须自己保证项目的根目录名不和其他项目重复。一个典型的GOPATH目录结构看起来就像下面这样:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n107" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">$GOPATH/
|-bin/
|-pkg/
-src/
|-github.com/
| |-cosmos/
| | |-cosmos-sdk/
| | -gaia/
| -tendermint/
| |-go-amino/
| -tendermint/
-myprojs/
|-proj1/
-proj2/</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n109" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">+--------------------------------------------+
| $GOPATH |
| +-----------------+ go get | +--------+
| +--| src/github.com/ |<--------------| GitHub |
| go | +-----------------+ git clone | +--------+
| install | +-----------------+ |
| +->| bin/ | |
| +-----------------+ |
+--------------------------------------------+</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n114" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">+-----------------------------------------------+
| $GOPATH |
| +----------------------+ go get | +--------+
| +--| src/github.com/ <------------------| GitHub |
| | | |-my/proj/ | | +--------+
| | | |-vendor/ | download |
| | | -github.com/ <-------------------+
| go | +----------------------+ |
| install | +----------------------+ |
| +->| bin/ | |
| +----------------------+ |
+-----------------------------------------------+</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n119" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">+-----------------------------+
| myproj1/ |
| |- ... vgo get |
| -go.mod <----------------------+
+-----------------------------+ | +--------+
|----| GitHub |
+-----------------------------+ | +--------+
| $GOPATH/pkg/mod/ | |
| |-github.com/ | |
| |-cosmos/ | |
| |-cosmos-sdk@0.35.0/ <------+
+-----------------------------+</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n150" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">module github.com/cosmos/cosmos-sdk
require (
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/bgentry/speakeasy v0.1.0
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8
github.com/cosmos/ledger-cosmos-go v0.10.3
...
)</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n155" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;"> git tag --sort=-taggerdate
v0.36.0-rc1
v0.35.0
v0.34.7
v0.34.6
v0.34.5
v0.34.4
v0.34.3
v0.34.2
v0.34.1
v0.34.0
...</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n159" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n163" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">module github.com/cosmos/cosmos-sdk
require (
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/bgentry/speakeasy v0.1.0
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8
github.com/cosmos/ledger-cosmos-go v0.10.3
github.com/fortytw2/leaktest v1.3.0 // indirect
github.com/gogo/protobuf v1.2.1
github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129
github.com/golang/protobuf v1.3.0
github.com/gorilla/mux v1.7.0
github.com/gorilla/websocket v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.6
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v0.9.2 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.2.0 // indirect
github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 // indirect
github.com/rakyll/statik v0.1.4
github.com/spf13/afero v1.2.1 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2
github.com/stretchr/testify v1.3.0
github.com/tendermint/btcd v0.1.1
github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/iavl v0.12.3-0.20190712145259-c834d3192b52
github.com/tendermint/tendermint v0.32.1
google.golang.org/grpc v1.19.0 // indirect
gopkg.in/yaml.v2 v2.2.2
)</pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n171" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
... </pre>
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n177" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-color: rgb(51, 51, 51); font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">git log --all --full-history --oneline -- glide.yaml
git log --all --full-history --oneline -- Gopkg.toml
git log --all --full-history --oneline -- go.mod</pre>
Language | Package Manager | SemVer | Manifest | Lockfile | Registry |
---|---|---|---|---|---|
Java | Maven | pom.xml | Maven Central | ||
Rust | Cargo | yes | Cargo.toml | Cargo.lock | https://crates.io/ |
Node.js | npm | yes | package.json | package-lock.json | https://www.npmjs.com/ |
Go 1.11+ | builtin | yes | go.mod | go.sum | GitHub、GitLab、Bitbucket等 |
Python | pip | Python Package Index |
本文由CoinEx Chain团队Chase写作,转载无需授权。 |
网友评论