20190324のGoに関する記事は10件です。

GolangでクロスプラットフォームGUIアプリを作る

GolangでGUIのクロスプラットフォームアプリを作る

fyne-io/fyne
というOpenGLを使うライブラリを使います。
この間1.0が出たみたいです。

僕はGolang Weeklyのツイートで知りました。

簡単なタイマーを作ってみる

こんな感じのものを作ります。

スクリーンショット 2019-03-24 23.06.08.png

リポジトリ

nozo-moto/pomodoro_timer

コード

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

音楽は

https://qiita.com/nozo_moto/items/018c508f74f325cf981a

の記事で書いたやり方を試したがダメだったので他のやり方を試してみる

雑感

GUIクロスプラットフォームアプリだとElectronくらいしかなさそうな上に、大変なのでサクッと作るならこっちの方が楽だなといった印象です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)でできる

参考

https://github.com/faiface/beep

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プライベートレポジトリを含むGAE/Go(go111)をCloud Buildで自動でプロイする

はじめに

go111 の登場で、Cloud Build を使えば設定ファイル1つで自動デプロイ出来るようになったはずでした。

cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/gcloud'
  args: ['app', 'deploy', '--project=$PROJECT_ID', 'gae/app/app.yaml']

monorepo ならプライベートでも上記の設定でデプロイ出来ます。

oybFJinHqBLJSCyiyIXDKh1IA2Wjpk22ye3YIiuv-QKf2awbcJaf6Y55cUdfgKMQIa1rvQb96VdvsLmu5A1hTNTq1MjBJmOB9XOKAAIa5kJaLt9XKOPJSxv2RdwAWfwU7W00.png

しかし manyrepo で private repository を go get するとなると一工夫必要に。

先に go mod vendor する

oybFJinHqBLJSCyiyIXDKh1IA2Wjpk22ye3YIiuv-QKf2awbcJaf6Y55cUdfgKMQIa1rvQb96VdvsLmu5E3Jcfwla9kVeb2McfUINqIi0UwEhX3DoM31n882XPJKWfpyIW00.png

まず、Cloud Build で go111 する際、我々が Cloud Build のトリガーに設定するプロセス(以下「Cloud Build(Trigger)」)とは別に、gcloud app deploy によって実行される Cloud Build のプロセスがあり(以下「Cloud Build(GAE)」)、go.mod を含む deploy、つまり .gcloudignorego.mod がない場合は Cloud Build(GAE) が依存解決もしてくれます。

非常に素敵なのですが、sshの鍵だったりトークンだったり何も設定できないため、プライベートレポジトリが go.mod に含まれると失敗します。

じゃあどうする

Cloud Build(Trigger) 側で go mod vendor し、更に .gcloudignorego.mod を指定して gcloud app deploy すればよいのです、vendor ディレクトリごと GCS にアップロードするので、.gcloudignorevendor を含めてはいけません。

まとめ

  1. go111 のプロジェクトを gcloud app deploy すると Cloud Build による deploy が実行される
  2. この際に実行される Cloud Build に対しては何も設定出来ない
  3. go.mod を含む場合、自動で go get し、そこにプライベートレポジトリがあると失敗する
  4. .gcloudignorego.mod を除外指定していれば自動で go get されない
  5. そのかわり、vendor.gcloudignore に指定せず、 go mod vendor 後に gcloud app deploy する
  6. vendor ディレクトリごと Upload され、go get も行われないので deploy は成功する
  7. 一連の作業を Cloud Build で自動化する為の cloudbuild.yaml がこちら
cloudbuild.yaml
steps:
- 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'

dirmv などで GOPATH に則ったディレクトリ構成にしています。
kmsKeyName に変数埋め込みは利用できないのでべた書きしてください、ご注意を。

.gcloudignore はこんな感じに

.gcloudignore
.gcloudignore
.git
.gitignore
go.mod
go.sum

YOUR_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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

依存にプライベートレポジトリを含むGAE/Go(go111)をCloud Buildで自動でプロイする

はじめに

go111 の登場で、Cloud Build を使えば設定ファイル1つで自動デプロイ出来るようになったはずでした。

cloudbuild.yaml
steps:
- 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 に含まれると必ず失敗します。

oybFJinHqBLJSCyiyIXDKh1IA2Wjpk22ye3YIiuv-QKf2awbcJaf6Y55cUdfgKMQIa1rvQb96VdvsLmu5A1hTNTq1MjBJmOB9XOKAAIa5kJaLt9XKOPJSxv2RdwAWfwU7W00.png

前段でgo mod vendorし、go.modをdeployしない

oybFJinHqBLJSCyiyIXDKh1IA2Wjpk22ye3YIiuv-QKf2awbcJaf6Y55cUdfgKMQIa1rvQb96VdvsLmu5E3Jcfwla9kVeb2McfUINqIi0UwEhX3DoM31n882XPJKWfpyIW00.png

Cloud Build(Trigger) 側で go mod vendor し、更に .gcloudignorego.mod を指定して gcloud app deploy すれば解決します。
vendor ディレクトリごと GCS にアップロードするので、.gcloudignorevendor を含めてはいけません。

まとめ

  1. go111 のプロジェクトを gcloud app deploy すると Cloud Build による deploy が実行される
  2. この際に実行される Cloud Build に対しては何も設定出来ない
  3. go.mod を含む場合、自動で go get し、そこにプライベートレポジトリがあると失敗する
  4. .gcloudignorego.mod を除外指定していれば自動で go get されない
  5. そのかわり、vendor.gcloudignore に指定せず、 go mod vendor 後に gcloud app deploy する
  6. vendor ディレクトリごと Upload され、go get も行われないので deploy は成功する
  7. 一連の作業を Cloud Build で自動化する為の cloudbuild.yaml がこちら
cloudbuild.yaml
steps:
- 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'

dirmv などで GOPATH に則ったディレクトリ構成にしています。
kmsKeyName に変数埋め込みは利用できないのでべた書きしてください、ご注意を。

.gcloudignore はこんな感じに

.gcloudignore
.gcloudignore
.git
.gitignore
go.mod
go.sum

YOUR_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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでGraphQLのMutationを実装する

このエントリーではGolang + MongoDBでGraphQLを使ってみるに書きつぐ形で、GraphQLのMutationについての説明を行います。

一度改めて、まとめを兼ねたGraphQLの説明をします。

GraphQLとは

GraphQLでは、これまでRESTfulで作ってきたように複数のエンドポイントを作成して通信を行う方法を行いません。
エンドポイントは最低一つだけで動くようになるのがGraphQLです。

基本的にはGraphQLの利点は、

・エンドポイント一つだけで良い
・型安全にデータベースとやりとりができる

の二点を心に留めておけば問題ないかと思います(補足があれば喜んでいただきます)。

さらっとした説明ですが、ここからは実装に関するお話。
と言ってもコードをつらつら書くわけではなく、概念的なものを考えていきます。

GraphQLの実装の考え方

まず、GraphQLの実装の流れは以下のような雰囲気で感じ取ってもらえればと思います(この流れはGolangに限らず一般的なものだと思いますたぶん)。

  1. GraphQLで扱うTypeの定義(GraphQLが扱うデータの設定)
  2. Fieldをデータの取得や作成の用途に合わせて定義(ここでTypeを利用する)
  3. Fieldをまとめてスキーマを作る
  4. GraphQLのリクエストを作成(GraphQLがデータベースに問い合わせる要求)
  5. スキーマとリクエストを合わせてGraphQLを実行
  6. 指定したデータが戻ってくる

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

はい、これでミューテーションの処理は終わりです。
ミューテーションの中の動きは以上となります:open_hands_tone2:

実装された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では動き方がわかればだいたいの実装の方法がわかってくるようになると思います。
それと、実際にコードを書いて動かしてみることも大切ですね:sunglasses:

間違った表現や説明をしている箇所があれば、忌憚なくご指摘をお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GolangでGraphQLのMutationを実装する

このエントリーではGolang + MongoDBでGraphQLを使ってみるに書きつぐ形で、GraphQLのMutationについての説明を行います。

一度改めて、まとめを兼ねたGraphQLの説明をします。

GraphQLとは

GraphQLでは、これまでRESTfulで作ってきたように複数のエンドポイントを作成して通信を行う方法を行いません。
エンドポイントは最低一つだけで動くようになるのがGraphQLです。

基本的にはGraphQLの利点は、

・エンドポイント一つだけで良い
・型安全にデータベースとやりとりができる

の二点を心に留めておけば問題ないかと思います(補足があれば喜んでいただきます)。

さらっとした説明ですが、ここからは実装に関するお話。
と言ってもコードをつらつら書くわけではなく、概念的なものを考えていきます。

GraphQLの実装の考え方

まず、GraphQLの実装の流れは以下のような雰囲気で感じ取ってもらえればと思います(この流れはGolangに限らず一般的なものだと思いますたぶん)。

  1. GraphQLで扱うTypeの定義(GraphQLが扱うデータの設定)
  2. Fieldをデータの取得や作成の用途に合わせて定義(ここでTypeを利用する)
  3. Fieldをまとめてスキーマを作る
  4. GraphQLのリクエストを作成(GraphQLがデータベースに問い合わせる要求)
  5. スキーマとリクエストを合わせてGraphQLを実行
  6. 指定したデータが戻ってくる

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

はい、これでミューテーションの処理は終わりです。
ミューテーションの中の動きは以上となります:open_hands_tone2:

実装された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では動き方がわかればだいたいの実装の方法がわかってくるようになると思います。
それと、実際にコードを書いて動かしてみることも大切ですね:sunglasses:

間違った表現や説明をしている箇所があれば、忌憚なくご指摘をお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-code

GCP プロジェクトの作成

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.md

App 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.md

App 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.go
package 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.go
package 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.yaml
runtime: go
api_version: go1.9

handlers:
  - url: /.*
    script: _go_app

inbound_services:
  - warmup

Go の 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 にアクセスすると、ブレークポイントでプログラムがストップします。

スクリーンショット 2019-03-24 12.10.48.png

ローカル開発用サーバーのオプション | Google Cloud

デプロイ

ローカル開発サーバーでの起動が出来たので、次はアプリケーションをクラウド上にデプロイします。

GCP プロジェクトを作成した際に使用した個人の Google アカウントで認証を通してデプロイすることもできますが、後の自動デプロイも見据え、デプロイ用サービスアカウントを作成して認証を通します。

サービスアカウントは次の手順で作成します。

まずは GCP のダッシュボードにアクセスし、そこからサービスアカウントの作成画面へ移動します。

[GCPプロジェクトダッシュボード] > [ナビゲーションメニュー] > [API とサービス] > [認証情報] > [認証情報を作成] > [サービスアカウントキー]

サービスアカウントの作成に必要な情報を入力します。入力が完了したら [作成] をクリックします。

サービスアカウント: 新しいサービスアカウント
  サービスアカウント名: (任意)
  役割: 
    - App Engine サービス管理者
    - App Engine デプロイ管理者
  サービスアカウントID: (任意)
キーのタイプ: JSON

[作成] をクリックすると秘密鍵が PC にダウンロードされます。
プロジェクトルートディレクトリ直下に key という名称のディレクトリを作成し、秘密鍵をこのディレクトリ内にリネームしつつ移動させます。

秘密鍵は GitHub 上にはアップしないので、.gitignorekey ディレクトリを追加します。

# ディレクトリ作成
mkdir key

# 秘密鍵をリネームしつつ移動
mv /path/to/secret-key.json ./key/deploy.json

# key ディレクトリを .gitignore に追記
echo 'key' >> .gitignore

スクリーンショット 2019-03-24 13.15.17.png

次に 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 ダッシュボードの右上のリンクからアプリケショーンにアクセスできます。

スクリーンショット 2019-03-24 14.35.08.png

スクリーンショット 2019-03-24 14.33.41.png

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 ダッシュボード] > [バージョン] から確認できます。

トラフィックの移行が完全に完了したら、再度ブラウザでアプリケーションにアクセスしてみます。

スクリーンショット 2019-03-24 14.48.23.png

サービス アカウントを使用して承認する | Google Cloud
appcfg.py コマンドライン引数 | Google Cloud
プロジェクト アクセス権の付与

おわりに

Part1 はこれにて以上です。

Prat2 では、Makefile を用いたタスク管理、Circle CI での自動デプロイ、データベース接続、機能の作り込みなど、より実践的な内容に入っていきます。

よかったら Twitter フォローしてね。@_rema424

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はまりがちな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 するとデプロイできた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む