- 投稿日:2019-03-24T23:16:24+09:00
GolangでクロスプラットフォームGUIアプリを作る
GolangでGUIのクロスプラットフォームアプリを作る
fyne-io/fyne
というOpenGLを使うライブラリを使います。
この間1.0が出たみたいです。僕はGolang Weeklyのツイートで知りました。
Fyne 1.0: A Cross Platform GUI Development Toolkit - https://t.co/Ag2EK2wkwA
— Golang Weekly (@golangweekly) 2019年3月22日簡単なタイマーを作ってみる
こんな感じのものを作ります。
リポジトリ
コード
OpenGLを触ったことがないのでC++とかで使うOpenGLがどうなのかはわからないですが、
Canbasを作ってその上にWidgetを乗せてくみたいな感じでとてもわかりやすいなと思います
雰囲気Flutterぽいなと思いました。package main import ( "fmt" "time" "fyne.io/fyne" "fyne.io/fyne/app" "fyne.io/fyne/widget" "github.com/nozo-moto/pomodoro_timer/timer" ) var ( showTimeLabel widget.Label ) const ( countTime = time.Second * 10 ) func init() { showTimeLabel.Alignment = fyne.TextAlignCenter } func main() { showTimeChan := make(chan time.Duration) timer := timer.New(countTime, showTimeChan) go timer.Run() go func() { for { select { case showtime := <-showTimeChan: showTimeLabel.SetText(formatTime(showtime)) } } }() startButton := &widget.Button{ Text: "Start", OnTapped: func() { timer.Start() }, } stopButton := &widget.Button{ Text: "Stop", OnTapped: func() { timer.Stop() }, } a := app.New() w := a.NewWindow("Timer") w.Resize(fyne.Size{Width: 150, Height: 150}) canvasObjects := []fyne.CanvasObject{ &widget.Label{Text: "Timer", Alignment: fyne.TextAlignCenter}, &showTimeLabel, startButton, stopButton, } w.SetContent(&widget.Box{Children: canvasObjects}) timer.Initialize(countTime) w.ShowAndRun() } func formatTime(t time.Duration) string { h := t / time.Hour m := (t - h*time.Hour) / time.Minute s := (t - h*time.Hour - m*time.Minute) / time.Second return fmt.Sprintf("%d:%d:%d", h, m, s) }timer.go
package timer import ( "time" ) const timerTime = 1 * time.Second type Timer struct { startTime time.Time showTimeChan chan time.Duration CountTime time.Duration } func New(countTime time.Duration, showTime chan time.Duration) *Timer { return &Timer{ startTime: time.Time{}, showTimeChan: showTime, CountTime: countTime, } } func (t *Timer) Run() { timeTicker := time.NewTicker(timerTime) for { select { case <-timeTicker.C: if t.startTime != (time.Time{}) { showtime := t.CountTime - time.Now().Sub(t.startTime) if showtime < 0 { t.Stop() break } t.showTimeChan <- showtime } } } } func (t *Timer) Start() { t.startTime = time.Now() } func (t *Timer) Stop() { t.startTime = time.Time{} t.showTimeChan <- 0 } func (t *Timer) Initialize(initalTimer time.Duration) { t.showTimeChan <- initalTimer }その他
タイマーが終ったのを通知させたり、音楽鳴らしたりしたかったができなかった
通知はRoadmapに乗ってるのでそのうちできるんじゃないかなと思います
https://github.com/fyne-io/fyne/wiki/Roadmap音楽は
の記事で書いたやり方を試したがダメだったので他のやり方を試してみる
雑感
GUIクロスプラットフォームアプリだとElectronくらいしかなさそうな上に、大変なのでサクッと作るならこっちの方が楽だなといった印象です。
- 投稿日:2019-03-24T22:36:22+09:00
Golangで音声ファイルを鳴らす
Golangで音声ファイルを鳴らす
Goで音声ファイルを鳴らします
github.com/faiface/beepを使いますコード
package main import ( "os" "time" "github.com/faiface/beep" "github.com/faiface/beep/mp3" "github.com/faiface/beep/speaker" ) func main() { if len(os.Args) <= 1 { panic("need music file") } f, err := os.Open(os.Args[1]) if err != nil { panic(err) } streamer, format, err := mp3.Decode(f) if err != nil { panic(err) } defer streamer.Close() if err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)); err != nil { panic(err) } done := make(chan bool) speaker.Play(beep.Seq( streamer, beep.Callback( func() { done <- true }, ), )) <-done }気になりそうなところ
Callbackを渡してるところ
speaker.Play(streamer)だとあんまり綺麗にできなくて// 音が聞こえる前に終わる :cry: speaker.Play(streamer) // ハングさせる // 終了を受け取れない :thinking: speaker.Play(streamer) select {} // Callbackでchannelで終了を通知するようにする :ok_woman: speaker.Play(beep.Seq( streamer, beep.Callback( func() { done <- true }, ), ))他の拡張子は?
MP3/WAV/OGG/FLACが(メジャーなものだと)使えるらしい
wavの場合
"github.com/faiface/beep/wav"
をインポートして
wav.Decode(f)でできる参考
- 投稿日:2019-03-24T20:45:30+09:00
enum値を引数に持つメソッドを持つstructのInterfaceを作るコツ
golangではライブラリ自体はinterfaceを提供しておらず、使う側で必要になったら作れという風潮があると思ってます。golangのinterfaceはダックタイプなので、概ねはそれで上手く行きます。
ただ、引数にenum値がある場合、素直にメソッドのシグネチャをコピペするとinterface自体が実装に依存してしまうので一工夫が必要です。
やってることはinterfaceにも同じenumを作りましょうというだけなのでなんということはないのですが、DIとかに慣れてない人はちょっと嵌りそうな気がします。実装例は以下です。参考になれば幸いです。
package main import "fmt" // 例えば、以下のようなinterface化したいライブラリ(?)があったとする。 type Hoge struct { a int } type Mode int const ( ModeOne = iota ModeTwo ) func NewHoge(a int) *Hoge { return &Hoge{a: a} } func (h Hoge) Mul(m Mode) int { return h.a * int(m) } // ここから下はインターフェース type MulerMode int const ( MulerModeOne = iota MulerModeTwo ) // 普通にやるとMulの引数の型にModeを指定してしまうが、そうするとInterfaceが実装に依存してしまう // ここでは同じ値域のenum、MulerModeをinterfaceとセットで用意して回避する type Muler interface { Mul(m MulerMode) int } // ここから下はインターフェースを通じてHogeを使いたい場合 type MulerImpl struct { Hoge } func NewMulerImpl(a int) *MulerImpl { return &MulerImpl{Hoge: Hoge{a: a}} } func (m *MulerImpl) Mul(mm MulerMode) int { var md Mode // ここでenum同士のマッピングをする switch mm { case MulerModeOne: md = ModeOne case MulerModeTwo: md = ModeTwo default: return 0 // 本当はエラーとかだと思う } return m.Hoge.Mul(md) } // 実際に呼び出す func main() { var m Muler m = NewMulerImpl(1) fmt.Print(m.Mul(MulerModeOne)) }
- 投稿日:2019-03-24T18:34:14+09:00
プライベートレポジトリを含むGAE/Go(go111)をCloud Buildで自動でプロイする
はじめに
go111の登場で、Cloud Build を使えば設定ファイル1つで自動デプロイ出来るようになったはずでした。cloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/gcloud' args: ['app', 'deploy', '--project=$PROJECT_ID', 'gae/app/app.yaml']monorepo ならプライベートでも上記の設定でデプロイ出来ます。
しかし manyrepo で private repository を go get するとなると一工夫必要に。
先に go mod vendor する
まず、Cloud Build で
go111する際、我々が Cloud Build のトリガーに設定するプロセス(以下「Cloud Build(Trigger)」)とは別に、gcloud app deployによって実行される Cloud Build のプロセスがあり(以下「Cloud Build(GAE)」)、go.modを含む deploy、つまり.gcloudignoreにgo.modがない場合は Cloud Build(GAE) が依存解決もしてくれます。非常に素敵なのですが、sshの鍵だったりトークンだったり何も設定できないため、プライベートレポジトリが
go.modに含まれると失敗します。じゃあどうする
Cloud Build(Trigger) 側で
go mod vendorし、更に.gcloudignoreにgo.modを指定してgcloud app deployすればよいのです、vendor ディレクトリごと GCS にアップロードするので、.gcloudignoreにvendorを含めてはいけません。まとめ
- go111 のプロジェクトを
gcloud app deployすると Cloud Build による deploy が実行される- この際に実行される Cloud Build に対しては何も設定出来ない
go.modを含む場合、自動で go get し、そこにプライベートレポジトリがあると失敗する.gcloudignoreでgo.modを除外指定していれば自動で go get されない- そのかわり、
vendorを.gcloudignoreに指定せず、go mod vendor後にgcloud app deployするvendorディレクトリごと Upload され、go getも行われないので deploy は成功する- 一連の作業を Cloud Build で自動化する為の
cloudbuild.yamlがこちらcloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/go:alpine' entrypoint: sh args: - -c - | mv `find /workspace -maxdepth 1 ! -name go` ./ git config --global url."https://$$GITHUB_TOKEN:x-oauth-basic@github.com/".insteadOf "https://github.com/" go mod vendor find /workspace dir: 'go/src/github.com/YOUR_ORG/YOUR_PROJECT' env: - 'GOPATH=/workspace/go' - 'GO111MODULE=on' secretEnv: ['GITHUB_TOKEN'] - name: 'gcr.io/cloud-builders/gcloud' args: - 'app' - 'deploy' - '--project=$PROJECT_ID' - 'app/app.yaml' dir: 'go/src/github.com/YOUR_ORG/YOUR_PROJECT' env: - 'GOPATH=/workspace/go' - 'GO111MODULE=off' secrets: - kmsKeyName: 'projects/YOUR_GCP_PROJECT_ID/locations/global/keyRings/github-tokens/cryptoKeys/github-deploy-token' secretEnv: GITHUB_TOKEN: 'YOUR_ENCRYPTED_GITHUB_TOKEN'
dirやmvなどでGOPATHに則ったディレクトリ構成にしています。
kmsKeyNameに変数埋め込みは利用できないのでべた書きしてください、ご注意を。.gcloudignore はこんな感じに
.gcloudignore.gcloudignore .git .gitignore go.mod go.sumYOUR_ENCRYPTED_GITHUB_TOKEN の作り方
https://github.com/settings/tokens でトークンを生成し、以下の様に暗号、前述 yaml に貼り付ける
gcloud kms keyrings create github-tokens --location=global --project=YOUR_GCP_PROJECT_ID gcloud kms keys create github-deploy-token --location=global --project=YOUR_GCP_PROJECT_ID --keyring=github-tokens --purpose=encryption echo -n $GITHUB_TOKEN | gcloud kms encrypt \ --plaintext-file=- \ --ciphertext-file=- \ --location=global \ --project=YOUR_GCP_PROJECT_ID \ --keyring=github-tokens \ --key=github-deploy-token | base64
- 投稿日:2019-03-24T18:34:14+09:00
依存にプライベートレポジトリを含むGAE/Go(go111)をCloud Buildで自動でプロイする
はじめに
go111の登場で、Cloud Build を使えば設定ファイル1つで自動デプロイ出来るようになったはずでした。cloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/gcloud' args: ['app', 'deploy', '--project=$PROJECT_ID', 'gae/app/app.yaml']monorepo なら private repository でも上記の設定でデプロイ出来ます。
しかし、manyrepo をやろうと本体と別の private repository を参照するとデプロイ出来なくなります。我々が Cloud Build のトリガーに設定するプロセス(以下「Cloud Build(Trigger)」)とは別に、
gcloud app deployによって実行される Cloud Build のプロセス(以下「Cloud Build(GAE)」)があり、後者はgo.modを見つけると依存を自動解決します。非常に素敵なのですが、sshの鍵だったりトークンだったり何も設定できないため、プライベートレポジトリが
go.modに含まれると必ず失敗します。前段でgo mod vendorし、go.modをdeployしない
Cloud Build(Trigger) 側で
go mod vendorし、更に.gcloudignoreにgo.modを指定してgcloud app deployすれば解決します。
vendor ディレクトリごと GCS にアップロードするので、.gcloudignoreにvendorを含めてはいけません。まとめ
- go111 のプロジェクトを
gcloud app deployすると Cloud Build による deploy が実行される- この際に実行される Cloud Build に対しては何も設定出来ない
go.modを含む場合、自動で go get し、そこにプライベートレポジトリがあると失敗する.gcloudignoreでgo.modを除外指定していれば自動で go get されない- そのかわり、
vendorを.gcloudignoreに指定せず、go mod vendor後にgcloud app deployするvendorディレクトリごと Upload され、go getも行われないので deploy は成功する- 一連の作業を Cloud Build で自動化する為の
cloudbuild.yamlがこちらcloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/go:alpine' entrypoint: sh args: - -c - | mv `find /workspace -maxdepth 1 ! -name go` ./ git config --global url."https://$$GITHUB_TOKEN:x-oauth-basic@github.com/".insteadOf "https://github.com/" go mod vendor find /workspace dir: 'go/src/github.com/YOUR_ORG/YOUR_PROJECT' env: - 'GOPATH=/workspace/go' - 'GO111MODULE=on' secretEnv: ['GITHUB_TOKEN'] - name: 'gcr.io/cloud-builders/gcloud' args: - 'app' - 'deploy' - '--project=$PROJECT_ID' - 'app/app.yaml' dir: 'go/src/github.com/YOUR_ORG/YOUR_PROJECT' env: - 'GOPATH=/workspace/go' - 'GO111MODULE=off' secrets: - kmsKeyName: 'projects/YOUR_GCP_PROJECT_ID/locations/global/keyRings/github-tokens/cryptoKeys/github-deploy-token' secretEnv: GITHUB_TOKEN: 'YOUR_ENCRYPTED_GITHUB_TOKEN'
dirやmvなどでGOPATHに則ったディレクトリ構成にしています。
kmsKeyNameに変数埋め込みは利用できないのでべた書きしてください、ご注意を。.gcloudignore はこんな感じに
.gcloudignore.gcloudignore .git .gitignore go.mod go.sumYOUR_ENCRYPTED_GITHUB_TOKEN の作り方
https://github.com/settings/tokens でトークンを生成し、以下の様に暗号、前述 yaml に貼り付ける
gcloud kms keyrings create github-tokens --location=global --project=YOUR_GCP_PROJECT_ID gcloud kms keys create github-deploy-token --location=global --project=YOUR_GCP_PROJECT_ID --keyring=github-tokens --purpose=encryption echo -n $GITHUB_TOKEN | gcloud kms encrypt \ --plaintext-file=- \ --ciphertext-file=- \ --location=global \ --project=YOUR_GCP_PROJECT_ID \ --keyring=github-tokens \ --key=github-deploy-token | base64
- 投稿日:2019-03-24T16:00:36+09:00
GoでGraphQLのMutationを実装する
このエントリーではGolang + MongoDBでGraphQLを使ってみるに書きつぐ形で、GraphQLのMutationについての説明を行います。
一度改めて、まとめを兼ねたGraphQLの説明をします。
GraphQLとは
GraphQLでは、これまでRESTfulで作ってきたように複数のエンドポイントを作成して通信を行う方法を行いません。
エンドポイントは最低一つだけで動くようになるのがGraphQLです。基本的にはGraphQLの利点は、
・エンドポイント一つだけで良い
・型安全にデータベースとやりとりができるの二点を心に留めておけば問題ないかと思います(補足があれば喜んでいただきます)。
さらっとした説明ですが、ここからは実装に関するお話。
と言ってもコードをつらつら書くわけではなく、概念的なものを考えていきます。GraphQLの実装の考え方
まず、GraphQLの実装の流れは以下のような雰囲気で感じ取ってもらえればと思います(この流れはGolangに限らず一般的なものだと思いますたぶん)。
- GraphQLで扱うTypeの定義(GraphQLが扱うデータの設定)
- Fieldをデータの取得や作成の用途に合わせて定義(ここでTypeを利用する)
- Fieldをまとめてスキーマを作る
- GraphQLのリクエストを作成(GraphQLがデータベースに問い合わせる要求)
- スキーマとリクエストを合わせてGraphQLを実行
- 指定したデータが戻ってくる
Golang + MongoDBでGraphQLを使ってみるにも書きましたが、このリクエストには二つの概念が含まれており、その一つがこのエントリーのタイトルとなっているMutation(以下ミューテーション)なのです。
*リクエストは公式で使われている言葉ではありませんが、この方が伝わりやすいかなと思ったのでこの表現を使っています。
*流れの4に「データベースと問い合わせる要求」と書いていますが、GraphQLが直接的にデータベースとやりとりすることはありません(実装の仕方によってはデータベースと直接やりとりするかもしれませんが。。。)。GraphQLは飽くまで、データベースから取り出されたデータ群に対して実行されます。例えば、特定のUserを取得するGraphQLのクエリーを実施すると、それはすでにデータベースから取得されたUserテーブルの全てのレコード(ここでは取り出されてから構造体の配列に変換しています)に対して行われます。リクエストにはクエリーとミューテーションが含まれます。
クエリー(いままでのGETのようなもの)・・・データの取得要求
ミューテーション(いままでのPOSTのようなもの)・・・データの更新要求前回の記事ではクエリーを利用したデータの取得に関するGraphQLの使い方を解説しましたが、ここではミューテーションを扱って、データをInsertする方法を解説します。
(あとあとUpdateやDeleteに関しても書きつぐだろうと思います)Golang + GraphQLでMutationを実装する
データベースはNoSQLのMongoDBを使います。
前回の記事では、GraphQLのスキーマの定義箇所のコードが以下のようになっていました。
rootQuery := graphql.ObjectConfig{ Name: "RootQuery", Fields: fields.UsersField, } schemaConfig := graphql.SchemaConfig{ Query: graphql.NewObject(rootQuery), // Query } schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) }これがミューテーションを使う場合には以下のようになります。
rootQuery := graphql.ObjectConfig{ Name: "RootQuery", Fields: fields.UsersField, } rootMutation := graphql.ObjectConfig{ Name: "RootMutation", Fields: fields.CreateUserField, } schemaConfig := graphql.SchemaConfig{ Query: graphql.NewObject(rootQuery), // Query Mutation: graphql.NewObject(rootMutation), // Mutation } schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) }rootMutation変数が作成され、SchemaConfigのMutationにこれを新しいGraphQLのオブジェクトとして追加しています。
*前回の記事では同じファイルにfieldsを定義していましたが、今回はファイルを分けているので、fields.UsersFieldやfields.CreateUserFieldのように書いています。それでは、rootMutationの中身を説明していきます。
それほど複雑ではないので、楽に感じるかもしれません。rootMutationはfields.CreateUserField(ここではfields/user.goにあるCreateUserField変数)をFieldsとして設定しています。
// fields/user.go var CreateUserField = graphql.Fields{ "createUser": &graphql.Field{ Type: types.UserType, Description: "Create new user", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "email": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "password": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { name, _ := params.Args["name"].(string) email, _ := params.Args["email"].(string) password, _ := params.Args["password"].(string) database.CreateNewUser(name, email, password) return nil, nil }, }, }クエリーで特定のユーザーを取得するときは、冒頭が"user"で始まっていましたが、ここでは"createUser"となっています。
ここの名前はあとでリクエストを送るときに利用します。Argsについてもクエリーの時と同じように理解していただければ良いです。リクエストを出すときに指定できる要素です。
コードでは、name, email, passwordとなっているので、リクエストを出すときにはこれらの要素を設定できるということです。POSTのリクエストとしては普通のことですね。最後から六行目の
database.CreateNewUser(name, email, password)は、データベースとやりとりする箇所です。
ここではファイルを外出ししているので、外にあるdatabaseディレクトリのuser.goファイルにデータベースとのやりとりをしているコードがあるということです。
database/user.goは以下の通りです。// database/user.go func CreateNewUser(name string, email string, password string) { // connection to mongoDB session, _ := mgo.Dial("mongodb://localhost/test") defer session.Close() db := session.DB("test") // insert newUser := &User{ Id: bson.NewObjectId(), Name: name, Email: email, Password: password, } col := db.C("users") if err := col.Insert(newUser); err != nil { log.Fatalln(err) } }ここはMongoDBでのInsert方法を理解していれば問題なく読み取れると思います。
func (*Collection) Insertはい、これでミューテーションの処理は終わりです。
ミューテーションの中の動きは以上となります実装されたMutationを利用する
ミューテーションの機能を利用するには、それを実行するためのリクエストを送る必要があります。
クエリーのリクエストは以下のようなものでした。
request := ` { user(id: "5c94f4d7e803694b2d09da75") { id name email password } } `これをミューテーションのリクエストに書き換えると、
request := ` mutation { createUser(name: "name", email: "email", password: "pass") { name email password } } `fields/user.goのcreateUserFieldは"createUser"という名前で定義したので、ここでcreateUserでミューテーションを書いています。
そしてその引数で作成する新しいデータの要素を指定します。それらの要素はcreateUserFieldのArgsのそれぞれに対応しており、それらがResolveの関数に渡ってInsertが実施されるわけです。
案外簡単な話だと思います。
mongoでデータベースに入ってdb.users.find()とかで中身を覗くと、作成したドキュメントが追加されていると思います。
入ってなかったらちょっとググってみてください笑まあ、GraphQLでは動き方がわかればだいたいの実装の方法がわかってくるようになると思います。
それと、実際にコードを書いて動かしてみることも大切ですね間違った表現や説明をしている箇所があれば、忌憚なくご指摘をお願いいたします。
- 投稿日:2019-03-24T16:00:36+09:00
GolangでGraphQLのMutationを実装する
このエントリーではGolang + MongoDBでGraphQLを使ってみるに書きつぐ形で、GraphQLのMutationについての説明を行います。
一度改めて、まとめを兼ねたGraphQLの説明をします。
GraphQLとは
GraphQLでは、これまでRESTfulで作ってきたように複数のエンドポイントを作成して通信を行う方法を行いません。
エンドポイントは最低一つだけで動くようになるのがGraphQLです。基本的にはGraphQLの利点は、
・エンドポイント一つだけで良い
・型安全にデータベースとやりとりができるの二点を心に留めておけば問題ないかと思います(補足があれば喜んでいただきます)。
さらっとした説明ですが、ここからは実装に関するお話。
と言ってもコードをつらつら書くわけではなく、概念的なものを考えていきます。GraphQLの実装の考え方
まず、GraphQLの実装の流れは以下のような雰囲気で感じ取ってもらえればと思います(この流れはGolangに限らず一般的なものだと思いますたぶん)。
- GraphQLで扱うTypeの定義(GraphQLが扱うデータの設定)
- Fieldをデータの取得や作成の用途に合わせて定義(ここでTypeを利用する)
- Fieldをまとめてスキーマを作る
- GraphQLのリクエストを作成(GraphQLがデータベースに問い合わせる要求)
- スキーマとリクエストを合わせてGraphQLを実行
- 指定したデータが戻ってくる
Golang + MongoDBでGraphQLを使ってみるにも書きましたが、このリクエストには二つの概念が含まれており、その一つがこのエントリーのタイトルとなっているMutation(以下ミューテーション)なのです。
*リクエストは公式で使われている言葉ではありませんが、この方が伝わりやすいかなと思ったのでこの表現を使っています。
*流れの4に「データベースと問い合わせる要求」と書いていますが、GraphQLが直接的にデータベースとやりとりすることはありません(実装の仕方によってはデータベースと直接やりとりするかもしれませんが。。。)。GraphQLは飽くまで、データベースから取り出されたデータ群に対して実行されます。例えば、特定のUserを取得するGraphQLのクエリーを実施すると、それはすでにデータベースから取得されたUserテーブルの全てのレコード(ここでは取り出されてから構造体の配列に変換しています)に対して行われます。リクエストにはクエリーとミューテーションが含まれます。
クエリー(いままでのGETのようなもの)・・・
ミューテーション(いままでのPOSTのようなもの)・・・前回の記事ではクエリーを利用したデータの取得に関するGraphQLの使い方を解説しましたが、ここではミューテーションを扱って、データをInsertする方法を解説します。
(あとあとUpdateやDeleteに関しても書きつぐだろうと思います)Golang + GraphQLでMutationを実装する
データベースはNoSQLのMongoDBを使います。
前回の記事では、GraphQLのスキーマの定義箇所のコードが以下のようになっていました。
rootQuery := graphql.ObjectConfig{ Name: "RootQuery", Fields: fields.UsersField, } schemaConfig := graphql.SchemaConfig{ Query: graphql.NewObject(rootQuery), // Query } schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) }これがミューテーションを使う場合には以下のようになります。
rootQuery := graphql.ObjectConfig{ Name: "RootQuery", Fields: fields.UsersField, } rootMutation := graphql.ObjectConfig{ Name: "RootMutation", Fields: fields.CreateUserField, } schemaConfig := graphql.SchemaConfig{ Query: graphql.NewObject(rootQuery), // Query Mutation: graphql.NewObject(rootMutation), // Mutation } schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) }rootMutation変数が作成され、SchemaConfigのMutationにこれを新しいGraphQLのオブジェクトとして追加しています。
*前回の記事では同じファイルにfieldsを定義していましたが、今回はファイルを分けているので、fields.UsersFieldやfields.CreateUserFieldのように書いています。それでは、rootMutationの中身を説明していきます。
それほど複雑ではないので、楽に感じるかもしれません。rootMutationはfields.CreateUserField(ここではfields/user.goにあるCreateUserField変数)をFieldsとして設定しています。
// fields/user.go var CreateUserField = graphql.Fields{ "createUser": &graphql.Field{ Type: types.UserType, Description: "Create new user", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "email": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "password": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { name, _ := params.Args["name"].(string) email, _ := params.Args["email"].(string) password, _ := params.Args["password"].(string) database.CreateNewUser(name, email, password) return nil, nil }, }, }クエリーで特定のユーザーを取得するときは、冒頭が"user"で始まっていましたが、ここでは"createUser"となっています。
ここの名前はあとでリクエストを送るときに利用します。Argsについてもクエリーの時と同じように理解していただければ良いです。リクエストを出すときに指定できる要素です。
コードでは、name, email, passwordとなっているので、リクエストを出すときにはこれらの要素を設定できるということです。POSTのリクエストとしては普通のことですね。最後から六行目の
database.CreateNewUser(name, email, password)は、データベースとやりとりする箇所です。
ここではファイルを外出ししているので、外にあるdatabaseディレクトリのuser.goファイルにデータベースとのやりとりをしているコードがあるということです。
database/user.goは以下の通りです。// database/user.go func CreateNewUser(name string, email string, password string) { // connection to mongoDB session, _ := mgo.Dial("mongodb://localhost/test") defer session.Close() db := session.DB("test") // insert newUser := &User{ Id: bson.NewObjectId(), Name: name, Email: email, Password: password, } col := db.C("users") if err := col.Insert(newUser); err != nil { log.Fatalln(err) } }ここはMongoDBでのInsert方法を理解していれば問題なく読み取れると思います。
func (*Collection) Insertはい、これでミューテーションの処理は終わりです。
ミューテーションの中の動きは以上となります実装されたMutationを利用する
ミューテーションの機能を利用するには、それを実行するためのリクエストを送る必要があります。
クエリーのリクエストは以下のようなものでした。
request := ` { user(id: "5c94f4d7e803694b2d09da75") { id name email password } } `これをミューテーションのリクエストに書き換えると、
request := ` mutation { createUser(name: "name", email: "email", password: "pass") { name email password } } `fields/user.goのcreateUserFieldは"createUser"という名前で定義したので、ここでcreateUserでミューテーションを書いています。
そしてその引数で作成する新しいデータの要素を指定します。それらの要素はcreateUserFieldのArgsのそれぞれに対応しており、それらがResolveの関数に渡ってInsertが実施されるわけです。
案外簡単な話だと思います。
mongoでデータベースに入ってdb.users.find()とかで中身を覗くと、作成したドキュメントが追加されていると思います。
入ってなかったらちょっとググってみてください笑まあ、GraphQLでは動き方がわかればだいたいの実装の方法がわかってくるようになると思います。
それと、実際にコードを書いて動かしてみることも大切ですね間違った表現や説明をしている箇所があれば、忌憚なくご指摘をお願いいたします。
- 投稿日:2019-03-24T15:12:53+09:00
Google App Engine で始める Go 実践入門 Part1
Google Cloud Plattform の PaaS である Google App Engine を用いて簡単なブログアプリを作りつつ、 Go 言語を学んでいこうという趣旨の記事です。
(ぶっちゃけた話、途中から Go の入門記事なのか Google App Engine の入門記事なのかわからない感じになってますがゆるしてください。)
ブログアプリ自体は簡単なものですが、記事タイトルに「実践」と含んでいますので、次のポイントも盛り込んだアプリに仕上げます。
- Circle CI で自動デプロイ
- Firebase で認証
- Visual Studio Code でリモートデバッグ
- O/Rマッパーに gorm
- ウェブフレームワークに Echo
記事が長くなってしまうので、Part1, Part2 の2部構成になります。
本記事 Part1 では以下の内容を扱います。
- 環境構築
- GCP プロジェクトの準備
- ローカル開発サーバーの起動
- リモートデバッグ
- Google App Engine へのデプロイ
ローカル環境構築
まずはローカル開発環境を構築します。OS は MacOS を想定しています。
準備するものは次の通りです。
- Xcode command line tool
- Homebrew
- Go
- Google Cloud SDK
- Cloud SQL Proxy
- Visual Studio Code
次のコマンドを実行してインストールをします。
# ------------------------ # Xcode command line tool # ------------------------ # インストール xcode-select --install # --------- # Homebrew # --------- # インストール /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" # --------- # Go # --------- # インストール brew install go # GOPATH の設定 echo 'export GOPATH=$HOME/go' >> ~/.bash_profile # PATH に GOPATH を追加 echo 'export PATH=$GOPATH/bin:$PATH' >> ~/.bash_profile # ----------------- # Google Cloud SDK # ----------------- # google-cloud-sdkのアーカイブをHomeディレクトリ配下にダウンロード curl -o ~/google-cloud-sdk.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-239.0.0-darwin-x86_64.tar.gz # Homeディレクトリ配下に解凍 tar xvzf ~/google-cloud-sdk.tar.gz -C ~/ # アーカイブを削除 rm -rf ~/google-cloud-sdk.tar.gz # インストールスクリプトを実行してパスを通す sh ~/google-cloud-sdk/install.sh -q --rc-path ~/.bash_profile # ログインスクリプトを再読み込み source ~/.bash_profile # SDKを最新版にアップデートする gcloud components update --quiet # Go 用の App Engine の拡張機能をインストールする gcloud components install app-engine-go --quiet # SDKのスクリプトに実行権限を付与 chmod +x ~/google-cloud-sdk/platform/google_appengine/* # ログインスクリプトに環境変数を追記して再読み込み echo 'export GOOGLE_CLOUD_SDK_ROOT=$HOME/google-cloud-sdk' >> ~/.bash_profile echo 'export PATH=$GOOGLE_CLOUD_SDK_ROOT/bin:$PATH' >> ~/.bash_profile echo 'export PATH=$GOOGLE_CLOUD_SDK_ROOT/platform/google_appengine:$PATH' >> ~/.bash_profile source ~/.bash_profile # ---------------- # Cloud SQL Proxy # ---------------- # Cloud SQL Proxy用のマウントフォルダを作成 if [ ! -d '/cloudsql' ]; then sudo mkdir /cloudsql ; fi # 権限を変更 if [ -d '/cloudsql' ]; then sudo chmod 777 /cloudsql ; fi # mac用cloud-sql-proxyのバイナリをダウンロードする # Linuxなどはこちらを参考に cloud.google.com/sql/docs/mysql/connect-admin-proxy curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64 # 権限を変更する sudo chmod 755 cloud_sql_proxy # パスの通っているディレクトリに移動させる mv cloud_sql_proxy /usr/local/bin/ # ------------------- # Visual Studio Code # ------------------- # インストール brew cask install visual-studio-codeGCP プロジェクトの作成
https://console.cloud.google.com/getting-started
上記の URL にアクセスして「プロジェクトの選択」 > [新しいプロジェクト] からプロジェクトを作成します。
GCP の利用が初めての人は無料トライアルへの申し込みも行っておくと良いです。
プロジェクトの作成が完了したら [プロジェクトの選択] > [すべて] > [(作成したプロジェクトの名称)] と選択して、プロジェクトのダッシュボードへと移動します。
App Engine アプリケーションの作成
プロジェクトのダッシュボードを開いた状態で [ナビゲーションメニュー] > [App Engine] > [ダッシュボード] と選択して、App Engine のダッシュボード画面へ移動します。
App Engine のダッシュボード画面が開けたら、 [アプリケーションを作成] > [asia-northeast1] > [アプリを作成] > [キャンセル] と選択して、東京リージョンで App Engine を有効化します。
GOPATH について
Go の開発は GOPATH と呼ばれるディレクトリ配下で行うことが標準的です。
GOPATH となるディレクトリの場所は自分自身で任意で決めて良いことになっていますが、$HOME/goをGOPATH に設定するのが一般的です。(Go 1.8+ ではデフォルトが$HOME/goとなっています。)
direnv 等のツールを用いてプロジェクトごとにカレントディレクトリを GOPATH に設定して開発を行うスタイルもありますが、今回は素直に$HOME/go配下で開発を行います。GOPATH 配下のディレクトリ構成は次のように定められています。
$GOPATH ├── bin ├── pkg └── srcこのディレクトリ構成を Node.js に例えると次のようになるかと思います。
- $GOPATH/bin はグローバルインストールした node_modules/bin が配置されるところ
- $GOPATH/pkg はビルドされたパッケージが配置されるところ (Nodeにはない概念)
- $GOPATH/src はグローバルインストールした node_modules と 自分のソースコードを配置するところ
src ディレクトリで実際に開発を行うことになりますが、src 直下にプロジェクトのルートディレクトリを配置するのではなく、ソースコードを管理しているリポジトリと紐づけて階層構造にするのが標準となっています。
$GOPATH ├── bin ├── pkg └── src └── [ホスティング先] └── [ユーザ名 or 組織名] └── [リポジトリ名] (ここがプロジェクトのルートディレクトリになる)例えば GitHub をホスティング先にしている私の場合は次のようになります。
$GOPATH ├── bin ├── pkg └── src └── github.com └── rema424 └── go-gae-blog-app-exampleプロジェクトルートを作成
それでは App Engine にデプロイするアプリケーションのプロジェクトディレクトリを作成していきます。
リモートリポジトリのホスティング先は GitHub を想定して話を進めます。まずは GitHub 上で新規リポジトリを作成します。
README.mdや.gitignoreも作成しておきましょう。リポジトリの作成が完了したら、リモートリポジトリをローカルにクローンします。
ディレクトリ名、ユーザ名、リポジトリ名は適宜自分のものに読み替えてください。# ディレクトリ作成 mkdir -p $GOPATH/src/github.com/rema424 # ディレクトリ移動 cd $_ # リポジトリのクローン git clone https://github.com/rema424/go-gae-blog-app-example.gitここまでの作業が完了すると、ディレクトリ構成は先ほどの例と同様となっているかと思います。
$GOPATH └── src └── github.com └── rema424 └── go-gae-blog-app-example ├── .gitignore └── README.mdパッケージ管理ツール dep のインストール
パッケージ管理ツールとして今回は
depを採用します。
Go 1.11 から導入されたgo modでもいいとは思いますが、今回はdepで。
depは次のコマンドでインストールします。カレントディレクトリの位置はどこでも構いません。go get -u -v github.com/golang/dep/cmd/dep次にパッケージ管理の初期化を行います。ディレクトリ名は自分のもので読み替えてください。
# プロジェクトルートに移動 cd $GOPATH/src/github.com/rema424/go-gae-blog-app-example # パッケージ管理の初期化 dep initここまでが完了すると、 GOPATH 配下は次のようになっているかと思います。
$GOPATH ├── bin │ └── dep └── src └── github.com ├── golang │ └── dep └── rema424 └── go-gae-blog-app-example ├── vendor ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml └── README.mdApp Engine のディレクトリ構成
開発作業に入る前に App Engine 開発におけるディレクトリ構成について説明します。
アプリケーションのディレクトリ構成は App Engine の場合少し特殊になります。ディレクトリ構成の一例を記載していますが、あくまでも一例となります。
この構成が絶対ではありません。イメージが膨らみやすいように少しファイルも記載しています。
go-gae-blog-app-example ├── lib ├── model │ ├── Article.go │ ├── Comment.go │ └── User.go ├── module │ ├── admin │ │ ├── handler │ │ │ ├── article_handler.go │ │ │ └── user_handler.go │ │ └── main │ │ ├── node_modules │ │ ├── static │ │ │ ├── dist │ │ │ ├── public │ │ │ └── src │ │ ├── template │ │ ├── app.yaml │ │ ├── db.go │ │ ├── main.go │ │ └── package.json │ └── blog │ ├── handler │ │ ├── article_handler.go │ │ └── user_handler.go │ └── main │ ├── node_modules │ ├── static │ │ ├── dist │ │ ├── public │ │ └── src │ ├── template │ ├── app.yaml │ ├── db.go │ ├── main.go │ └── package.json ├── repository │ ├── article_repository.go │ ├── comment_repository.go │ └── user_repository.go ├── service │ ├── article_service.go │ ├── auth_service.go │ └── user_service.go ├── vendor ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml └── README.mdApp Engine の特殊な点として module という概念があります。
module は昔の呼び方で今は service に変わっていますが、service ディレクトリと名称が被ってしまうので旧名の module で話を進めます。この module とは何かというと、 1 つ 1 つのアプリケーションだと認識して構いません。
App Engine におけるプロジェクトとモジュールの関係を整理すると次のようになります。
- プロジェクト ≠ アプリケーション
- モジュール = アプリケーション
- プロジェクトは複数のモジュール(アプリケーション)を持つ ( 1 つ以上)
- 課金はプロジェクト単位
「プロジェクトは複数のモジュール(アプリケーション)を持つ ( 1 つ以上)」というのが一番のポイントであるかと思います。
例えば「ブログプロジェクト」の中で「ブログサイト」と「管理者用サイト」を同時に開発するといったスタイルです。
もちろん「ブログプロジェクト」の中で「ブログサイト」を開発し、「ブログ管理者プロジェクト」の中で「管理者用サイト」を開発するといったように、プロジェクトを分けるという選択肢もあります。
ここらへんは分断されたモノリス、整理されたモノリス、マイクロサービスといった話になってくるので状況によるかと思います。
先ほどのディレクトリ構成は整理されたモノリスを目指した構成になっています。
ドメイン領域のソースコードをプロジェクト内で共通化し、HTTPの処理や認可/認証の処理はモジュールごとにハンドリングするといった形です。Visual Studio Code で Go の開発準備
Visual Studio Code (以下 VS Code) に Go の拡張機能を導入することによってコーディングの生産性を上げることができます。
Go の拡張機能は次の手順で導入ができます。
[VS Code を起動] > [Command + P] > [ext install ms-vscode.go] > [Enter]次に Linter や Debugger などのツールを導入していきます。
通常はgo getコマンドでインストールするのですが、Go 拡張機能を使うと簡単にツール一式をインストールすることが可能です。[VS Code を起動] > [F1] > [>Go: Install/Update Tools] > [チェックボックス全てにチェック] > [OK]インストールが完了すると、これらツールが GOPATH 配下にインストールされたことが分かります。
PATH に$GOPATH/binを追加しているので、これらコマンドはターミナルから呼び出すことができます。$GOPATH ├── bin │ ├── impl │ ├── guru │ ├── gotests │ ├── goreturns │ ├── gorename │ ├── goplay │ ├── gopkgs │ ├── gomodifytags │ ├── golint │ ├── godef │ ├── gocode-gomod │ ├── gocode │ ├── go-symbols │ ├── go-outline │ ├── fillstruct │ ├── dlv │ └── dep ├── pkg │ └── darwin_amd64 └── src ├── github.com │ ├── uudashr │ ├── stamblerre │ ├── sqs │ ├── skratchdot │ ├── rogpeppe │ ├── rema424 │ ├── ramya-rao-a │ ├── pkg │ ├── mdempsky │ ├── karrick │ ├── josharian │ ├── haya14busa │ ├── golang │ ├── go-delve │ ├── fatih │ ├── davidrjenni │ ├── cweill │ └── acroca └── golang.org └── x最後に VS Code の設定ファイルを作り、拡張機能をカスタマイズします。
Go 拡張機能では Format ツールがデフォルトではgoreturnsとなっていますが、depとの相性を考えてgofmtに変更します。まずは設定ファイルをプロジェクトディレクトリの中に作ります。
# ディレクトリ移動 cd $GOPATH/src/rema424/go-gae-blog-app-example # ディレクトリ作成 mkdir .vscode # 設定ファイル作成 touch .vscode/settings.jsonファイルが作成できたら、次の内容を書き込んで完了です。
.vscode/settings.json{ "go.formatTool": "gofmt", "editor.formatOnSave": true }Hello World
それでは実際にコーディング作業に入っていきます。
はじめに Hello World を表示してみます。ウェブフレームワークには Echo を採用します。
Echo のガイドに App Engine を利用する場合のレシピが載っているので参考にします。Google App Engine Recipe | Echo
以下、ターミナルでの作業はプロジェクトルートで行っていきます。
# ディレクトリ移動 cd $GOPATH/src/rema424/go-gae-blog-app-example # ディレクトリ作成 mkdir -p module/blog/main module/blog/handler # ディレクトリ移動 cd module/blog/main # ファイル作成 touch app.yaml app.go app-engine.go app-standalone.go # ディレクトリ移動 cd ../handler # ファイル作成 touch hello_handler.goこの時点でのディレクトリ構成は次の通りです。
go-gae-blog-app-example ├── .vscode │ └── settings.json └── module ├── blog │ ├── handler │ │ └── hello_handler.go │ └── main │ ├── app-engine.go │ ├── app-standalonde.go │ ├── app.go │ └── app.yaml ├── vendor ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml └── README.mdファイルにコードを書いていきます。
modue/blog/handler/hello_handler.gopackage handler import ( "net/http" "github.com/labstack/echo" ) type ( // Response ... Response struct { Message string } ) // HelloHandler ... func HelloHandler(c echo.Context) error { message := "Hello World!" res := &Response{ Message: message, } return c.JSON(http.StatusOK, res) } // ParrotHandler ... func ParrotHandler(c echo.Context) error { message := c.Param("message") res := &Response{ Message: message, } return c.JSON(http.StatusOK, res) }modue/blog/main/app.gopackage main import ( "github.com/rema424/go-gae-blog-app-example/module/blog/handler" ) var e = createMux() func init() { e.GET("/", handler.HelloHandler) e.GET("/:message", handler.ParrotHandler) }modue/blog/main/app-engine.go// +build appengine package main import ( "net/http" "github.com/labstack/echo" ) func createMux() *echo.Echo { e := echo.New() http.Handle("/", e) return e }modue/blog/main/app-standalone.go// +build !appengine package main import ( "github.com/labstack/echo" "github.com/labstack/echo/middleware" ) func createMux() *echo.Echo { e := echo.New() e.Use(middleware.Recover()) e.Use(middleware.Logger()) e.Use(middleware.Gzip()) return e } func main() { e.Logger.Fatal(e.Start(":8080")) }module/blog/main/app.yamlruntime: go api_version: go1.9 handlers: - url: /.* script: _go_app inbound_services: - warmupGo の
Build Constraints(もしくはBuild Tag) と呼ばれる機能を使っています。
これにより App Engine 環境でもその他の環境でも動作するコードになっています。Build に関する機能を使っているためアプリケーションの動作にはビルドが必要です。
go runでの起動はできないことに注意です。ソースコードが出来上がったら次に関連モジュールを
depでインストールしていきます。
depはソースコード上で import ブロックに記載されたモジュールを一括でインストールします。# ディレクトリ移動 cd $GOPATH/src/rema424/go-gae-blog-app-example # モジュールのインストール dep ensure -vモジュールが
vendorディレクトリ配下にインストールされているのが分かるかと思います。これらモジュールは git 管理する必要はないので、
.gitignoreに vendor を追加します。echo 'vendor' >> .gitignoreそれではローカル PC 環境でアプリケーションを起動します。
# app.yaml のあるモジュールルートに移動 cd module/blog/main/ # ビルドを実行 go build # バイナリを実行 ./mainアプリケーションが起動するので、ブラウザで
localhost:8080にアクセスすると Hello World! が表示されるはずです。App Engine ローカル開発サーバーの起動
App Engine はローカル開発サーバーを提供しています。
これにより「ローカルでは動いていたのに App Engine にデプロイしたら動かなかった」という可能性を小さくすることが可能となっています。
dev_appserver.py module/blog/main/app.yamlサーバーが立ち上がるので、
localhost:8080にアクセスすると Hello World! が表示されます。
以降の開発は App Engine ローカル開発サーバーで行うと良いでしょう。ホットリロード機能も付いています。
dev_appserver.pyでサーバーを起動すると、同時に Cloud Datastore Emulator も起動されます。
今回は Cloud Datastore は使わないので、次のようなオプションを指定することによりサーバーの起動を速めることができます。dev_appserver.py module/blog/main/app.yaml --support_datastore_emulator=False
--support_datastore_emulator=Falseなしで起動すると、app.yamlと同じディレクトリにindex.yamlというファイルが自動生成されますが、 Cloud Datastore は使わないので削除しておきましょう。削除しておかないと後のデプロイの工程で失敗します。
dev_appserver.pyではなくgoapp serveコマンドでも同様にサーバーを起動することができます。goapp serve module/blog/main/app.yaml
goapp serveコマンドはdev_appserver.pyのラッパーになっています。
ただし、オリジナルのdev_appserver.pyよりも利用できるオプションが少ないのでgoappは基本的には使わなくて良いかと思います。例えば、サーバー起動時に環境変数を渡すといったことが
dev_appserver.pyではできますがgoapp serveではできません。App Engine のローカル開発サーバーはホスト OS 上で export された環境変数は読み込みません。環境変数を利用するためにはサーバー起動時にオプションで渡すか、
app.yamlファイルに記載するかのどちらかになります。しかし、GitHub にアップできないような情報に対して環境変数を使いたいので、GitHub で管理したい
app.yamlには環境変数は定義できません。したがって、必然的にgoappではなくdev_appserver.pyを使うことになるかと思います。リモートデバッグ
VS Code と App Engine ローカル開発サーバーでリモートデバッグができるようにします。
ポイントは次の通りです。
- VS Code でデバッグ用の設定ファイルを作る
- サーバー起動時に
--go_debugging=Trueをオプションで渡す- Go の Debugger である
delveをインストールする (インストール済み)delveコマンドを用いて Debugger をサーバーにアタッチするまずは VS Code のデバッグ用設定ファイルを作成します。
touch .vscode/launch.json.vscode/launch.json{ "version": "0.2.0", "configurations": [ { "name": "Connect to server", "type": "go", "request": "launch", "mode": "remote", "remotePath": "${workspaceFolder}", "port": 2345, "host": "127.0.0.1", "program": "${workspaceFolder}", "env": {}, "args": [] } ] }次にサーバーを起動します。
dev_appserver.py module/blog/main/app.yaml --support_datastore_emulator=False --go_debugging=Trueサーバーが起動したらターミナルをもう 1 枚開いて Debugger をアタッチします。
VS Code であればターミナルの分割 (Command + Alt + Ctrl + \) を使うのが便利です。dlv attach $(ps u | grep _go_ap[p] | head -1 | awk '{print $2}') --headless --listen=127.0.0.1:2345 --api-version=2次に VS Code 上でブレークポイントを設置します。
デバッグを開始 (F5) します。
この状態でブラウザから
localhost:8080にアクセスすると、ブレークポイントでプログラムがストップします。ローカル開発用サーバーのオプション | Google Cloud
デプロイ
ローカル開発サーバーでの起動が出来たので、次はアプリケーションをクラウド上にデプロイします。
GCP プロジェクトを作成した際に使用した個人の Google アカウントで認証を通してデプロイすることもできますが、後の自動デプロイも見据え、デプロイ用サービスアカウントを作成して認証を通します。
サービスアカウントは次の手順で作成します。
まずは GCP のダッシュボードにアクセスし、そこからサービスアカウントの作成画面へ移動します。
[GCPプロジェクトダッシュボード] > [ナビゲーションメニュー] > [API とサービス] > [認証情報] > [認証情報を作成] > [サービスアカウントキー]サービスアカウントの作成に必要な情報を入力します。入力が完了したら [作成] をクリックします。
サービスアカウント: 新しいサービスアカウント サービスアカウント名: (任意) 役割: - App Engine サービス管理者 - App Engine デプロイ管理者 サービスアカウントID: (任意) キーのタイプ: JSON[作成] をクリックすると秘密鍵が PC にダウンロードされます。
プロジェクトルートディレクトリ直下にkeyという名称のディレクトリを作成し、秘密鍵をこのディレクトリ内にリネームしつつ移動させます。秘密鍵は GitHub 上にはアップしないので、
.gitignoreにkeyディレクトリを追加します。# ディレクトリ作成 mkdir key # 秘密鍵をリネームしつつ移動 mv /path/to/secret-key.json ./key/deploy.json # key ディレクトリを .gitignore に追記 echo 'key' >> .gitignore次に App Engine Admin API を有効化します。
[GCPプロジェクトダッシュボード] > [ナビゲーションメニュー] > [API とサービス] > [ライブラリ] > [App Engine Admin API] > [有効にする]デプロイを実施します。
プロジェクト ID と バージョン番号が必要になります。
プロジェクト ID はダッシュボードのプロジェクト選択画面から確認できます。バージョン番号は任意です。# 秘密鍵を使って認証を通す gcloud auth activate-service-account --key-file key/deploy.json # デプロイを実行する appcfg.py update -A <your-project-id> -V <version> --oauth2_access_token=$(gcloud auth print-access-token 2> /dev/null) module/blog/main
index.yamlファイルが残っている場合はここで 403 エラーが発生します。
index.yamlを削除するか、サービスアカウントに対して [Datastore インデックス管理者] の権限を付与することでエラーは出なくなります。デプロイが完了したら App Engine ダッシュボードの右上のリンクからアプリケショーンにアクセスできます。
2 回目以降のデプロイでは、トラフィックの移行といった作業も必要になります。
App Engine は Immutable Infrastructure を実践しているので、デプロイの度に新しいインスタンスが作成されます。そのため、ソースコードをデプロイするだけではなく、ルーティングを新しいインスタンスへと変更するといった工程も必要です。
少しソースコードを書き換えて再度デプロイしてみます。
バージョン番号は 1 回目のデプロイとは違うものにします。# デプロイを実行する appcfg.py update -A <your-project-id> -V <version> --oauth2_access_token=$(gcloud auth print-access-token 2> /dev/null) module/blog/main # ルーティングを切り替える appcfg.py migrate_traffic -A <your-project-id> -V <version> --oauth2_access_token=$(gcloud auth print-access-token 2> /dev/null) module/blog/main
migrate_trafficを実行すると、徐々にルーティングが切り替わっていきます。切り替わっていく様子は [App Engine ダッシュボード] > [バージョン] から確認できます。トラフィックの移行が完全に完了したら、再度ブラウザでアプリケーションにアクセスしてみます。
サービス アカウントを使用して承認する | Google Cloud
appcfg.py コマンドライン引数 | Google Cloud
プロジェクト アクセス権の付与おわりに
Part1 はこれにて以上です。
Prat2 では、Makefile を用いたタスク管理、Circle CI での自動デプロイ、データベース接続、機能の作り込みなど、より実践的な内容に入っていきます。
よかったら Twitter フォローしてね。@_rema424
- 投稿日:2019-03-24T12:02:35+09:00
はまりがちなGAE/Go(+dep)のデプロイを簡単3ステップで解決
gcloud app deployでエラー?
ディレクトリ構成
. ├── Gopkg.lock ├── Gopkg.toml ├── README.md ├── gae │ ├── handler.go ├── app │ ├── app.go │ ├── app.yaml │ ├── secret.yaml └── vendor ├── github.com └── golang.orgこの構成で
gcloud app deployするとエラーが出る?出たエラー
ERROR: (gcloud.app.deploy) Error Response: [9] Deployment contains files that cannot be compiled: Compile failed:
/work_dir/github.com/yuuis/gae/handler.go:12:2: can't find import: "github.com/nlopes/slack"なぜエラーが出るか
GAEがvendoringに対応していないから? (2019/03/12)
だけど、vendoringしながらデプロイもしたい!
対応していなくても、depを使って依存性管理しながらGAEにデプロイしたい!
depを使いつつデプロイする方法
ディレクトリ構成
. ├── Gopkg.lock ├── Gopkg.toml ├── README.md ├── gae │ ├── handler.go ├── app │ ├── app.go │ ├── app.yaml │ ├── secret.yaml ├── gopath │ ├── src -> ../vendor └── vendor ├── github.com └── golang.org簡単3ステップ
step1. ルートディレクトリ直下に新しく
gopathというディレクトリを作る``` $ mkdir gopath ```step2.
gopathディレクトリの下に、vendorを指すシンボリックリンクを作る``` $ cd gopath $ ln -s ../vendor/ src ```step3. $GOPATHを
gopathを見るように変更する``` $ export GOPATH=your_directory/gopath ```これで
gcloud app deployするとデプロイできた。
- 投稿日:2019-03-24T09:08:41+09:00
プログラミング言語Go 第5章 メモ
「関数」
書籍:プログラミング言語Go
第5章 「関数」 の要点と思われる箇所を自分のメモ用にまとめました。5.1 関数宣言
- 関数宣言は、名前、パラメータのリスト、省略可能な結果のリスト、本体から構成される。
func name(parameter-list) (result-list) { body }
- パラメータ同様結果も名前を付けることができ、名前付けをしたパラメータはその型のゼロ値のローカル変数を宣言することになる。
- 結果リストを持つ関数は、原則 (panic の呼び出しで終わるか、break のない無限 for ループで終わるのでなければ。) return 文で終わらないといけない。
- パラメータにはブランク識別子 _ を使うこともでき、パラメータが使われないことを強調するために使える。
func first(x int, _ int) { return x }
- 関数のシグニチャはパラメータの型の列と結果の型の列が同じであれば同じ。パラメータ名などは関数の型に影響しない。
- Goにはデフォルトパラメータという概念や、名前で引数を指定する方法はない。★
- 引数はコピーを受け取る値渡し。しかし、ポインタ、スライス、マップ、関数、チャネルなどの何らかの種類の参照が引数に含まれていれば、関数内処理が呼び出し元の引数に影響を与えうる。
- 本体のない関数宣言を見かけた場合は、Go以外の言語で実装されている関数を示し、関数のシグニチャを定義している。
5.2 再帰
- 関数は自分自身の関数を自分自身の関数内で呼び出す再帰呼び出しが可能。ツリー操作などをする場合に便利。
- outline の再起呼び出しでは、呼び出し先は stack のコピーを受け取る。呼び出し先が stack に要素を追加しても呼び出し元に見える要素は修正しない。呼び出し元のstackは呼び出す前と変わらない。
func outline(stack []string, n *html.Node) { if n.Type == html.ElementNode { stack = append(stack, n.Data) // push tag fmt.Println(stack) } for c := n.FirstChild; c != nil; c = c.NextSibling { outline(stack, c) } }
- 多くのプログラミング言語は固定長の関数呼び出しスタック(大きさ64KB-2MBまでが普通)を使っている。再起呼び出しの深さに制限を課すので大きなデータ構造の再起によるスタックオーバーフローに注意が必要だが、Goは可変長スタックを使っているためスタックオーバーフローの心配はない。★
5.3 複数戻り値
- 関数は複数戻り値を返すことができる。よくあるのが望まれた計算結果と、エラーもしくは処理が成功したかどうかのboolを返す関数。
- 多値呼出 (複数戻り値の関数呼出) は複数のパラメータを持つ関数呼出にも使える。デバッグに便利。製品コードではめったに使わない。
func hoge() string, bool { ... } fmt.Println(hoge()) val, ok := hoge() fmt.Println(val, ok) // 上記の Println と同じ出力が得られる。
- 多値関数の結果は名前が重要になる。戻り値にも名前を付けると戻り値の内容が理解しやすくなる。
func Size(rect image.Rectangle) (width, height int) {
- 慣習的に最後の bool は結果の成功を示すので名前は不要。error も多くの場合何の説明も必要としない。
- 名前付きの結果を持つ関数内では return 文のオペランドは省略可能。空リターンと呼ばれ、名前付き結果の変数のそれぞれを正しい順序で返す短い表記方法。
func CountWordsAndImage(url string) (words, images int, err error) { resp, err := http.Get(url) if err != nil { return } ...
- 空リターンはコードの重複を減らすがコードの理解を容易にするわけではない。例えば戻りがエラーのケースなのか正常なケースなのか分かりにくくなりうる。空リターンは控えめにしたほうが良い。★
5.4 エラー
- エラーはパッケージAPIやアプリケーションのUIの重要な一部。失敗は予期される振る舞いの一つ。
- 予期される振る舞いが失敗の場合、慣習的に関数の戻り値の最後が失敗を意味する値となる。
- エラー時の error 以外の戻り値に意味がある場合はドキュメントに書くことが重要。
- Goの例外機構は、バグを示す本当に予期されていないエラーを報告するためのもの。頑強なプログラムを構築するためのルーチンで予期されるエラーには使われない。★
- 予期せぬバグのみ例外とする理由は、例外処理は制御フローとエラー記述をもつれさせる傾向にあり、望まぬ結果をもたらしやすく、例外が報告された際に問題を理解しにくくさせやすいため。
- Goプログラムはエラーに対処するために if や return などの普通の制御フローを使う。★
5.4.1 エラー処理戦略
- 自作関数内でエラーを返す際は、重要な情報を補完する。findLinks の Parse エラーでは、パーサでエラーが起きたこと、パースドキュメントのURLを補完している。★
doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil,fmt.Errorf("parsing %s as HTML: %v", url err) }
- 究極的にエラーがmainで処理される場合、全体的な失敗に対する根本問題から明確な因果の連鎖を提供すべきである。★
- 一時的あるいは予想できない問題を表すエラーに対しては失敗した操作を再び試みることに意味があるケースがある。その場合、諦める前に時間を置いて再試行するなどが意味を持つ。
- 処理を進めるのが不可能な状態になった場合、エラー表示しプログラムを停止する。ただし、mainパッケージ内でのみ行うべきでライブラリなどはプログラムを停止してはいけない。
- 処理を継続することに意味があるのなら、エラーを記録し、制限された機能で処理を続ける。
- エラー処理を忘れてもそのプログラム以外の別の手段でリカバリーできるような場合にはエラーを無視して良い場合もある。意図的にエラーを無視する場合はドキュメントに明記すること。★
5.4.2 ファイルの終わり (EOF: End of File)
- ioパッケージではファイルの終わりの状態により発生した読み込みの失敗は、io.EOF として区別されたエラーで報告される。
in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break // 読み込みを終了 } if err != nil { ...5.5 関数値
- Goでは関数を変数に代入可能 (関数はファーストクラス値)。関数値を代入した変数で関数を呼び出すこともできる。関数型のゼロ値はnil。
func square(n int) int { return n * n } f := squre fmt.Println(f(3)) // 9
- 引数の型や戻り値の型が同じでないと関数値の変数に再度関数値を代入できない。
func square(n int) int { return n * n } func product(m, n int) int { return m * n } f := squre f = product // エラー: func(int, int) int を func(int) int へ代入できない。5.6 無名関数
- 関数リテラルは関数宣言のように書くが、func予約語の後に名前がない。関数リテラルは式であり、その値は無名関数と呼ばれる。 strings.Map(func(f, rune) rune { return r + 1 }, "HAL-9000")
- 無名関数はレキシカルな環境の全体へアクセスできる(無名関数を囲うスコープ内の変数にアクセスできる)。★
func squares() func() int { // (func() int) が戻りの関数。 var x int return func() { // squares() 関数のローカル変数 x にアクセスできる。 x++ return x * x } } func main() { f := squares() fmt.Println(f()) // 1 fmt.Println(f()) // 4 fmt.Println(f()) // 9
- squares の例は関数値が単なるコードではなく状態を持つことを示す。★
- 無名内部関数はそれを囲む関数のローカル変数にアクセス可能。このような関数値はクロージャと呼ばれる技法で実装されており、Goプログラマの多くはその用語を関数値に対して使っている。
- 無名関数が再起を必要とする場合、最初に宣言する必要がある。
var visitAll func(items []string) visitAll = func(items []string) { for _, item := range items { if !seen[item] { seen[item] = true visitAll(m[item]) order = append(order, item) } } }5.7 可変個引数関数
- 可変個引数関数を宣言するには最後のパラメータの方の前に省略記号 "..." を付ける。
func sum(vals ...int) { total := 0 for _, val := range vals { total += val } return total }
- 可変個引数関数に配列を渡すことも可能。関数呼び出し時に配列の後ろに...をつける。 order = append(order, array...)
- 可変個引数として interface{}型を使うと、最後の引数に対してすべての型を受け付けることができることを意味する。
5.8 遅延関数呼び出し
- defer 文は、関数やメソッド呼び出しの前に予約語 defer を付けたもので、予約語 defer を付けた関数、メソッドの呼び出しは、defer 文を含む関数が完了するまで遅延される。★
- return を実行したり関数の最後に到達したりという正常な完了ではないパニックなどの異常な完了でも defer は正常に動作する。★
- 遅延関数呼び出しは、遅延された順序の逆順に実行される。
- defer文はオープンとクローズ、接続と切断、ロックとアンロックのように一対になる操作で使われることが多い。デバッグでのログだし(関数に入った、出たを対にする場合)にも使う。★
func hoge() { defer trace("hoge")() // traceの戻りはfuncなので、最後に()を忘れないこと。 .... // hoge() の関数本体処理 } func trace(msg string) func() { start := time.Now() log.Printf("enter %s", msg) return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) } }
- クロージャと組み合わせると関数の処理結果を表示することなどもできる。★
func double(x int) (result int) { // 戻り値に名前を付けていることに注意!! defer func() { fmt.Printf("double(%d) = %d\n", x, result) }() return x + x } _ = double(4) // defer により、"double(4) = 8" が表示される。
- ループ内の defer 文は、ループが記載されている関数の終わりまで遅延関数が実行されないので注意。解決方法はループ本体の処理を関数化する。
5.9 パニック
- Goでは境界外への配列アクセスやnilポインタによる参照などのコンパイル時に検出できない誤りがあった場合、実行時にパニックになる。
- 典型的なパニックでは、通常実行は停止し、ごルーチン内でのすべての遅延関数呼び出しが行われ、ログメッセージを表示しクラッシュする。
- パニック時のログは、たいていパニック値とパニック時に動作していた関数呼び出しのスタックを指名す個々のごルーチンのスタックトレースが含まれる。
- 多言語の例外と異なり、パニックはプログラムをクラッシュさせるため、「予期されない」重大なエラーに対して使われる。
- 「予期される」エラー (誤った入力や設定、失敗したI/Oから発生する類のエラー) は error を使いきちんと処理されるべきで、パニックにすべきではない。★
- パ肉が発生した場合、すべての遅延された関数は、スタックの最上位の関数の遅延された関数から始まって main 関数まで逆順に実行される。
5.10 リカバー
- recover呼び出しをするとパニックになっていた関数は止まった場所から続けることはできないが、正常にリターンする。非パニック時の recover 呼び出しは何も影響なく nil が返る。
func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } }() // ... パーサ処理 }
- パニックからの無条件な回復は推奨されない。以下の理由から。
- パニック後のパッケージの変数の状態はほとんど定義されていなかったり文書化されていない。
- データ構造への重要な更新が不完全だったり、ファイルやネットワーク接続が開かれ閉じられていない、ロックが獲得され解放されていないなどありうる。
- クラッシュを仮にログファイル内の一行で置き換えた場合など、無条件な回復はバグに気づかなくさせる原因となる。
- 特にほかのパッケージのパニックからの回復は試みるべきではない。公開APIは失敗を error として報告すべき。★
練習問題解答例
https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter05







