Upload
takuya-ueda
View
1.157
Download
0
Embed Size (px)
Citation preview
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