GAE/Go+glide的な構成での環境構築 ~ローカルサーバー立ち上げまで~

  • 21
    いいね
  • 0
    コメント

GAE/Go+glide的な構成での環境構築とローカルサーバの起動までの手順備忘録

プロジェクト構成

この解説では、最終的に以下のようなディレクトリ構成になる。

$GOPATH
  ├── app
  │   ├── app.yaml      <= app.yamlはsrcディレクトリの外に配置
  │   └── main.go
  └── src
      ├── glide.yaml    <= glide.yamlはsrc直下
      ├── glide.lock
      ├── {package}     <= 基本的なソースコードはsrc以下のパッケージに
      └── vendor        <= アプリで利用する外部パッケージ

GAE/Goの仕様でapp.yamlが配置されているディレクトリ以下のコードが全てビルドされてしまう。
この仕様のため、vendoringを使用して外部ライブラリを利用しようとすると、ほぼ間違いなくビルドでこけてしまうのでapp.yamlはsrcディレクトリには含めないようにしている。
参考
Google App EngineでGoを動かすときに知っておくべきこと(ソースコード・ビルド編) - Hatena Blog

開発ツール

以下のツールをインストール

必須

その他(あると便利)

環境構築

プロジェクト作成

goenv未使用の場合
  • GOPATH以下にappディレクトリを作成する
  • appディレクトリ内に、app.yamlを作成する
app.yaml
application: {プロジェクト名}
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
goenvを使用する場合
  • 以下のコマンドでプロジェクトを作成する
goenv -gae -go {$APPENGINE_SDK/goroot} -deps $GOPATH {プロジェクト名}
  • 自動生成されたディレクトリ内に、appディレクトリを作成
  • srcディレクトリ内にapp.yamlファイルが自動生成されているので、appディレクトリに移動する

この時点で以下のような構成になる

${GOPATH}
  ├── activate(goenvでプロジェクトを作成した場合)
  ├── app
  │   └── app.yaml
  └── src
      └── {プロジェクト名}.go(goenvでプロジェクトを作成した場合)

glide初期設定

glideの初期設定など

  • srcディレクトリ内でglide createを実行
  • glide.yamlが自動生成されるので、使用する外部パッケージを記載するか、glide get {パッケージ名}を実行する
glide.yaml
# cloud.google.com/go/storageを使用する場合
# glide getを実行した場合は自動でパッケージが追記される

package: .
import:
  - package: cloud.google.com/go
    subpackages:
      - storage

ここまでで以下のような構成になっているはず(glide.yamlを直接編集した場合はまだglide.lockvendorディレクトリは作成されない)

$GOPATH
  ├── activate(goenvでプロジェクトを作成した場合)
  ├── app
  │   └── app.yaml
  └── src
      ├── glide.yaml
      ├── glide.lock 
      ├── vendor
      └── {プロジェクト名}.go(goenvでプロジェクトを作成した場合)

サンプルコード

ローカルサーバーの起動確認と、glideでの外部ライブラリ管理確認のサンプルコード。
DataStoreを使用して簡単なデータ登録と取得を行うAPIを作成してみる。

  • ディレクトリ構成
$GOPATH
  ├── activate
  ├── app
  │   ├── app.yaml
  │   └── main.go
  └── src
      ├── glide.lock
      ├── glide.yaml
      ├── sample
      │   └── crud.go
      └── vendor
  • app.yaml
app.yaml
application: sample-crud
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
  • glide.yaml
glide.yaml
package: .
import:
- package: golang.org/x/net
  subpackages:
  - context
- package: google.golang.org/appengine
  version: ~1.0.0
  subpackages:
  - datastore
  • ソースコード
app/main.go
package main

import (
    "net/http"
    "sample"
)

func init() {
    http.Handle("/foo", sample.GetCRUDSampleHandler())
}
src/sample/crud.go
package sample

import (
    "encoding/json"
    "fmt"
    "net/http"

    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
)

const KindFoo = "foo"

type Foo struct {
    Foo string `json:"foo"`
    Bar string `json:"bar"`
}

func GetCRUDSampleHandler() *CRUDSample {
    return new(CRUDSample)
}

type CRUDSample struct {
    ctx context.Context
    w   http.ResponseWriter
    r   *http.Request
}

func (c *CRUDSample) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    c.ctx, c.w, c.r = appengine.NewContext(r), w, r
    switch r.Method {
    case http.MethodGet:
        c.Get()
    case http.MethodPost:
        c.Post()
    default:
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprintln(w, "Not found.")
    }
}

type RespGetFoo struct {
    Id  int64  `json:"id"`
    Foo string `json:"foo"`
    Bar string `json:"bar"`
}

func (c *CRUDSample) Get() {
    var foos []Foo
    keys, err := datastore.NewQuery(KindFoo).GetAll(c.ctx, &foos)
    if err != nil {
        c.responseServerError(err)
        return
    }
    result := make([]RespGetFoo, len(keys))
    for i, f := range foos {
        result[i] = RespGetFoo{keys[i].IntID(), f.Foo, f.Bar}
    }
    c.responseJson(http.StatusOK, result)
}

func (c *CRUDSample) Post() {
    var foo Foo
    err := json.NewDecoder(c.r.Body).Decode(&foo)
    if err != nil {
        c.responseServerError(err)
        return
    }
    key := datastore.NewIncompleteKey(c.ctx, KindFoo, nil)
    newKey, err := datastore.Put(c.ctx, key, &foo)
    if err != nil {
        c.responseServerError(err)
        return
    }
    response := map[string]int64{"id": newKey.IntID()}
    c.responseJson(http.StatusCreated, response)
}

func (c *CRUDSample) responseJson(status int, v interface{}) {
    c.w.WriteHeader(status)
    json.NewEncoder(c.w).Encode(v)
}

func (c *CRUDSample) responseServerError(err error) {
    c.w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintln(c.w, err)
}

実行

  • 依存ライブラリの取得
    srcディレクトリに移動して、glide upを実行。 プロジェクトや、外部ライブラリの依存関係を解決してvendorディレクトリにぶち込んでくれる。
$ cd src
$ glide up
  • ローカルサーバー起動
    appディレクトリに移動し、goapp serve app.yamlを実行
$ cd app
$ goapp serve app.yaml

注意点
ローカルサーバの起動にはpython2系が必要なため、ローカルサーバの起動前にpyhtonのバージョンを変更しておくこと。
pyenvなどを使っている場合は

$ cd app
$ pyenv local 2.7.13

とかを実行して、appディレクトリ以下のpythonバージョンのを固定しておくと便利

以上、GAE/Go+glideの環境準備からローカルサーバーの起動までの手順。
今後glideでのパッケージ管理は基本になるかと思うので、手順などや構成などを忘れないように。