1
/
5

Go 1.11 の modules・vgo を試す - 実際に使っていく上で考えないといけないこと #golang

日本時間の 8/25 早朝,Go 1.11 がリリースされました 🎉 🎉

https://go.googlesource.com/go/+/go1.11

Go 1.11 には WebAssembly 対応を始めとするド派手な新機能からコアライブラリ・ツールチェインの細かな改善まで,いろいろな変更が入っています.個人的には golang.org/x/tools/go/packages が気になっていました../... がうまく扱える API かな?とか思ってたけど,内部で普通に go list 叩いちゃってるのがかわいい.

Go 1.11 Release Notes - The Go Programming Language
The latest Go release, version 1.11, arrives six months after Go 1.10. Most of its changes are in the implementation of the toolchain, runtime, and libraries. As always, the release maintains the Go 1 promise of compatibility. We expect almost all Go prog
https://golang.org/doc/go1.11

そして,Go 1.11 には現実世界を生きる Gopher にとっては待望ともいえる機能が Experimental ですが入ってます.それが Modules です.今年2月に提案され話題となった Go & Versioning およびそのプロトタイプ実装であった vgogo コマンドに取り込まれ, Modules という名前でリリースされました.

この記事では,この Modules を実際のプロジェクトに導入しようと試みたうえで,実際の開発フローに取り入れていくために何が優れており・何が足りないかを考えてみたいと思います.

現状の依存管理ツール - dep

dep は Go が公式に "実験的に" 作っている依存管理ツールです.glide など以前から存在していたツールからの migration の仕組みも持っており,dep への移行を促すプロジェクトも多いです.

dep · Dependency management for Go
Dependency management for Go
https://golang.github.io/dep/

dep やそれ以前の Go における dependency management の歴史・変遷も面白いのですが,そのあたりは Go & Versioning やその翻訳記事・vgo 関連のトークスライドなどにまとまっているため今回は深くは触れません.

Wantedly における Go プロジェクトの依存管理は,去年の10月頃に glide から dep に移行しました.マイクロサービスもしくは CLI での利用が主ですので,そこまで大規模な事例はないですが,1年ほど使い続けて感じた dep の「難しかったところ」をあげてみます.

  • Docker による開発環境との相性がよくない
    • Wantedly によくあるプロジェクトでは,コマンド一発で docker 上でアプリケーションが起動するようになっている
    • Docker 上の開発環境では,Docker コンテナが死んだときに依存を入れ直さなくていいように, vendor ディレクトリはコンテナとは別 volume にすることが多い
    • 一方で,depvendor への書き込みが失敗したときにロールバックできるように,dep ensure
      時に既存の vendor ディレクトリを vendor.orig にリネームする
    • このとき,ディレクトリの owner がおかしなことになって dep ensure が失敗してしまう
    • (Go はどこでも動くので, Docker 上での開発をやめても問題ないといえばないけど…)
  • 標準ツールではないので,CI や本番ビルドで使うのが面倒
    • curl -L -s https://.../dep-linux-amd64 -o ./bin/dep chmod +x ./bin/dep をマイクロサービス増やすたびに3回書く(開発用 Dockerfile, 本番用 Dockerfile,CI)
    • dep は決定版というもののでもないので,dep ensure -vendor-onlydep ensure -add <pkg> などコマンドを覚えるのがしんどい
  • executable なツールの依存をうまく扱えない
    • mockgenprotoc-gen-* など
    • とりあえず required にいれといて,make setupgo build -o bin/bar github.com/foo/bar するような運用でカバーしてる

(※ いっぱい書きましたが, dep は存在しなかった時代を考えたくないくらいには良いツールです)

特に 普段 Go を触らない人たちにとっては,よくわかんない感じに dep ensure が落ちる・コマンド体型が難しい(他ではあまり見ない)など,Developer Experience 的にしんどい場面が多々ありました.

Modules (vgo) を試す

golang/go
go - The Go programming language
https://github.com/golang/go/wiki/Modules

試すための準備

  • GO111MODULE という環境変数を on にします
    • direnv 等で,作業ディレクトリ中は常に on になるようにしておくと便利です
  • go を 1.11 にします

dep -> Modules への移行

これは dep への移行時と同じく,Gopkg.lock を読んでいい感じにする機能が入っています.なので,基本的には go mod init をしたあとに普通にビルド(go build)するだけです.

$ go mod init
go: creating new go.mod: module github.com/wantedly/sugoi-api
go: copying requirements from Gopkg.lock

$ grapi build  # go build
go: finding github.com/spf13/cobra v0.0.3
go: finding github.com/philhofer/fwd v1.0.0
go: finding github.com/pkg/errors v0.8.0
...

実プロジェクトで試したところ,fork したリポジトリを参照している場合(Gopkg.tomlsource を指定した場合)にビルドに失敗しました.ここまでは見てくれないようなので,いい感じにコミットハッシュを書き換えます.

 package github.com/wantedly/sugoi-api

 require (
 	// snip.

 	// パッケージは fork 元だが,コミットハッシュは fork 後のやつ
 	github.com/basvanbeek/ocsql v0.0.0-20180807130626-1b1c0827cbf1

 	// snip.
 )

+// fork 元 => fork 後
+replace github.com/basvanbeek/ocsql v0.0.0-20180807130626-d946a62502e3 => github.com/bgpat/ocsql v0.0.0-20180807130626-1b1c0827cbf1

これで再度ビルドすると,正しく実行できるバイナリが生成されました.

今回踏まなかっただけで,dep のマイナー機能を使っている場合は無視される可能性があります.そのときは気合でがんばりましょう.

依存パッケージのダウンロード

dep では dep ensure -vendor-only で依存パッケージのダウンロードをしていました.他の言語だとRuby の bundle install や Node.js の npm install もしくは yarn に相当します.

一方 Modules が有効になった環境であれば,go buildgo test 時などに必要なパッケージを勝手にダウンロードしてくれます.

$ go test ./...
go: finding github.com/google/go-cmp v0.2.0
go: downloading github.com/google/go-cmp v0.2.0
?       github.com/wantedly/sugoi-api/cmd/server       [no test files]
...

便利.

明示的な依存パッケージのダウンロードは go mod download で可能です.

$ go mod download
go: finding github.com/google/go-cmp v0.2.0

依存パッケージの追加

Modules では,go.mod が存在するディレクトリで go get をするだけで適切にバージョン管理されます.

$ go get github.com/golang/mock

$ git diff
diff --git a/go.mod b/go.mod
index c405511..edc0042 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,7 @@ require (
        github.com/go-sql-driver/mysql v1.4.0 // indirect
        github.com/gogo/protobuf v1.0.0
        github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
+       github.com/golang/mock v1.1.1 // indirect
        github.com/golang/protobuf v0.0.0-20180712042225-70b3af33377e
        github.com/gomodule/redigo v2.0.0+incompatible
        github.com/google/go-cmp v0.2.0
diff --git a/go.sum b/go.sum
index 686e134..eeeeaa6 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20180712042225-70b3af33377e h1:+juDoIDv8ng1gAwO/VZdSNzDb1HiSE6Bt+4a4g0nVWU=
 github.com/golang/protobuf v0.0.0-20180712042225-70b3af33377e/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=

便利.

executable なツールの依存管理

これに関してはあまり状況は変わってないです.議論自体はされていて,とりあえずのBest practice 的なものが紹介されています.下記のようなファイルを tools.go として置いておいて,go.mod に管理させることで既存の go mod を使ったフローに乗れるそうです.

// +build tools

package tools

import (
        _ "golang.org/x/tools/cmd/stringer"
)

Modules で管理されてるディレクトリでは go buildgo install はその go.mod のコンテキストで実行されるので,正しくバージョン管理されたバイナリを作ることができます.

ただ,流石に tools.go 書いて go build -o bin/bar github.com/foo/bar して…を毎回やるのはしんどいです.そこで,gex というツールを書いてみました.

# `go get` して `go.mod` に反映し,`tools.go` にも書く
$ gex --add golang.org/x/tools/cmd/stringer

# 必要に応じて `go build` され,`$PWD/bin` 以下にバイナリを吐きそれを実行する
$ gex stringer

Modules 環境じゃなくても使える気がするので,executable なツールのバージョン管理に困っていた人は試してみてください.

izumin5210/gex
gex - The implementation of clarify best practice for tool dependencies.
https://github.com/izumin5210/gex


アプリケーションエンジニアとしての所感

Modules を使ってみて最も良いと思ったのは,やはり go get をするだけで依存パッケージを追加できるという最もシンプルなフローに戻ってこれることでした.また,git pull 直後など足りないパッケージがあれば勝手に落としてきてくれるのもかなり好印象です.Go にバンドルされると CI のセットアップ等が超楽になるのも嬉しい!

一方で,dep からの移行をいますぐやるかというとかなり悩ましいです.Modules は未だ Experimental なので何が起きるかわからないというのが大きな問題です.また,dep などからの移行パスが完全に用意されているわけではないので,そこそこの規模のプロジェクトではある程度歯を食いしばる覚悟が必要かもしれません.

(個人的に)期待していた executable なツールの管理ですが,これはまだまだ検討段階といった感じでした.Best practice にそったツールも作ってみたので,いろいろ試して本家にフィードバックできたらと考えています.

全体的にかなり体験がいいので,自分が Owner になっているマイクロサービス等で小さく master に投入して試していくのはやってみようかと思います.

その他,考えること

CLI・ツール開発者にとっては,これまで以上に「go get されてもちゃんと動くこと」を強く意識する必要がありそうです.よくある「ビルド時に ldflags でバージョン番号を埋め込む」方式などは,go get されると悲しい感じになります.一方で,今後は go get がタグをサポートすることになるので,そういう意味では開発は楽になるかもしれません.

ライブラリ開発者にとっては,…ちゃんと Semantic Versioning に従おうな というアタリマエのことが最重要です.breaking change はなるべく避け,避けきれないときは major version 上げましょう.あと,自分がメンテしてるライブラリに go.mod を置くのを早めにやったほうが喜ばれそうです.

さいごに

この記事では,Go 1.11 で導入された Modules を実際に導入し(ようとし)てみてぶつかった問題や考えるべきことについて述べました.記事中に何度も書いてますが,Modules は Experimental な機能です.導入は自己責任でお願いします(?).

しかし,自分たちがたくさん使ってフィードバックしていくことで,本リリースされたときによりよいもの・欲しいものが出やすくなると思います.用法用量をまもって正しく導入しようとしてみましょう.

Wantedly, Inc.では一緒に働く仲間を募集しています
44 いいね!
44 いいね!

同じタグの記事

今週のランキング

泉 将之さんにいいねを伝えよう
泉 将之さんや会社があなたに興味を持つかも