The Go gopher was designed by Renée French.The gopher stickers was made by Takuya Ueda.Licensed under the Creative Commons 3.0 Attributions license.
Go1.8 forGoogle App Engine@GDG DevFest Tokyo 20172017年10月09日(月)ハッシュタグ:
#DevFest17 #DevFest_room1
自己紹介
上田拓也@tenntenn
2
所属
コミュニティ活動
&
Go ビギナーGo Conference
ソウゾウ エキスパートチーム
技術をアウトプットするところに技術は集まる
■ エキスパートチームとは?● 50%以上の時間を技術コミュニティへの貢献に充てる
■ エキスパートチームの役割● 社内に新しい技術を取り取り込む● 社外のコミュニティなどを通じて社会へ還元する
■ エキスパートチームの活動● カンファレンス・勉強会の開催/運営● 対外的な講演活動● 執筆、雑誌への寄稿、インタビュー● 社内外での担当技術の普及推進
3
@tenntenn担当:Go・GCP
@mhidaka担当:Android
メンバー
アジェンダ
● Google App Engine● Go1.8 for Google App Engine● Go1.8対応
● まとめ
4
Google App Engine
5
Google App Engineとは
■ Google が提供するPaaS● 高いスケーラビリティ● メンテナンスコストが低い
■ スタンダード環境とフレキシブル環境● スタンダード環境
○ 従来からあるGAEの環境、SEとも○ Go、Java8、Python 2.7、PHPが使える○ Goはインスタンスの起動が恐ろしく早い
● フレキシブル環境○ 旧MVMs、FEとも○ Go、Java8、Python 2.7/3.4、Node.js、Ruby
6
Hello, world!8
package myappimport "fmt"import "net/http"
func init() {http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "Hello, world!")
}
main関数ではなくinit関数
net/httpパッケージを使って
HTTPリクエストを処理
HTTPハンドラ
app.yaml9
runtime: goapi_version: go1
handlers:- url: /.* script: _go_app
■ アプリケーションの設定ファイル
すべてのリクエストをGoで処理
ルーティングやログインの有無などを設定することができる
Goの各バージョンの対応状況
■ Go1.6● 現在のデフォルトのバージョン● 現在のGoの最新は1.9であるためかなり離れている
■ Go1.8(ベータ)● ベータとして対応中● 概ねバグはなくなってきている● Cloud Datastoreなどのライブラリ類がまだ未対応● Goのひとつ前のバージョン
10
Goのバージョンは半年に1回アップデートされる
Go1.8 for Google App Engine
11
Go1.6 → 1.8で新しくなったこと12
■ Go1.6 → Go1.7● contextパッケージが標準になった● サブテストができるようになった
■ Go1.7 → Go1.8● sort.Sliceが導入され、型を作る必要がなくなった● pluginパッケージが導入
コンテキスト
■ Contextインタフェース● Google Cloud Platformの各APIを使うために必要● Goの標準としては主にゴルーチンのキャンセル処理などに使用
13
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{}}
コンテキストとキャンセル処理
■ コンテキストの主な目的はキャンセル処理● ゴルーチンをまたいだ処理のキャンセルに使う
14
bc := context.Background()t := 50*time.Millisecondctx, cancel := context.WithTimeout(bc, tc)defer cancel()select {case <-time.After(1 * time.Second):
fmt.Println("overslept")case <-ctx.Done():
fmt.Println(ctx.Err())}
コンテキストに値を持たせる
■ WithValueで値を持たせる● 例:キャッシュを充てない
15
type withoutCacheKey struct{}func WithoutCache(c context.Context) context.Context {
if IsIgnoredCache(c) {return c
}return context.WithValue(c, withoutCacheKey{}, struct{}{})
}func IsIgnoredCache(c context.Context) bool {
return c.Value(withoutCacheKey{}) != nil}
Google App Engineとコンテキスト
■ 例:Datastoreからデータの取得
16
type Person struct {ID int64 `datastore:"-"`Name string `datastore:"name"`Age int `datastore:"age"`
}const k = "Person" // Kind名key := datastore.NewKey(ctx, k,"",100, nil)var p Personif err := datastore.Get(ctx, key, &p); err != nil { /*エラー処理*/}p.ID = key.IntID()
第1引数でコンテキストを指定
コンテキストの歴史
■ appengine.Context● 初代コンテキスト● Goの標準とは関係ない
■ golang.org/x/net/context.Context● Go1.6までのGo準標準のコンテキスト● Google App Engine(Go1.6の場合)で使用されている
○ google.golang.org/appengine■ フレキシブル環境対応がされたラッパー
■ context.Context● Go1.7からのGo標準のコンテキスト● 現在Google App EngineはGo1.6なので使えない● ベータ版としてであればGo1.8が使える
17時
代の
流れ
サブテスト
■ 子テストを実行するしくみ● Go1.7から導入された
func TestAB(t *testing.T) {t.Run("A", func(t *testing.T) { t.Error("error") })t.Run("B", func(t *testing.T) { t.Error("error") })
}
go test -v sample -run TestAB/A=== RUN TestAB=== RUN TestAB/A--- FAIL: TestAB (0.00s) --- FAIL: TestAB/A (0.00s) sample_test.go:10: errorFAILexit status 1FAIL sample 0.007s
サブテストを指定して実行
18
テーブル駆動テスト
■ テスト対象のデータを羅列してテストするvar flagtests = []struct {
in stringout string
}{{"%a", "[%a]"}, {"%-a", "[%-a]"}, {"%+a", "[%+a]"},{"%#a", "[%#a]"}, {"% a", "[% a]"},
}func TestFlagParser(t *testing.T) {
var flagprinter flagPrinterfor _, tt := range flagtests {
s := Sprintf(tt.in, &flagprinter)if s != tt.out {
t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)}
}}
19
サブテストとテーブル駆動テスト
func TestIsOdd(t *testing.T) {cases := []*struct {name string; input int; expected bool}{
{name: "+odd", input: 5, expected: true},{name: "+even", input: 6, expected: false},{name: "-odd", input: -5, expected: true},{name: "-even", input: -6, expected: false},{name: "zero", input: 0, expected: false},
}
for _, c := range cases {t.Run(c.name, func(t *testing.T) {
if actual := IsOdd(c.input); c.expected != actual {t.Errorf(
"want IsOdd(%d) = %v, got %v",c.input, c.expected, actual)}})
}}
20
サブテストとテーブル駆動テスト
■ 利点● 落ちた箇所が分かりやすい
○ テストケースの名前が表示される● 特定のサブテストだけ実行できる
○ テストケースが大量な場合分かりやすい
21
Go1.7までのソート22
type byAge []Person
func (a byAge) Less(i, j int) bool { return a[i].Age < a[j].Age }func (a byAge) Len() int { return len(a) }func (a byAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func main() { people := []Person{ {"Gopher", 7}, {"Alice", 55}, {"Vera", 24} } sort.Sort(byAge(people))}
■ sort.Interfaceを実装する必要があった
sort.Sliceの導入23
func main() {people := []Person{{"Gopher", 7}, {"Alice", 55}, {"Vera", 24}}sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age})
}
■ 型を作る必要がなくなった
pluginパッケージ
■ プラグイン機構の導入● プラグインモードとしてビルドしたものを動的に読み込める● cgoを使用するため、Google App Engineでは使えない
○ ビルドはできるがエラーが発生し何もおこならない
24
$ go build -buildmode=plugin
Go1.8対応
25
SDKのアップデート26
■ gcloudコマンドの場合● components updateでアップデートを掛ける
■ Google App Engine SDKを落としている場合
● ダウンロードページから再度落としてくる
$ gcloud components update
Go1.8に対応するには
■ 設定の変更● app.yamlを修正
■ コードの修正● golang.org/x/net/contextから標準のcontextに変更● ライブラリが未対応の部分を修正
○ 型に互換のない場所
27
app.yamlの書き換え
■ api_versionをgo1.8に書き換える
28
runtime: goapi_version: go1.8
handlers:- url: /.* script: _go_app
使用されるgoappコマンドやGOROOTがスイッチされる
コンテキストの互換29
■ インタフェースはメソッドが同じであれば互換がある● 標準のcontext.Contextとx/net/context.Contextは
同じメソッドリスト● インタフェース変数の代入はメソッドリストの一致のみの判定
○ ダックタイピング● 型が別でも代入できる
type A interface { String() string }type B interface { String() string }var _ A = B(nil)
例
GAE/Go1.6までコンテキストの管理法30
■ リクエストとコンテキストをマップで管理
var ctxs = make(map[*http.Request]context)
func NewContext(r *http.Request) context {ctxsMu.Lock()defer ctxsMu.Unlock()c := ctxs[r]if c == nil {/* 省略 */}return c
}
goroot/src/appengine_internal/api_dev.go
Request.WithContext問題
■ Request.WithContextでリクエストが書き換えられる● リクエストが書き換えられると対応するコンテキストが取れない
○ キーにヒットしなくなる● gorilla/muxやlion v2など問題が発生する
31
func (r *Request) WithContext(ctx context.Context) *Request { if ctx == nil { panic("nil context") } r2 := new(Request) *r2 = *r r2.ctx = ctx if r.URL != nil { /* 省略 */ } return r2 }
Request.WithContextの実装
フィールドをコピーしている!
GAE/Go1.8のコンテキストの管理法32
■ Contextの中にApp Engineのコンテキストを入れる● リクエストをキーに使っていない● Request.WithContextされてもコンテキストはコピーされる
const ContextKey keyType = "App Engine context"func NewContext(req *http.Request) context {
c := req.Context().Value(ContextKey)if c == nil { /* 省略 */}return c.(context)
}
goroot-1.8/src/appengine_internal/api_go18.go
コンテキストの置き換え33
■ go tool fixコマンドを使う● Goのバージョン間のマイグレーションを行うコマンド● Go1.0以前に活躍● x/net/context.Contextからcontext.Contextに置き換える● goappコマンドにはない
$ go tool fix -force=context main.go
import文をsedしても対して変わらない
型の不一致の問題
■ 型が合わないケースが存在する● コンテキストを含むコンポジット型
○ マップやスライス● コンテキストを引数や戻り値に取るクロージャ
34
err := datastore.RunInTransaction(ctx, func(ctx context.Context) error {key := datastore.NewKey(ctx, "Counter", "singleton", 0, nil)var cnt struct { Count `datastore:"count"` }err := datastore.Get(ctx, key, &cnt)if err != nil && err != datastore.ErrNoSuchEntity { return err}cnt.Count++if _, err := datastore.Put(ctx, key, &cnt); err != nil { return err }
}, nil)
datastore.RunInTransactionの例
型の不一致の解決
■ ラッパーを作る● ライブラリに合わせてクロージャの型を変換する
35
import xcontext "golang.org/x/net/context"func RunInTransaction(c context.Context, f func(c context.Context) error) error {
return datastore.RunInTransaction(c, func(xc xcontext.Context) error {return f(xc)
})}
datastore.RunInTransactionのラッパー
Go1.9にも期待
■ 新機能● 型エイリアス● t.Helper()
○ テストのヘルパー関数がより便利に● sync.Map型● math/bitsパッケージ
36
Go1.8を乗り越えればきっと!
まとめ
■ Go1.8にはベータとして対応● 大きな問題は解決済み● サブテストやsort.Sliceが使えるのは大きい● Goはバージョンアップごとにパフォーマンスが改善している
■ 本番利用にはまだ早そう● コンテキストの問題が残っている● 正式版に向けてライブラリ等の改修がされる?● メルカリ カウルではChat Opsから導入中● Go1.9はよ
37
Thank you!
twitter: @tenntenn Qiita: tenntennconnpass: tenntenn
38