(Photo From Go Brand Book)
已經有不少文章介紹如何用 modules 引入外部 package,但一般公司更常見的狀況是專案內需要引入自己寫的 internal package,本文講解 go modules 基本觀念以及利用實例說明如何安排專案結構。
Update:
- 2018.10.17 新增專文介紹: 如何透過 Go Modules 引入 insecure private 專案
- 2018.10.05 vscode-go v0.6.90 正式支援 modules
- 2018.09.27 新增 vscode-go beta資訊,已可支援 autocomplete 以及 definition
- 2018.09.21 新增 gocode autocomplete hotfix
- 2018.09.12 更新 vscode-go 對於 modules 相關支援現況
modules 重點摘要
- 主要目的是解除
$GOPATH
的依賴,同時開發多專案時無需頻繁切換 vendor 目錄,而 dep 僅為過渡型解決方案。 - 目前屬於實驗功能,可用環境變數
GO111MODULE
控制行為:- off: go command 不使用 modules 功能,而是沿用舊有的 GOPATH 模式
- on: 強制使用 modules 功能,只根據 go.mod 下載 dependency 而完全忽略 GOPATH 以及 vendor 目錄
- auto: Golang 1.11 預設值,go command 根據當前工作目錄狀態決定是否啟用 modules 功能,滿足任一條件時才啟動此功能:
- 當前目錄位於
GOPATH/src
之外並且包含go.mod
文件 - 當前目錄位於包含
go.mod
文件的目錄下
- 當前目錄位於
- modules 下載的 package 預設放在
$GOPATH/pkg
目錄下,允許同 package 多種版本並存。 - go build, go get, go test 等指令都會影響
go.mod
,請多留意。
modules 基本用法
專案結構
myproj ├── main.go └── pkg └── myapi └── data └── api.go
創建 modules
本例用 Gin 框架來實作兩個 restful API,handler實作分別屬於 main
以及 pkg/myapi/data
兩個 internal package。
進入 myproj 目錄,並用內建指令建立 modules
$ go mod init myproj go: creating new go.mod: module myproj
此時自動產生 go.mod
檔案,內容只有一行
module myproj
我們來看看兩支go檔內容:
// main.go
package main
import (
dataapi "myproj/pkg/myapi/data"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/health", GetHealthHandler)
router.GET("/health-dataapi", dataapi.GetDataAPIHealthHandler)
s := &http.Server{
Addr: ":8000",
Handler: router,
}
s.ListenAndServe()
}
// GetHealthHandler - GET /health to expose service health
func GetHealthHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "Service is alive!",
})
}
// pkg/myapi/data/api.go
package data
import (
"net/http"
"github.com/gin-gonic/gin"
)
// GetDataAPIHealthHandler GET /health-dataapi to expose heathy check result of data API
func GetDataAPIHealthHandler(c *gin.Context) {
// do something to check heathy of data API
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "Data API is alive",
})
}
可以發現 main.go
裡面宣告使用 internal package 的方法跟以前很不一樣,由於 go.mod
會掃描同工作目錄下所有 package 並且變更引入方法,必須將 myproj 當成路徑的前綴,也就是需要寫成 import myproj/pkg/myapi/data/
,以往 GOPATH/dep 模式允許的 import ./pkg/myapi/data
已經失效,詳情可見此 issue。
此時用老朋友 go build 創建執行檔,並輸出到 bin/main
$ go build -o bin/main main.go
可發現此時 go.mod
內容被拓展為:
module myproj
require (
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
github.com/gin-gonic/gin v1.3.0
github.com/golang/protobuf v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/ugorji/go/codec v0.0.0-20180831062425-e253f1f20942 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
)
go module
拉取 package 的原則是先拉最新的 release tag,若無tag則拉最新的commit,詳見 Modules官方介紹。 go 會自動生成一個 go.sum
檔案記錄整個 dependency tree:
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/ugorji/go/codec v0.0.0-20180831062425-e253f1f20942 h1:CZORS/4d6i+5FKSAtbRIjlElV2BAFYv/bokcaEVUimQ=
github.com/ugorji/go/codec v0.0.0-20180831062425-e253f1f20942/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
有寫過 node 的人應該發現 go.mod/go.sum
的關係跟 package.json/package-lock.json
類似,前者定義 dependency root,後者將關係展開。
最後執行 bin/main
可看到 Gin 貼心列出 handler 從屬的 package。
$ bin/main [GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /health --> main.GetHealthHandler (3 handlers) [GIN-debug] GET /health-dataapi --> myproj/pkg/myapi/data.GetDataAPIHealthHandler (3 handlers) Listening port 8080
到這邊一個 go module 就完成了,以往需要將 vendor 目錄一起提交到 git 以免 CI/CD 流程拉到錯誤的外部 package,有了 modules 加上 build cache 以後,在 build server 上面跑起來體感速度比 1.10 時代還快。
如果你習慣專案裡要有 vendor 目錄,可以執行以下指令產生 vendor 目錄:
$ go mod vendor go: finding golang.org/x/sys/unix latest go: finding golang.org/x/sys latest go: downloading golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 go: finding github.com/modern-go/concurrent latest
modules 大致使用方法講到這邊,由於這是個實驗性的功能,不建議大規模把公司的專案搬過去,畢竟軟體工程界的鐵則就是不要用 .0
的版本 :p
VS code 開發套件 issues 列表
Golang 1.10 使用dep以後不強制要求 GOPATH 設定 vendor 目錄,依本文的專案結構會發現 gocode 無法正確抓到 internal package,解決方法參考本文 轉換 nsf/gocode -> mdempsky/gocode 套件。可以追蹤下列連結了解最新支援進度:
- vscode-go
- gocode
- gopkgs
參考資料