Go 部落格
Go 模組:v2 及更新版本
簡介
這篇文章是系列文章的第 4 篇。
- 第 1 部分 — 使用 Go 模組
- 第 2 部分 — 移轉至 Go 模組
- 第 3 部分 — 發布 Go 模組
- 第 4 部分 — Go 模組:v2 及更新版本 (本篇文章)
- 第 5 部分 — 保持模組相容性
注意:有關開發模組的文件,請參閱 開發和發布模組。
隨著成功專案逐漸成熟,並加入新的需求,過往的功能和設計決策可能會失去意義。開發人員可能會希望整合從中學到的經驗,包括移除已棄用的函數、重新命名類型或將複雜套件分割成可管理的區塊。此類變更需要下游使用者努力將其程式碼移轉至新的 API,因此務必審慎衡量好處是否大於成本後,才能進行變更。
對於仍處於實驗階段的專案——主版本為 v0
——使用者預期偶爾會發生重大變更。對於宣告為穩定的專案——主版本為 v1
或更高——重大變更必須在新的主版本中執行。這篇文章探討主版本的語意、如何建立並發布新的主版本,以及如何維護模組的多個主版本。
主版本與模組路徑
模組在 Go 中將一項重要原則正式化,即 匯入相容性規則
If an old package and a new package have the same import path,
the new package must be backwards compatible with the old package.
根據定義,一個套件的新主版本不與先前版本向後相容。這意味著一個模組的新主版本必須有不同於先前版本的模組路徑。從 v2
開始,主版本必須出現在模組路徑的結尾(宣告於 go.mod
檔案中的 module
陳述)。例如,當 github.com/googleapis/gax-go
模組的作者開發 v2
時,他們使用了新的模組路徑 github.com/googleapis/gax-go/v2
。想要使用 v2
的使用者必須將他們的套件匯入和模組需求改成 github.com/googleapis/gax-go/v2
。
需要主版本字尾是 Go 模組有別於大多數其他相依性管理系統的方法之一。需要字尾來解決 菱形相依性問題。在 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
這樣的斜線字尾。
主版本策略
建議的策略是使用命名為依據主版本字尾的目錄來開發 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 及之後的版本
這篇文章使用 github.com/googleapis/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/googleapis/gax-go
的 v2
版本,我們會建立一個新的 v2/
目錄,並將套件複製到該目錄中。
$ mkdir v2
$ cp -v *.go v2
'call_option.go' -> 'v2/call_option.go'
'gax.go' -> 'v2/gax.go'
'header.go' -> 'v2/header.go'
'invoke.go' -> 'v2/invoke.go'
$
現在,讓我們建立一個 v2 go.mod
檔案,方法是複製目前的 go.mod
檔案,然後在模組路徑中加入 /v2
字尾
$ 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
等)。
結論
主要版本變更會產生開發和維護的負擔,而且需要下游使用者投資以進行移轉。在專案越大時,這些負擔會越大。只有在找出令人信服的理由後,才應變更主要版本。一旦找出一個令人信服的暫停使用變更理由,我們建議在 master 分支中開發多個主要版本,這是因為它與更多不同種類的現有工具相容。
對 v1+
模組的暫停使用變更應始終在一個新的 vN+1
模組中發生。當一個新的模組釋出時,這表示維護人員和需要移轉到新套件的使用者會有額外的負擔。因此,維護人員應在發布穩定版本之前驗證自己的 API,並審慎考慮暫停使用變更是否在 v1
之後真的必要。
下一篇文章:Go 滿 10 歲了
上一篇文章:使用 Go 1.13 中的錯誤
部落格索引