38
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 for Google App Engine @GDG DevFest Tokyo 2017 20171009() ハッシュタグ: #DevFest17 #DevFest_room1

Go1.8 for Google App Engine

Embed Size (px)

Citation preview

Page 1: Go1.8 for Google App Engine

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

Page 2: Go1.8 for Google App Engine

自己紹介

上田拓也@tenntenn

2

所属

コミュニティ活動

&

Go ビギナーGo Conference

Page 3: Go1.8 for Google App Engine

ソウゾウ エキスパートチーム

技術をアウトプットするところに技術は集まる

■ エキスパートチームとは?● 50%以上の時間を技術コミュニティへの貢献に充てる

■ エキスパートチームの役割● 社内に新しい技術を取り取り込む● 社外のコミュニティなどを通じて社会へ還元する

■ エキスパートチームの活動● カンファレンス・勉強会の開催/運営● 対外的な講演活動● 執筆、雑誌への寄稿、インタビュー● 社内外での担当技術の普及推進

3

@tenntenn担当:Go・GCP

@mhidaka担当:Android

メンバー

Page 4: Go1.8 for Google App Engine

アジェンダ

● Google App Engine● Go1.8 for Google App Engine● Go1.8対応

● まとめ

4

Page 5: Go1.8 for Google App Engine

Google App Engine

5

Page 6: Go1.8 for Google App Engine

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

Page 7: Go1.8 for Google App Engine

メルカリ カウルでも採用7

https://goo.gl/ZzHDoW

Page 8: Go1.8 for Google App Engine

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ハンドラ

Page 9: Go1.8 for Google App Engine

app.yaml9

runtime: goapi_version: go1

handlers:- url: /.* script: _go_app

■ アプリケーションの設定ファイル

すべてのリクエストをGoで処理

ルーティングやログインの有無などを設定することができる

Page 10: Go1.8 for Google App Engine

Goの各バージョンの対応状況

■ Go1.6● 現在のデフォルトのバージョン● 現在のGoの最新は1.9であるためかなり離れている

■ Go1.8(ベータ)● ベータとして対応中● 概ねバグはなくなってきている● Cloud Datastoreなどのライブラリ類がまだ未対応● Goのひとつ前のバージョン

10

Goのバージョンは半年に1回アップデートされる

Page 11: Go1.8 for Google App Engine

Go1.8 for Google App Engine

11

Page 12: Go1.8 for Google App Engine

Go1.6 → 1.8で新しくなったこと12

■ Go1.6 → Go1.7● contextパッケージが標準になった● サブテストができるようになった

■ Go1.7 → Go1.8● sort.Sliceが導入され、型を作る必要がなくなった● pluginパッケージが導入

Page 13: Go1.8 for Google App Engine

コンテキスト

■ 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{}}

Page 14: Go1.8 for Google App Engine

コンテキストとキャンセル処理

■ コンテキストの主な目的はキャンセル処理● ゴルーチンをまたいだ処理のキャンセルに使う

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())}

Page 15: Go1.8 for Google App Engine

コンテキストに値を持たせる

■ 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}

Page 16: Go1.8 for Google App Engine

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引数でコンテキストを指定

Page 17: Go1.8 for Google App Engine

コンテキストの歴史

■ 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時

代の

流れ

Page 18: Go1.8 for Google App Engine

サブテスト

■ 子テストを実行するしくみ● 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

Page 19: Go1.8 for Google App Engine

テーブル駆動テスト

■ テスト対象のデータを羅列してテストする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

Page 20: Go1.8 for Google App Engine

サブテストとテーブル駆動テスト

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

Page 21: Go1.8 for Google App Engine

サブテストとテーブル駆動テスト

■ 利点● 落ちた箇所が分かりやすい

○ テストケースの名前が表示される● 特定のサブテストだけ実行できる

○ テストケースが大量な場合分かりやすい

21

Page 22: Go1.8 for Google App Engine

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を実装する必要があった

Page 23: Go1.8 for Google App Engine

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})

}

■ 型を作る必要がなくなった

Page 24: Go1.8 for Google App Engine

pluginパッケージ

■ プラグイン機構の導入● プラグインモードとしてビルドしたものを動的に読み込める● cgoを使用するため、Google App Engineでは使えない

○ ビルドはできるがエラーが発生し何もおこならない

24

$ go build -buildmode=plugin

Page 25: Go1.8 for Google App Engine

Go1.8対応

25

Page 26: Go1.8 for Google App Engine

SDKのアップデート26

■ gcloudコマンドの場合● components updateでアップデートを掛ける

■ Google App Engine SDKを落としている場合

● ダウンロードページから再度落としてくる

$ gcloud components update

Page 27: Go1.8 for Google App Engine

Go1.8に対応するには

■ 設定の変更● app.yamlを修正

■ コードの修正● golang.org/x/net/contextから標準のcontextに変更● ライブラリが未対応の部分を修正

○ 型に互換のない場所

27

Page 28: Go1.8 for Google App Engine

app.yamlの書き換え

■ api_versionをgo1.8に書き換える

28

runtime: goapi_version: go1.8

handlers:- url: /.* script: _go_app

使用されるgoappコマンドやGOROOTがスイッチされる

Page 29: Go1.8 for Google App Engine

コンテキストの互換29

■ インタフェースはメソッドが同じであれば互換がある● 標準のcontext.Contextとx/net/context.Contextは

同じメソッドリスト● インタフェース変数の代入はメソッドリストの一致のみの判定

○ ダックタイピング● 型が別でも代入できる

type A interface { String() string }type B interface { String() string }var _ A = B(nil)

Page 30: Go1.8 for Google App Engine

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

Page 31: Go1.8 for Google App Engine

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の実装

フィールドをコピーしている!

Page 32: Go1.8 for Google App Engine

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

Page 33: Go1.8 for Google App Engine

コンテキストの置き換え33

■ go tool fixコマンドを使う● Goのバージョン間のマイグレーションを行うコマンド● Go1.0以前に活躍● x/net/context.Contextからcontext.Contextに置き換える● goappコマンドにはない

$ go tool fix -force=context main.go

import文をsedしても対して変わらない

Page 34: Go1.8 for Google App Engine

型の不一致の問題

■ 型が合わないケースが存在する● コンテキストを含むコンポジット型

○ マップやスライス● コンテキストを引数や戻り値に取るクロージャ

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の例

Page 35: Go1.8 for Google App Engine

型の不一致の解決

■ ラッパーを作る● ライブラリに合わせてクロージャの型を変換する

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のラッパー

Page 36: Go1.8 for Google App Engine

Go1.9にも期待

■ 新機能● 型エイリアス● t.Helper()

○ テストのヘルパー関数がより便利に● sync.Map型● math/bitsパッケージ

36

Go1.8を乗り越えればきっと!

Page 37: Go1.8 for Google App Engine

まとめ

■ Go1.8にはベータとして対応● 大きな問題は解決済み● サブテストやsort.Sliceが使えるのは大きい● Goはバージョンアップごとにパフォーマンスが改善している

■ 本番利用にはまだ早そう● コンテキストの問題が残っている● 正式版に向けてライブラリ等の改修がされる?● メルカリ カウルではChat Opsから導入中● Go1.9はよ

37

Page 38: Go1.8 for Google App Engine

Thank you!

twitter: @tenntenn Qiita: tenntennconnpass: tenntenn

38