Slide 1

Slide 1 text

CA 1Day Youth Boot Camp Part of Go Takuma Shibuya The Gopher is designed by Renée French

Slide 2

Slide 2 text

Name Takuma Shibuya CyberAgent/AI事業本部 Go, Rust Kubernetes Compiler Software Architecture @sivchari

Slide 3

Slide 3 text

1.Goとその特徴 2.Goの様々なライブラリ、機能に慣れよう 3.実践API編 4.Goの最新機能Genericsに触れてみる 5.おわり

Slide 4

Slide 4 text

1.業務で使うGoの機能を仕組みから知り、なぜを理解する 2.Goを書くときの引き出しを増やす Goal

Slide 5

Slide 5 text

Goとその特徴

Slide 6

Slide 6 text

Goとは ● Googleが開発したプログラミング言語 ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 7

Slide 7 text

Goのリリースサイクル ● 2月と8月にメジャーリリース ○ 最初の3ヶ月は新規機能含めた開発 ○ 後半はドキュメント、バグ修正 ○ Development Freezeでは毎月リリースがある(前後あり) ■ Go1.18は3/15 ● ベータ版 ○ ベータでは既存のバグは全て修正 ○ 未知のバグを発見する目的がある ○ リリースが早いことは推奨されている ○ 重要なコード変更は追加のベータ版をリリース ● リリース候補版 ○ リリース候補版はほぼリリース版 ○ 致命的なバグがある場合に追加のリリース候補版がでる ○ 発行基準の1つがGoogleが本番ビルドに使用する Go Release Cycle

Slide 8

Slide 8 text

Goの後方互換性 ● 現在はGo1 ○ Go 1 and the Future of Go Programs に説明がある ● Go1とGo2 ○ Go1の仕様に沿って書かれたコードはその仕様の間は正しく実行できることを意図している ○ 互換性の保証はソースコードレベル ■ コンパイルしたバイナリレベルでの互換性は保証していない ○ Go1.18のジェネリクスが導入されても後方互換性が担保されている ○ Go2は何かしら破壊的な変更が入る可能性があるときにリリースされる ■ GenericsはGo2で導入される可能性があった ■ Go2についての詳細な説明は公式から上がっている Go 2 Draft Designs Toward Go 2

Slide 9

Slide 9 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 10

Slide 10 text

予約語の少なさ Q. Goの予約語の数は? 1. 45個 2. 38個 3. 25個 4. 37個

Slide 11

Slide 11 text

予約語の少なさ A. Goの予約語の数は? 1. 45個 (Zig) 2. 38個 (JavaScript[ES2022]) 3. 25個 4. 37個

Slide 12

Slide 12 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 13

Slide 13 text

シンプルで柔軟な言語設計 Q. Goのパラダイムは? 1. 関数型 2. オブジェクト指向 3. 両方 4. どちらとも言えない

Slide 14

Slide 14 text

シンプルで柔軟な言語設計 Q. Goのパラダイムは? 1. 関数型 2. オブジェクト指向 3. 両方 4. どちらとも言えない

Slide 15

Slide 15 text

シンプルで柔軟な言語設計 Is Go an object-oriented language? Yes and no. • 型とメソッドがあるため可能ではある • 型(クラス)の継承関係がGoには存在しない • 似たようなものとして埋め込みは提供されている type a struct {} type c interface{} type b struct { a c }

Slide 16

Slide 16 text

シンプルで柔軟な言語設計 Is Go an object-oriented language? Yes and no. • クラスではなく型にメソッドを実装できる type MyInt int func (m *MyInt) inc() { *m++ } func main() { var m MyInt println(m.inc()) }

Slide 17

Slide 17 text

シンプルで柔軟な言語設計 Go at Google: Language Design in the Service of Software Engineering Topic 15: Composition not inheritance • Goでは継承ではなく合成を提供している type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }

Slide 18

Slide 18 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 19

Slide 19 text

豊富な標準パッケージ ● DBへ接続したい ○ database/sql ● Web APIを作成したい ○ net/http ● Testをかきたい ○ testing ● ASTの操作がしたい ○ go/ast ● 時間の操作や比較をしたい ○ time

Slide 20

Slide 20 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 21

Slide 21 text

豊富な標準ツール ● cover ○ testのカバレッジプロファイルを取れる ● doc ○ パッケージの使い方や説明などを確認できる ● fix ○ deprecatedされたAPIを置き換えたりできる(golang.org/x/net/context -> context) ● pprof ○ プログラムのgoroutine実行数などのプロファイルができる ● vet ○ 静的解析で怪しいコードを検知してくれる etc… (asm, compile, link, pack, test2json)

Slide 22

Slide 22 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 23

Slide 23 text

シングルバイナリ・クロスコンパイル ● Goのコードはコンパイル後シングルバイナリになる ○ バイナリさえあれば処理系などが必要ない ■ JavaScript -> V8, Node, Deno, Bun ■ Java -> JVM ○ デプロイ容易性 ○ コンテナとの相性 ■ マルチステージビルド ● クロスコンパイルによるバイナリ配布が容易 ○ Apple Silicon(Arm64)でIntel Mac(Amd64)で動くバイナリを配布したい GOOS=linux GOARCH=amd64 go build main.go

Slide 24

Slide 24 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 25

Slide 25 text

goroutineによる並行プログラム ● go キーワードで並行処理のための別スレッド(後述)を容易に生成できる e.g.) 並行に100までプリントしたい! var wg sync.WaitGroup for i := 1; i <= 100; i++ { i := i wg.Add(1) go func() { println(i) wg.Done() }() } wg.Wait()

Slide 26

Slide 26 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 27

Slide 27 text

バージョン管理の容易さ ● プログラム言語には様々なバージョンツールがある ○ npm/yarn ○ pyenv ○ composer ● Goにも存在はするが基本的に必要ない ○ ライブラリの依存はgo.modとgo mod/go getコマンドが標準で存在する ○ Go自体のバージョンアップも標準コマンドのみで可能 Go 1.19が使いたい! go install golang.org/dl/go1.19@latest go1.19 download go1.19 version -> go versuon go1.19 $OS

Slide 28

Slide 28 text

Goとは ● Googleが開発したオープンソースプロジェクト ○ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表 ○ 2022年9月の時点でGo1.19がリリース ○ 2月と8月にリリースされる ● Goの特徴 ○ 予約語の少なさ ○ シンプルで柔軟な言語設計 ○ 豊富な標準パッケージ ○ 豊富な標準ツール ○ シングルバイナリ・クロスコンパイル ○ goroutineによる並行プログラミング ○ バージョン管理の容易さ ○ 多くのOSSライブラリへの採用

Slide 29

Slide 29 text

多くのOSSライブラリへの採用 moby(Docker) • Dockerのオリジナル • 最新のDocker Engineと同じリリース containerd • OCI Image Format • コンテナイメージの管理など • RPCでやりとり runc • OCI Runtime Specification • コンテナの実行をする

Slide 30

Slide 30 text

多くのOSSライブラリへの採用 terraform ● HCL(HashiCorp Configuration Language)によるIaC ● AWS/GCP/Azure etc.. ● Custom Providerにより拡張が可能

Slide 31

Slide 31 text

多くのOSSライブラリへの採用 kubernetes ● Podを最小単位とした構成環境 ● デプロイやスケール、ロードバランシングの柔軟性がある ● CRDに沿った拡張 ● Istio(written in Go)などでも登場する

Slide 32

Slide 32 text

多くのOSSライブラリへの採用 esbuild ● JSのビルドツール ● ビルドがwebpackの10倍ほど(公式より) ● v1ではないがベータ後期としている

Slide 33

Slide 33 text

Goの様々なライブラリ 機能に慣れよう

Slide 34

Slide 34 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 35

Slide 35 text

Goのダウンロード Goの公式からダウンロードする

Slide 36

Slide 36 text

Dockerのダウンロード Docker Desktopを使うと簡単にダウンロードできる

Slide 37

Slide 37 text

GitHubからリポジトリをクローンしよう go-rookie-gymをforkする ・ownerを自分のアカウントにしてcreate forkしよう

Slide 38

Slide 38 text

GitHubからリポジトリをクローンしよう codeからリンクをコピーして git clone

Slide 39

Slide 39 text

注意点! ● ここにあげるものが全てではない ● ここにあるものが絶対にいいものではない ● 質問はチャットで! ● クイズもあるから考えてみよう!

Slide 40

Slide 40 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 41

Slide 41 text

早速プログラムを動かそう package main import ( "log" "net/http" ) func main() { http.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) { log.Println("Welcome to Go Rookie Gym :)") }) log.Fatal(http.ListenAndServe(":8080", nil)) }

Slide 42

Slide 42 text

早速プログラムを動かそう ● train-httpserverでgo run main.go ● 別のタブで curl localhost:8080/welcome を実行 ● Welcome to Go Rookie Gym :)と出たら成功

Slide 43

Slide 43 text

JSONを受け取って出力しよう ● GoではJSONのエンコード、デコード用に encoding/json がある ● フィールドにjsonタグをつけることでリクエストボディをマッピングできる ● keyでのマッピングなのでデータ型さえあっていればいい ● 設定しない場合フィールド名をキャメルケースにしてマッチするものにマッピングされる ○ e.g. Age = “age” FullName = “fullName” type User struct { Name string `json:”name”` MyID int `json:”id”` }

Slide 44

Slide 44 text

JSONを受け取って出力しよう ● Payloadにkey名がnameの文字列を受け取れるようにjsonタグを追加しよう ● go run main.goでサーバーを立ち上げる ● curl localhost:8080/json -d ‘{ “name”: “自分の名前” }` を実行 ● { “name”: “Hello 自分の名前” } が返ってくれば成功

Slide 45

Slide 45 text

http serverのまとめ • net/httpだけで簡単にサーバーを立ち上げることができる • jsonをエンコード、デコードするときはjsonタグを使うと自由にマッピングできる

Slide 46

Slide 46 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 47

Slide 47 text

interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

Slide 48

Slide 48 text

interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

Slide 49

Slide 49 text

振る舞いを抽象化する https://go.dev/play/p/T77psnc50IN type Hello interface { Hello() } type hello struct{} func (h *hello) Hello() { fmt.Println(“Hello from Go”) }

Slide 50

Slide 50 text

振る舞いを抽象化する https://go.dev/play/p/TMs_eyxn2uB interfaceを満たしているかチェックできる if _, ok := v.(Hello); ok { log.Println("satisfied") }

Slide 51

Slide 51 text

interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

Slide 52

Slide 52 text

あらゆる型を代入することができる https://go.dev/play/p/maDUbkHfCvE func main() { var val interface{} val = 1 val = “a” val = true fmt.Println(val) }

Slide 53

Slide 53 text

あらゆる型を代入することができる https://go.dev/play/p/8jXj3Evo8AO switch any.(type) { case int: // snip case string: // snip case bool: // snip }

Slide 54

Slide 54 text

あらゆる型を代入することができる ● 型によって振る舞いを動的に変更できる ○ go/ast ■ astで表現される全てのNodeがPosとEndをという振る舞いを実装しながら固有の実装をもつ ○ database/sql ■ SQLの実行結果をScanするときに型に合わせて振る舞いを変更する

Slide 55

Slide 55 text

あらゆる型を代入することができる ● プロダクトレベルでのユースケースを試してみよう! ○ train-interfaceに移動 `cd train-interface` ○ go run main.goでサーバーを起動 ○ 別のタブで curl localhost:8080/ -d '{"id": 1, "name": "a"}' を実行する(1, “a”は好きなのでOK) ○ go run main.goをしたタブで{1 a}が出力されていたら成功

Slide 56

Slide 56 text

あらゆる型を代入することができる func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // defer r.Body.Close() http.Requestに関してはCloseがいらない var u user body, err := io.ReadAll(r.Body) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if err := json.Unmarshal(body, &u); err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } log.Println(u) w.WriteHeader(http.StatusOK) }) log.Println(http.ListenAndServe(":8080", nil)) }

Slide 57

Slide 57 text

あらゆる型を代入することができる func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // defer r.Body.Close() http.Requestに関してはCloseがいらない var u user body, err := io.ReadAll(r.Body) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if err := json.Unmarshal(body, &u); err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } log.Println(u) w.WriteHeader(http.StatusOK) }) log.Println(http.ListenAndServe(":8080", nil)) }

Slide 58

Slide 58 text

あらゆる型を代入することができる type user struct { id int `json:"id"` name string `json:"name"` }

Slide 59

Slide 59 text

あらゆる型を代入することができる type user struct { id int `json:"id"` name string `json:"name"` } なんで?🤔

Slide 60

Slide 60 text

あらゆる型を代入することができる type user struct { id int `json:"id"` name string `json:"name"` } private field !!

Slide 61

Slide 61 text

あらゆる型を代入することができる type user struct { ID int `json:"id"` Name string `json:"name"` } これで解決?

Slide 62

Slide 62 text

あらゆる型を代入することができる type user struct { ID int `json:"id"` Name string `json:"name"` } 他のパッケージに公開したく ないときは?

Slide 63

Slide 63 text

あらゆる型を代入することができる ● 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L607 u, ut, pv := indirect(v, false)

Slide 64

Slide 64 text

あらゆる型を代入することができる ● 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L479 if u, ok := v.Interface().(Unmarshaler); ok { return u, nil, reflect.Value{} }

Slide 65

Slide 65 text

あらゆる型を代入することができる ● 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L479 if u, ok := v.Interface().(Unmarshaler); ok { return u, nil, reflect.Value{} }

Slide 66

Slide 66 text

あらゆる型を代入することができる ● 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L119 type Unmarshaler interface { UnmarshalJSON([]byte) error }

Slide 67

Slide 67 text

あらゆる型を代入することができる ● 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L119 type Unmarshaler interface { UnmarshalJSON([]byte) error } これを満たせばよさそう!

Slide 68

Slide 68 text

あらゆる型を代入することができる func (u *user) UnmarshalJSON(b []byte) error { u2 := &struct { ID int Name string }{} if err := json.Unmarshal(b, &u2); err != nil { return err } u.id = u2.ID u.name = u2.Name return nil }

Slide 69

Slide 69 text

interfaceのまとめ • 振る舞いを抽象化することで利用者側が実装をカスタムできる • 型に合わせて振る舞いを動的に変更できる

Slide 70

Slide 70 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 71

Slide 71 text

並行処理と並列処理 並行処理と並列処理の違いを説明できますか? ちょっと考えてみましょう!

Slide 72

Slide 72 text

並行処理と並列処理 ● 並行処理と並列処理は範囲と点 ● 並行処理と並列処理は性質が異なる

Slide 73

Slide 73 text

並行処理と並列処理 ● 並行処理と並列処理は範囲と点 ● 並行処理と並列処理は性質が異なる

Slide 74

Slide 74 text

並行処理と並列処理は範囲と点 並行処理(Concurrency) 並列処理(Parallelism)   プロセスB プロセスA 並行処理時間 プロセスA   プロセスB プロセスA   プロセスB プロセスA Or

Slide 75

Slide 75 text

並行処理と並列処理 Concurrency is not parallelism プログラムでは、並行処理とは独立して実行されるプロセスの合成であり、並列処理とは(おそらく関連 する)計算を同時に実行することです。同時並行とは、一度にたくさんのことを処理することです。並列 処理とは一度にたくさんのことを行うことです。

Slide 76

Slide 76 text

並行処理と並列処理は範囲と点 並行処理(Concurrency) 並列処理(Parallelism)   プロセスB プロセスA 並行処理時間 プロセスA   プロセスB プロセスA   プロセスB プロセスA Or 同時にたくさん処理する 同時にたくさん扱うor処理する

Slide 77

Slide 77 text

並行処理と並列処理 ● 並行処理と並列処理は範囲と点 ● 並行処理と並列処理は性質が異なる

Slide 78

Slide 78 text

並行処理と並列処理は性質が異なる 並行性はコードの性質を指し、並列性は動作しているプログラムの性質を指します。 Ref: Go言語による並行処理

Slide 79

Slide 79 text

並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) }

Slide 80

Slide 80 text

並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) }

Slide 81

Slide 81 text

並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) } ● このコードに期待することは並行性?並列 性? ● CPUのコアが1だった時に期待するのは 並行性?並列性? ● CPUのコアが複数だったら求めるものは並 行性?並列性?

Slide 82

Slide 82 text

並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) } ● このコードに期待することは並行性?並列 性? ● CPUのコアが1だった時に期待するのは並 行性?並列性? ● CPUのコアが複数だったら求めるものは並 行性?並列性? ● 並列に実行される可能性を期待している ● コードは並行性を期待する ● 実行環境が並列性を決定する

Slide 83

Slide 83 text

goroutineとは? • 簡単に並行処理ができる軽量スレッド • ブロッキング(IO待ちなど)すると自動で別のgoroutineが他の実行可能なスレッド(Go runtime上の スレッド)で実行される • goroutineはGoプログラムの最小実行単位であり、entry pointのfunc mainはruntime/proc.go にあるfunc mainでコールされており、proc.goのfunc mainはmain goroutineと呼ばれる FYI https://go.dev/doc/faq#goroutines

Slide 84

Slide 84 text

goroutineは制御できない ● goroutineはcoroutineと似ている ● 違いはブロックされると実行可能なcoroutineを別のスレッドに移動する

Slide 85

Slide 85 text

coroutineとは? ● 中断・再開が可能なプログラム Python def hello(): print(‘Hello’, end=’ ‘) yield // 1 print(‘World’) yield // 2 h = hello() // 1まで h.__next__() // 1から2まで h.__next__() // 2から最後まで Kotlin fun runMain(): Join = viewModelScope.launch { val world = World() println(“Hello, ${world}) } suspend fun World(): String = withContext(Dispatchers.IO) { delay(2000) return “World” }

Slide 86

Slide 86 text

goroutineは実行順も制御できない https://www.voidcanvas.com/nodejs-event-loop https://zenn.dev/mmomm/articles/ff83eb49a7b642#%E5%8F% 82%E8%80%83%E8%B3%87%E6%96%99

Slide 87

Slide 87 text

goroutineは実行順も制御できない setTimeout(() => console.log(4)); setImmediate(() => console.log(6)); process.nextTick(() => console.log(2)); Promise.resolve().then(() => console.log(3)); console.log(1); require('fs').readFile('sample.txt', (err, data) => { console.log(5); }); https://www.voidcanvas.com/nodejs-event-loop https://zenn.dev/mmomm/articles/ff83eb49a7b642#%E5%8F% 82%E8%80%83%E8%B3%87%E6%96%99

Slide 88

Slide 88 text

goroutineは制御できない ● 開発者側が中断と再開を制御できない ● いつ起動し、終了するかも異なる可能性がある ● ブロックされたときにGo runtimeがスケジューリングして勝手にやってくれる

Slide 89

Slide 89 text

Go scheduler ● goroutineで発生するIOのブロッキングなどをユーザーが意識せずとも制御してくれる ● ユーザーが気にしなければいけないのは何をするか、どこで同期するか • M:Nのグリーンスレッドをスケジューリングしている

Slide 90

Slide 90 text

Go scheduler ● goroutineで発生するIOのブロッキングなどをユーザーが意識せずとも制御してくれる ● ユーザーが気にしなければいけないのは何をするか、どこで同期するか • M:Nのグリーンスレッドをスケジューリングしている

Slide 91

Slide 91 text

スレッドモデル ● 1: 1 ○ マシンのコアを全て利用できる。ユーザースレッドとカーネルスレッドが1:1のため別の操作を挟み たい場合は割り込み処理が必要になりコンテキストスイッチに時間がかかる ● N:1 ○ 複数のユーザースレッドが1つのカーネルスレッドと紐づくためコンテキストスイッチが早いが、ユー ザーレベルのライブラリが必要であり、1つのカーネルにN個のプロセスが紐づくため、並行処理が できない ● M:N ○ マルチコアでマルチスレッドを動かすため、コンテキストスイッチが早く並行、並列処理もできる欲張 りセットだが、実装が複雑になる

Slide 92

Slide 92 text

私用PCで何個作れるかチャレンジした package main import ( "log" "runtime" "time" ) func main() { defer func() { log.Println(runtime.NumGoroutine()) }() go func() { for { go func() { time.Sleep(time.Second * 60) }() } }() time.Sleep(time.Second * 30) } • 100個 • 5000個 • 10万個 • 1000万個 ヒント: コア数は8です

Slide 93

Slide 93 text

私用PCで何個作れるかチャレンジした package main import ( "log" "runtime" "time" ) func main() { defer func() { log.Println(runtime.NumGoroutine()) }() go func() { for { go func() { time.Sleep(time.Second * 60) }() } }() time.Sleep(time.Second * 30) } • 100個 • 5000個 • 10万個 • 1000万個 (12319732) ヒント: コア数は8です

Slide 94

Slide 94 text

Go schedulerの動きをみる ● train-goroutine/procsに移動 ● run.shを実行 ● P0: ~~やG1: ~~などが出てきて最後にSCHED: ~~がでれば成功

Slide 95

Slide 95 text

Go schedulerの動きをみる ● GOMAXPROCSというオプションによりコア数を1にしている ● 出力結果をみるとP0しかないことがわかる ● 内部では複数のMとGという何かが切り替わりながらスケジューリングされている

Slide 96

Slide 96 text

Go schedulerの簡単な説明 ● schdulerのG, M, Pってなに? ○ Gはgoroutine ○ Mはgoroutineを実行する場所 ○ Pはgoroutineを実行するリソースをもつOSのCPUのようなもの P M G G G G HACKING.md

Slide 97

Slide 97 text

Go schedulerの動きをみる ● つまり内部的にはP0(GOMAXPROCS=1)に対して複数の実行する場所であるMとgoroutineであ るGが存在し実行されていることがわかる ● CPUに関係なくGo runtimeの中で複数のプロセスが実行されているということはM:Nであるといえ る

Slide 98

Slide 98 text

Go channelとは? • 並行に実行している関数同士が特定の方の値を送受信するためのもの • channel<- や <-channelという構文でデータのコミュニケーションをとる

Slide 99

Slide 99 text

Go channelに触れてみよう ● train-goroutine/chanに移動する ● go run main.goでプログラムを実行 ● 最後にendと出力されれば成功

Slide 100

Slide 100 text

Go channelに触れてみよう func main() { ch := make(chan int) go func() { time.Sleep(time.Second * 2) ch <- 1 }() log.Println("wait") log.Println(<-ch) close(ch) go func() { time.Sleep(time.Second * 2) // closeしても遅れるが初期値が返却されるようになる ch <- 1 }() log.Println(<-ch) // closeを2回するとpanicする // close(ch) log.Println("end") }

Slide 101

Slide 101 text

Go channelに触れてみよう func main() { ch := make(chan int) go func() { time.Sleep(time.Second * 2) ch <- 1 }() log.Println("wait") log.Println(<-ch) close(ch) go func() { time.Sleep(time.Second * 2) // closeしても遅れるが初期値が返却されるようになる ch <- 1 }() log.Println(<-ch) // closeを2回するとpanicする // close(ch) コメントを外して実行してみよう log.Println("end") }

Slide 102

Slide 102 text

Deadlock package main func main() { ch := make(chan struct{}) <-ch } ● 並行に実行している関数同士が特定の方の値を 送受信するためのもの ● 並行に実行し、chに送信しているgo routineが 存在しない ● CT(compile time)ではわからずRT(runtime) でしか判明しない

Slide 103

Slide 103 text

Go channelに触れてみよう ● 2回チャネルをcloseするとpanicしてしまうので呼び出し側でcloseすることを意識する ● Deadlockには注意する

Slide 104

Slide 104 text

WaitGroup ● このプログラムを実行してみよう package main import “log” func main() { go func() { log.Println(“goroutine”) }() }

Slide 105

Slide 105 text

WaitGroup ● goroutineの起動と終了はユーザーが制御することはできない ● 複数のgoroutineをバックグラウンドで動かさずどこかで同期したい ● 同期のために何かしらブロッキングするためのものが欲しい

Slide 106

Slide 106 text

WaitGroup ● このプログラムを実行してみよう package main import ( "log" "sync" ) func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() log.Println("goroutine") }() wg.Wait() }

Slide 107

Slide 107 text

WaitGroup ● Add: 終了待ちのgoroutineの数だけ内部のstateをインクリメントする ● Done: 終了したgoroutineが呼び内部のstateをデクリメントする ● Wait: 内部のstateが0になるまでブロックする

Slide 108

Slide 108 text

WaitGroupの注意点 ● 失敗するコードを実行する ○ train-goroutine/waitに移動する ○ go run main.goを実行 ○ fatal: errorがでれば一旦は成功

Slide 109

Slide 109 text

WaitGroupの注意点 Q. なぜ fatal error: all goroutines are asleep - deadlock!になってしまう? ヒント 内部のstateは共有されます

Slide 110

Slide 110 text

WaitGroupの注意点 Q. なぜ fatal error: all goroutines are asleep - deadlock!になってしまう? ヒント 内部のstateは共有されます A. sync.WaitGroupがコピーされてしまうから

Slide 111

Slide 111 text

WaitGroupの注意点 ● go vet を実行してみよう ○ train-goroutine/waitに移動 ○ go vet main.goを実行 ○ 標準出力にcopies lock value ~が出力されたら成功 go vet: goが提供するtool群の1つでよくあるエラーを静的解析で発見する

Slide 112

Slide 112 text

WaitGroupの注意点 ● go funcの引数をポインタにする go func(wg *sync.WaitGroup) { // カウントを減らす defer wg.Done() log.Println(i) }(&wg) sync.WaitGroupを関数に渡すときは 注意!

Slide 113

Slide 113 text

Mutex ● 並行性処理における排他制御の問題 package main import ( "fmt" "time" ) func main() { c := 0 for i := 0; i < 1000; i++ { go func() { c++ }() } time.Sleep(time.Second) fmt.Println(c) } ● 出力されるcは?先に予想しよう ○ train-goroutine/mutex/raceに移動 ○ go run main.go ○ 出力された結果をチャットにコメント

Slide 114

Slide 114 text

Mutex ● 複数のgoroutineがどの数字をインクリメントするかはタイミングによる ○ Aがcを0から1に ○ Bがcを1から2に ○ Cが同じタイミングでcを1から2に

Slide 115

Slide 115 text

Mutex ● mapだとpanicする ○ Go1.16 から検出するようになった if one goroutine is writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program

Slide 116

Slide 116 text

Mutex ● sync.MutexはこのようなData Raceに対する明示的な排他制御を提供する ● sync.Mutexとsync.RWMutexが存在する ○ sync.Mutexはシンプルなロックを提供する ■ ReadとWriteがそれぞれでロックをとり別のgoroutineの処理をまつ ○ sync.RWMutexは共有ロックを提供する ■ ReadとWriteが共有ロックをもち、Writeは別のgoroutineをまつがReadは許可

Slide 117

Slide 117 text

Mutex ● プログラムを実行してみる ○ train-goroutine/mutexに移動 ○ go run main.goを実行 ○ endと最後に出力されれば成功

Slide 118

Slide 118 text

Mutex ● プロダクトレベルでの使い方 ○ インメモリキャッシュ ○ グローバルリソースへのアクセス

Slide 119

Slide 119 text

select go func() { ch1 <- 1 }() go func() { ch2 <- 2 }() select { case v1, ok := <-ch1: // case v2, ok := <-ch2: // default: }

Slide 120

Slide 120 text

select go func() { ch1 <- 1 }() go func() { ch2 <- 2 }() select { case v1, ok := <-ch1: // case v2, ok := <-ch2: // default: } ● channelの受け取りを多重化できる ● switchのように上から評価されず受け取れたものか ら ● どれにも該当しない場合はdefaultへ ● defaultがない場合はブロックされる

Slide 121

Slide 121 text

semaphore ● 制御なしでgoroutineを生成すると当然パフォーマンスは下がる ● rate limitが存在すると制御しないといけない ● train-goroutine配下のコードを実行してみよう ○ nosemaphoreとsemaphoreを実行し違いを確認してみよう

Slide 122

Slide 122 text

semaphore ● goroutineの実行数を制御することができる ○ bufferありのchannelを使用する ○ semaphore packageを使用する

Slide 123

Slide 123 text

bufferありのchannelを使用する PlayGround ch := make(chan struct{}, 1) for { ch<-struct{}{} go func() { defer func() { <-ch }() // }() }

Slide 124

Slide 124 text

semaphore packageを使用する PlayGround var s *semaphore.Weighted = semaphore.NewWeighted(1) func longProcess(ctx context.Context) { // Acquireで取得するとcountが減る // 0になるとブロックする if err := s.Acquire(ctx, 1); err != nil { fmt.Println(err) return } // カウントを戻す defer s.Release(1) fmt.Println("Wait...") time.Sleep(1 * time.Second) fmt.Println("Done") }

Slide 125

Slide 125 text

errgroup ● goroutine で呼ぶ関数の戻り値は受け取ることができない ○ 内部でerror型を扱いたいけどreturnでerrorを渡せない ○ 既存の機能を扱う場合error型のchannelを渡すことで対応できる ○ errorが発生しないケースなどをかんがえるとハンドリングが煩雑になる

Slide 126

Slide 126 text

errgroup ● sync.WaitGroup + error型を返せるgoroutineを利用できる ○ errgroup.Group.Goでerror型を返すgoroutineを生成できる ○ errgroup.Group.Waitでsync.Waitのように待ちながら生成したgoroutineでerrorがないか チェックできる ○ errgroup.WithContextで後述のcontextも扱うことができる

Slide 127

Slide 127 text

errgroupの注意点 ● errgroupが取得できるエラーは一番最初に発生したもののみ ○ 5つのうち2つエラーが発生した場合に全てのエラーを受け取れない ○ contextによる処理のキャンセルが存在するため ● 複数のエラーをまとめたい場合はhashicorp/go-multierrorがある ○ 基本的にはerrgroupと同じだが、全てのエラーを受け取れる ○ errgroupと異なりcontextを用いた処理のキャンセルが存在しない

Slide 128

Slide 128 text

goroutine leak ● goroutineはユーザーから制御できない ○ プロセスとして残り続ける可能性がある ○ 待ちのプロセスは残り続けるだけだが参照されている変数などがGCされずOOMする

Slide 129

Slide 129 text

goroutine leak func main() { log.Printf("before leak:%d\n", runtime.NumGoroutine()) leak(nil) time.Sleep(3 * time.Second) log.Printf("after leak:%d\n", runtime.NumGoroutine()) } func leak(c <-chan string) { // closeされない!! go func() { for cc := range c { log.Println(cc) } }() log.Printf("in leak:%d\n", runtime.NumGoroutine()) } before leak: 1 in leak: 2 after leak: 2

Slide 130

Slide 130 text

goroutine leak ● 対処法 ○ 後述のcontextとselectなどでしっかりと抜けるようにする ○ ブロッキングする処理やデーモン、ループ処理は終了する条件を正しくかく

Slide 131

Slide 131 text

goroutineのまとめ ● 容易に並行処理を実行することができるようにGoが提供する軽量スレッド ● 排他制御や同期するための標準パッケージを活用する ● goroutine leakに注意する

Slide 132

Slide 132 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 133

Slide 133 text

contextとは contextパッケージの中で定義されているContext型は、デッドライン、キャンセルシグナル、その他のリ クエストに応じた値をAPI境界やプロセス間で伝達します FYI: Context

Slide 134

Slide 134 text

contextとは contextパッケージの中で定義されているContext型は、デッドライン、キャンセルシグナル、その他のリ クエストに応じた値をAPI境界やプロセス間で伝達します FYI: Context ❓

Slide 135

Slide 135 text

contextとは ● ListenAndServeはリクエストごとにgoroutineを生成する ● 実践的に考えるとそれぞれのgoroutineがさらにgoroutineを生成する可能性がある(DB, 外部API へのリクエスト) ● 意識せず多くのgoroutineを生成している可能性がある(goroutine leakのリスク) ● 3rd packageをソースコードレベルで意識する必要が出てくる ● Aは処理を止めたいがBは続けたい ○ channelを使用すると煩雑になる

Slide 136

Slide 136 text

contextとchannelの比較 ● 実際にコードをみてみよう ○ train-context/why-ctxに移動 ○ proc1はchannelを使用した例、proc3はcontextを使用した例 ○ 25行目をコメントアウトしてgoroutine leakが発生していることを確認してみよう

Slide 137

Slide 137 text

contextとは ● contextを使用すると煩雑な処理をシンプルにすることができる ● cancelを用いることでAPIリクエストやある処理のキャンセルができる ○ 10秒以上かかったらキャンセルする ○ メインプロセスが終了したためgoroutineを全て終了する

Slide 138

Slide 138 text

context cancel ● train-context/ctx-cancelを実行してみよう ● context.WithCancelを用いることでcancelCtxとcancelFuncが返ってくる ● cancelFuncを呼ぶことでcontext内部のchannelをcloseする ● context.Done()経由でcancelされたかどうかを確認できる

Slide 139

Slide 139 text

context timeout ● train-context/ctx-timeoutを実行してみよう ● context.WithTimeoutを用いることでtimerCtxとcancelFuncが返ってくる ● cancelFuncを呼ぶ、ある秒数が過ぎることでcontext内部のchannelをcloseする ● context.Done()経由でcancelされたかどうかを確認できる

Slide 140

Slide 140 text

context deadline ● train-context/ctx-deadlineを実行してみよう ● context.WithDeadlineを用いることでtimerCtxとcancelFuncが返ってくる ● cancelFuncを呼ぶ、ある時刻から指定秒が過ぎることでcontext内部のchannelをcloseする ● context.Done()経由でcancelされたかどうかを確認できる

Slide 141

Slide 141 text

contextのcancelFuncは必ず呼ぶ ● contextのcancelFuncはdeferなどで必ず呼ぶことを忘れない ○ channelを2回以上cancelするとpanicするがcontextは制御してくれている ○ Documentにも書かれている ■ キャンセルしないとcontextがメモリに残り続ける可能性がある ■ go vetを使うことでチェックもできる

Slide 142

Slide 142 text

contextを用いて値を伝播する ● contextはAPIのキャンセル以外にも特定の値をkey/valueで伝播させられる ○ 分散システムでTrace IDを伝播させたい ○ 認証トークン ○ ユーザーID ○ Query Cache

Slide 143

Slide 143 text

contextを用いて値を伝播する ctx := context.WithValue(ctx, “key”, “value) ctx.Value(“key”)

Slide 144

Slide 144 text

context.Valueのtips ● keyにはempty structを使うことでメモリを節約できる ○ train-context/ctx-valueを実行してみよう ○ stringは16byte、empty structは0byte ● context.Valueのkeyはprivateにする ○ context.Valueに同じkeyをsetすると上書きされる ○ privateにすることでsetとgetを関数経由でのみ行えるようにできる

Slide 145

Slide 145 text

contextの親子関係と兄弟関係 ● train-context/parent-childを実行してみよう ○ contextは親子関係、兄弟関係になる可能性がある ○ 親子関係は親のキャンセルが子へ伝播し逆はない ○ context.Valueの場合は子から親へ探索する ○ 兄弟関係のcontextは伝播しない ○ timeoutは親より短かければそちらが優先される

Slide 146

Slide 146 text

contextは親子関係、兄弟関係になる context.WithCancel context.WithValue context.WithTimeout context.WithDeadline

Slide 147

Slide 147 text

キャンセルは親から子へ伝播する context.WithCancel context.WithValue context.WithTimeout context.WithDeadline ❌ ❌ ❌

Slide 148

Slide 148 text

キャンセルは親から子へ伝播する context.WithCancel context.WithValue context.WithTimeout context.WithDeadline ❌ 子から親への伝播はない

Slide 149

Slide 149 text

contextの親子関係と兄弟関係 ● train-context/parent-childを実行してみよう ○ contextは親子関係、兄弟関係になる可能性がある ○ 親子関係は親のキャンセルが子へ伝播し逆はない ○ context.Valueの場合は子から親へ探索する ○ 兄弟関係のcontextは伝播しない ○ timeoutは親より短かければそちらが優先される

Slide 150

Slide 150 text

context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4

Slide 151

Slide 151 text

context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4 2が欲しい

Slide 152

Slide 152 text

context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4 2が欲しい ❌

Slide 153

Slide 153 text

兄弟関係は伝播しない context.WithValue context.WithValue context.WithValue 1 2 3 4 4が欲しい ❌

Slide 154

Slide 154 text

timeoutは短い方が優先される context.WithTimeout 5sec 5sec context.WithTimeout 3sec 3sec 5sec context.WithTimeout 7sec

Slide 155

Slide 155 text

● structに含めない ○ contextにセットする値はリクエストスコープに閉じるものにする ■ structに含めるとリクエストスコープを超えてしまう ● トークンの漏洩 ● 意図しない値の上書き ○ 第一引数で渡す Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. context contextのアンチパターン

Slide 156

Slide 156 text

● net/httpは例外 ○ contextは1.7から追加された ○ Goは後方置換性を重視しているため、APIレベルでの変更はGo1の段階ではない ○ net/httpは1Requestごとに1goroutineを生成するためリクエストスコープに絞れるためcontext のアンチパターンは踏まない ● database/sql ○ database/sqlはXXXContextのようなAPIを追加した ○ Transactionスコープの管理をするためにcontext.Contextをstructに含んでいる アンチパターンの例外

Slide 157

Slide 157 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 158

Slide 158 text

● Goが提供するDB操作を行うための標準パッケージ ● 実際に利用する場合はdatabase/sqlと使用したいDBのSQL Driverをインポートしdatabase/sqlを 介して利用する ● database/sqlはコネクションプールの管理等を行い特定のDBへの実装は提供していない ○ 提供しているのはinterface ○ 具体的な実装はinterfaceを満たしたSQL Driverが行う ○ 公式のwikiには56個掲載されている ○ 最近GoogleからSpannerのDriverもリリースされた database/sqlとは

Slide 159

Slide 159 text

● ORMもdatabase/sqlをラップする形で実装している ○ GORM ■ GORMがdatabase/sqlを満たすようにSQL Driverを提供している ○ Ent ■ database/sqlの実装をラップして拡張している database/sqlとは

Slide 160

Slide 160 text

● train-dbに移動 ● docker-compose up —build -dでコンテナを起動(起動まで少し時間がかかる) ● go run main.goを実行 database/sqlに触れてみる

Slide 161

Slide 161 text

import ( "context" "database/sql" "log" _ "github.com/go-sql-driver/mysql" ) database/sqlに触れてみる

Slide 162

Slide 162 text

● Blank import インポート宣言は、インポートするパッケージとインポートされるパッケージの依存関係を宣言するもので ある。パッケージがそれ自身を直接または間接的にインポートすることや、エクスポートされた識別子を 一切参照せずにパッケージを直接インポートすることは違法です。パッケージの副作用(初期化)のため だけにパッケージをインポートするには、明示的なパッケージ名として空白の識別子を使用します。 database/sqlに触れてみる

Slide 163

Slide 163 text

● Blank import インポート宣言は、インポートするパッケージとインポートされるパッケージの依存関係を宣言するもので ある。パッケージがそれ自身を直接または間接的にインポートすることや、エクスポートされた識別子を 一切参照せずにパッケージを直接インポートすることは違法です。パッケージの副作用(初期化)のため だけにパッケージをインポートするには、明示的なパッケージ名として空白の識別子を使用します。 database/sqlに触れてみる

Slide 164

Slide 164 text

● Goのinitは組み込みの初期化関数 ● 依存先のパッケージにinitがある場合先に依存先が解決される ● initを同一パッケージで複数書いた場合ファイルの上から順番に解決される ● SQL Driverがinitを用いてinterfaceの具象実装をdatabase/sqlから呼べるようにする database/sqlに触れてみる

Slide 165

Slide 165 text

db, err := sql.Open("mysql", "dsn") if err != nil { log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる

Slide 166

Slide 166 text

Q. DBと最初にコネクションを張るのはどっち? db, err := sql.Open("mysql", "dsn") if err != nil { log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる

Slide 167

Slide 167 text

A. PingContext db, err := sql.Open("mysql", "dsn") if err != nil { log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる

Slide 168

Slide 168 text

● sql.OpenはSQL Driverによるが必ずコネクションを張るかはわからない ○ MySQL Driverはコネクションを張っていない ● 疎通確認は必ずPing/PingContextで確認する database/sqlに触れてみる

Slide 169

Slide 169 text

● 複数レコードを取得する場合はQuery/QueryContextを利用する ○ 返り値としてRowsが取得できる ■ Rows.NextでScanするレコードを確認する ■ Rows.Scanでデータを特定の変数、structのフィールドにマッピングする ● 単一レコードを取得する場合はQueryRow/QueryRowContextを利用する ○ 返り値としてRowが取得できる ■ Row.Scanでデータを特定の変数、structのフィールドにマッピングする ● INSERT/UPDATE/DELETE etc..はExec/ExecContextを利用する database/sqlに触れてみる

Slide 170

Slide 170 text

● トランザクションはBegin/BeginTxで取得する ○ Query/QueryRow/Execなどを同じように使える ○ Commitでトランザクションをコミットする ○ Rollbackでトランザクションをロールバックする database/sqlに触れてみる

Slide 171

Slide 171 text

● database/sqlを使用する際は同時に使いたいDBのSQL Driverもインポートする ● 疎通確認はPingを使う ● 発行したいクエリに合わせてメソッドやトランザクションをうまく利用する database/sqlのまとめ

Slide 172

Slide 172 text

今回触れる機能 ● http server ● interface ● goroutine ● context ● database/sql ● testing

Slide 173

Slide 173 text

● Goのテストはgo testでUTやBenchmarkなどさまざまなテストを実行できる ● ファイルのサフィックスに_testがついているものをテスト対象とする ● テストファイルはビルド対象にならない Goにおけるテスト

Slide 174

Slide 174 text

● Test ○ プレフィックスにTestがついている ○ ユニットテストなど ● Benchmark ○ プレフィックスにBenchmarkがついている ○ パフォーマンスの比較などで使う ● Fuzz ○ プレフィックスにFuzzがついている ○ 初期で与えられた値からランダムな値を生成して入力値からバグを発見する ● Example ○ プレフィックスにExampleがついている ○ 出力結果のテストやGoDocにサンプルとして載せることができる Goのテストの種類

Slide 175

Slide 175 text

func TestSum(t *testing.T) { got := Sum(1, 1) want := 2 if got != want { t.Errorf(“got %d\n want %d”, got, want) return } } Goのテストサンプル

Slide 176

Slide 176 text

● Goのtesting packageにはLog、Skip、ヘルパーなどしか存在しない ● assertの3rd packageは存在するが標準ではない Goのtestingにはassertがない

Slide 177

Slide 177 text

FAQ: Why does Go not have assertions? ● Assertは便利だがエラーメッセージが自動で出力されるため、レポート内容が適当になる ● エラーレポートは何が起きたかを書くことが重要なためしっかりと自分で書く ● 覚えることを増やすことより丁寧に書き、今後のデバッグの際に役立つようにしよう Goのtestingにはassertがない

Slide 178

Slide 178 text

● GoではテストをTable Driven Testで書くことが推奨されている ● 公式のwikiにも書き方などが存在する ● テストケースの入出力を1つのstructにまとめ、すべてのテストケースをforを用いて実行する Table Driven Test

Slide 179

Slide 179 text

● Table Driven Testを実行してみよう ○ train-testingに移動する ○ go test tdd_test.go main.go -vを実行 ○ PASSと出力されれば成功 Table Driven Test

Slide 180

Slide 180 text

tests := []struct { name string args args want int wantErr bool } { { name: “success”, args: args { i: 1, j: 2, }, want: 3, wantErr: false, }, } Table Driven Test

Slide 181

Slide 181 text

tests := []struct { name string args args want int wantErr bool } { { name: “success”, args: args { i: 1, j: 2, }, want: 3, wantErr: false, }, } Table Driven Test この1つの塊がテストケース

Slide 182

Slide 182 text

● 新しいテストケースの追加が容易 ● テーブルをみることで入出力から期待する内容を理解できる ● structとmapでは実行順序が変わる可能性があるため注意する ● Goに限られたテスト手法ではない Table Driven Testのポイント

Slide 183

Slide 183 text

● Add関数は負の値を受け取るとエラーを返す ● 実際に入力値が負の値で、エラーケースが正しく実行されることを確認するためのテストケースを書い てみよう テストケースを追加してみよう

Slide 184

Slide 184 text

● Goにはtesting.Parallelという関数がある ● testing.Parallelを使うことで同一パッケージ内のテストを逐次ではなく並行に実行できる Table Driven Testの実行時間を短くする

Slide 185

Slide 185 text

● Parallel Testを実行してみよう ○ train-testingに移動 ○ go test parallel_test.go main.go -vを実行 ○ PASSすれば成功 Table Driven Testの実行時間を短くする

Slide 186

Slide 186 text

● サブテストをParallelで並行にする際は値をコピーする(goroutineの起動よりもforが早い) ● Parallelなサブテストはトップレベルのテストが実行されたあとに処理される Parallel Testの注意点

Slide 187

Slide 187 text

● go testでも-raceオプションを使うことができる ● -raceオプションを有効にすることでデータの競合を検出することができる Testを実行してraceを検出する

Slide 188

Slide 188 text

● -raceオプションを使ってみよう ○ train-testingに移動する ○ go test -race race_test.go main.go -vを実行 ○ WARNING: DATA RACEと出力されれば成功 Testを実行してraceを検出する

Slide 189

Slide 189 text

● raceするテストではracememを生成したgoroutineとTestRaceのgoroutineどちらからも操作され ている ● データの競合を避けるためには排他制御をしてあげる必要がある Testを実行してraceを検出する

Slide 190

Slide 190 text

● Cleanup ○ Cleanupを呼び出しているテスト関数の一番最後に呼ばれる ○ Parallelを使うとトップレベルが先に終了するため絶対に最後にしたい処理はCleanupを使う ● Setenv ○ Cleanupを用いてそのテスト関数だけで使える環境変数をセットできる ● Tempdir ○ Cleanupを用いてそのテスト関数だけで使える一時ディレクトリを作成することができる ● Helper ○ ヘルパー関数などを読んだときに呼び出し先の行数でエラーなどが出力されるようになる ○ テストのレポート情報をより正確に伝えることができる testingパッケージの便利メソッド

Slide 191

Slide 191 text

「推測するな、計測せよ」 ● AとBの書き方のどちらを採用するとプログラムのパフォーマンスがいいか悩む ● 採用する際には誰がみてもわかる説得力が重要 ● Benchmark Testを書いて判断に理由を持たせる Benchmarkでパフォーマンスを計測する

Slide 192

Slide 192 text

● Benchmark Testを実行してみよう ○ 計測対象はスライスのcapacityを事前に確保したほうがいいのかしない方がいいのかどうか ○ train-testing/benchに移動する ○ go test -bench . -benchmemを実行 ○ PASSすれば成功 Benchmarkでパフォーマンスを計測する

Slide 193

Slide 193 text

● Benchmark Testの結果のみかた ○ 数字: 実行した回数 ○ ns/op: 1回あたりの実行にかかった時間 ○ B/op: 1回あたりのアロケーションで確保した容量 ○ allocas/op: 1回あたりのアロケーション回数 → 結果から先に容量を確保した方が断然にいいことがわかる Benchmarkでパフォーマンスを計測する

Slide 194

Slide 194 text

● ユニットテストなどでは見つけられないエッジケースを発見する ● プログラムに対してさまざまな入力を与える ● Go本体ではjsonなどのエンコード、デコードのテストで採用されている Fuzzing Testでバグを発見する

Slide 195

Slide 195 text

● 公式サイトにも説明やチュートリアルが存在する Fuzzing Testでバグを発見する

Slide 196

Slide 196 text

● Fuzzing Testを実行してみよう ○ train-testingに移動する ○ go test -fuzz FuzzCalc fuzzing_test.go -fuzztime 10sを実行 ○ 色々出てきてエラーが吐かれたら成功 Fuzzing Testでバグを発見する

Slide 197

Slide 197 text

func FuzzCalc(f *testing.F) { f.Add(1, 2, "+") f.Fuzz(func(t *testing.T, v1, v2 int, ope string) { _, _ = Calc(v1, v2, ope) }) } func Calc(v1, v2 int, ope string) (int, error) { switch ope { case "+": return v1 + v2, nil case "-": return v1 - v2, nil case "*": return v1 * v2, nil case "/": return v1 / v2, nil } return 0, errors.New("") } Fuzzing Testでバグを発見する

Slide 198

Slide 198 text

func FuzzCalc(f *testing.F) { f.Add(1, 2, "+") f.Fuzz(func(t *testing.T, v1, v2 int, ope string) { _, _ = Calc(v1, v2, ope) }) } func Calc(v1, v2 int, ope string) (int, error) { switch ope { case "+": return v1 + v2, nil case "-": return v1 - v2, nil case "*": return v1 * v2, nil case "/": return v1 / v2, nil } return 0, errors.New("") } Fuzzing Testでバグを発見する もしv2が0なら?

Slide 199

Slide 199 text

● Fuzzing testはfuzztimeを決めないと無限に実行される ○ CIに組み込む際は絶対にfuzztimeを設定する ● Fuzzing testに失敗した場合testdataに失敗した時の入力値が保存される ○ 次の実行はこの入力値から実行されるため修正すればPASSされる Fuzzing Testのポイント

Slide 200

Slide 200 text

● Testを書くことは難しい ○ 外部APIやクラウドサービスをどのようにテストするか ○ Rate limitを気にしないといけない ○ API Keyが必要な場合どのように共有する? ○ BQのようにプロセッシングにお金が絡むようなケース Testとmock

Slide 201

Slide 201 text

● mockを使用してテストする ○ 関数の呼び出しを期待する引数で呼ばれることを確認し、ダミーの結果を返却する ■ GetUserという関数をUserIDが1である引数で呼ばれることを期待し、成功した場合Name: Gopherを返却する ○ mockした関数が期待する引数で呼ばれなかったり、そもそも呼ばれない場合失敗する ○ Spyは入出力を記録するだけでMockの呼び出し時点では評価されない ○ Stubは常に何かしら置き換えたものを返すだけ Testとmock

Slide 202

Slide 202 text

● gomock ○ 定義されたinterfaceからmockを自動生成してくれる ○ mockgenコマンドで生成できる ○ go generateと一緒につかわれることがおおい ■ go generateを実行することでファイルに存在する//go:generate ディレクティブを実行できる Testとmock

Slide 203

Slide 203 text

● mockを体験してみよう ○ train-testingに移動してdb.goを見てみよう ○ go generate db.goを実行する ○ mockディレクトリとmock_db.goが生成されていれば成功 ○ go test db_test.go db.go -vを実行する ○ PASSすれば成功 Testとmock

Slide 204

Slide 204 text

ctrl := gomock.NewController(t) mockdb := mock.NewMockDB(ctrl) mockdb.EXPECT().Ping(context.Background()).Retrun(nil) usecase := NewUsecase() usecase(mockdb, context.Background()) Testとmock 17行目をコメントアウトして15行目と16 行目のコメントを外してみよう

Slide 205

Slide 205 text

● assertを使うよりも丁寧なレポートメッセージを書くことを意識しよう ● BenchmarkやFuzzingを適切に使うことでソフトウェアの品質をあげよう ● mockを上手に使い環境に依存しないテストをかこう testingのまとめ

Slide 206

Slide 206 text

実践API編

Slide 207

Slide 207 text

基礎編 ● グループとユーザーがあるアプリケーションに新規のAPIを3つ追加する ○ ユーザーの新規登録 ○ UserIDに紐づくグループを全て返す ○ グループの新規作成 発展編 ● httptest ● mockをつかってUnit Testをかいてみる ● JWT認証をいれてみる: secret keyはgo-rookie-gymでsign ● Transactionをいれる(ユーザー登録) 実践API編

Slide 208

Slide 208 text

ユーザーの新規作成: 作成後、ユーザーに紐づくグループも1つ作成する(グループ名はなんでもOK) エンドポイント: /user メソッド: Post リクエストボディ: { “name”: string } レスポンス: { “id”: int, “group_id”: int } 実践API編

Slide 209

Slide 209 text

UserIDに紐づくグループを全て返す エンドポイント: /groups?user_id=xx メソッド: Get リクエストボディ: なし レスポンス: [ { “id”: int, “name”: string } ] 実践API編

Slide 210

Slide 210 text

グループの新規作成 エンドポイント: /group メソッド: POST リクエストボディ: { “user_id”: int, “name”: string } レスポンス: { “id”: int, } 実践API編

Slide 211

Slide 211 text

● cmd/server ○ GoのAPIサーバーの立ち上げや、DIなどの処理をここにかこう ● handler ○ アプリケーション層、リクエスト、レスポンスの処理を記述する ● domain ○ userとgroupのモデリング箇所(今回はstruct定義くらいになっちゃう) ○ repositoryのIFの定義 ● usecase ○ ビジネスロジックを記述する ● infrastructure ○ 永続化の実装を書く(今回はMySQL) ● schema ○ 今回のテーブル定義が記述されている アーキテクチャの説明

Slide 212

Slide 212 text

● httptest ○ Goのtestを理解する ● mockをつかってUnit Testをかいてみる ○ golang/mock ○ 資料のp.200から見返してみる ● JWT認証をいれてみる: secret keyはgo-rookie-gymでsign ○ golang-jwt/jwt ● Transactionをいれる(ユーザー登録) ○ GoのTransactionでラッパー関数 発展編のポイント

Slide 213

Slide 213 text

Goの最新機能Genericsに触 れてみる

Slide 214

Slide 214 text

● プログラミング言語の機能・仕様の1つ ● 同じコードでさまざまなデータ型を処理できるようにするもの ● 公式のチュートリアルもある func Append[T Stringer] (args []T) []string { result := make([]string, len(T)) for i, arg := range args { result[i] = arg.String() } return result } Genericsとは

Slide 215

Slide 215 text

● 関数と型は型パラメータをもつ func Append[T Stringer] (args []T) []string { result := make([]string, len(args)) for i, arg := range args { result[i] = arg.String() } return result } GoのGenericsの原則

Slide 216

Slide 216 text

● 型パラメータが満たすべき制約はinterfaceを型制約として用いることで実現する func Append[T Stringer] (args []T) []string { result := make([]string, len(args)) for i, arg := range args { result[i] = arg.String() } return result } type Stringer interface { String() string } GoのGenericsの原則

Slide 217

Slide 217 text

● Go1.18から追加されたinterface ● 比較可能な値であることを制約とするためのもの ● ユースケースとしてはmapへの処理 ○ mapのkeyが存在するかどうかは ==か!=が用いられる comparable interface

Slide 218

Slide 218 text

● Goにはdefined typeと呼ばれる型定義をおこなう機能がある ○ e.g. type A int ● 型の同一性においての言語仕様はType identityに定義されている ○ 2つの型のどちらかがdefined typeであれば結果は常に異なる ○ 2つの型のいずれもdefined typeでない場合、同一の場合がある Goにおける型の同一性

Slide 219

Slide 219 text

Q. 以下のコードの型は同一でしょうか? type MyInt int var a MyInt var b MyInt a == b Goにおける型の同一性

Slide 220

Slide 220 text

A. 同一である type MyInt int var a MyInt var b MyInt a == b Goにおける型の同一性

Slide 221

Slide 221 text

Q. 以下のコードの型は同一でしょうか? type MyInt int type MyMyInt int var a MyInt var b MyMyInt a == b Goにおける型の同一性

Slide 222

Slide 222 text

A. 同一ではない type MyInt int type MyMyInt int var a MyInt var b MyMyInt a == b Goにおける型の同一性

Slide 223

Slide 223 text

Q. 以下のコードの型は同一でしょうか? var a struct { ID int Name string } var b struct { ID int `json:”id”` Name string `json:”name”` } var c struct { Name string ID int } Goにおける型の同一性 A. a == b B. b == c C. a == c

Slide 224

Slide 224 text

A. どれも同一ではない var a struct { ID int Name string } var b struct { ID int `json:”id”` Name string `json:”name”` } var c struct { Name string ID int } Goにおける型の同一性 A. a == b B. b == c C. a == c

Slide 225

Slide 225 text

● interface型に受け入れる型とメソッドを列挙する type A interface { string | int String() string } Goの型制約の作り方

Slide 226

Slide 226 text

● defined typeを書きたい時も全部列挙する? type MyInt int type MyMyInt int type A interface { string | int | MyInt | MyMyInt String() string } Goの型制約の作り方

Slide 227

Slide 227 text

● 結局はint型なんだからまとめたい!!! type MyInt int type MyMyInt int type A interface { string | int | MyInt | MyMyInt String() string } Goの型制約の作り方

Slide 228

Slide 228 text

● ~を使うことでunderlying typeが同じであるものを受けいける型制約を作れる type MyInt int type MyMyInt int type A interface { string | ~int String() string } Goの型制約の作り方

Slide 229

Slide 229 text

● Goの全ての型は対応するunderlying typeをもつ ● 1つの型に対して必ずunderlying typeは1つしか存在しない Goのunderlying type

Slide 230

Slide 230 text

type T Xと宣言された場合 ● Tがboolean、数値型、文字列型のような型リテラルの場合はTのunderlying typeはTになる ● それ以外の場合はTのunderlying typeはXのunderlying typeになる Goのunderlying type

Slide 231

Slide 231 text

Q. intのunderlying typeは? Q. type MyInt intの時のMyIntのunderlying typeは? Q. type MyMyInt MyIntの時のMyMyIntのunderlying typeは? Goのunderlying type

Slide 232

Slide 232 text

Go1.17まで type Stringer interface { String() string } func fn(args []Stringer) []string { // } type MyInt int func (myint MyInt) String() string { return strconv.Itoa(int(i)) } func main() { myints := []MyInt{0, 1, 2} fn(myints) } Genericsのユースケース MyIntはStringerを満たしているが[]MyIntは []Stringerと同一ではないため[]Stringerに castする処理が必要

Slide 233

Slide 233 text

Go1.18から type Stringer interface { String() string } func fn[T Stringer](args []T) []string { // } type MyInt int func (myint MyInt) String() string { return strconv.Itoa(int(i)) } func main() { myints := []MyInt{0, 1, 2} fn(myints) } Genericsのユースケース Genericsによりcastの処理が不要になった

Slide 234

Slide 234 text

● GoのGenericsはCT(Compile Time)で解決できない場合はビルドに失敗する ○ C++はRuntimeのときに探索し存在しなければStackTraceでRuntime Errorが出力される ● 必要以上にGenericsを導入しない ○ 必要以上にGenericsを入れることで逆にコードの品質を下げる可能性もある Genericsのポイント