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

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](http://motemen.hatenablog.com/entry/2016/11/gae-go-building#goapp-%E3%81%AF%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%83%AB%E3%83%BC%E3%83%88%E4%BB%A5%E4%B8%8B%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%97%E3%82%88%E3%81%86%E3%81%A8%E3%81%99%E3%82%8B)

# 開発ツール
以下のツールをインストール
#### 必須
+ [AppEngine SDK](https://cloud.google.com/appengine/downloads)  
+ [glide](https://github.com/Masterminds/glide)  
+ python2.7  

#### その他(あると便利)
+ [goenv](https://bitbucket.org/ymotongpoo/goenv)
通常のGo開発環境とGAE/Goの環境の切り替えに

+ [pyenv](https://github.com/yyuu/pyenv)
AppEngine SDKを動かすのに、python2系が必須のため、pythonのバージョンの切り替えが必要な場合
######参考
[goenvでgae/goと普通のgoの環境を切り替える - Qiita](http://qiita.com/sinmetal/items/71cfba4ae27cc2366572)

# 環境構築
### プロジェクト作成
##### goenv未使用の場合
+ `GOPATH`以下に`app`ディレクトリを作成する
+ `app`ディレクトリ内に、`app.yaml`を作成する

```yaml:app.yaml
application: {プロジェクト名}
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
```

##### goenvを使用する場合
+ 以下のコマンドでプロジェクトを作成する

```bash
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 {パッケージ名}`を実行する

```yaml:glide.yaml
# cloud.google.com/go/storageを使用する場合
# glide getを実行した場合は自動でパッケージが追記される

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

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

```
$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

```yaml:app.yaml
application: sample-crud
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
```

+ glide.yaml

```yaml:glide.yaml
package: .
import:
- package: golang.org/x/net
  subpackages:
  - context
- package: google.golang.org/appengine
  version: ~1.0.0
  subpackages:
  - datastore
```

+ ソースコード

```go:app/main.go
package main

import (
	"net/http"
	"sample"
)

func init() {
	http.Handle("/foo", sample.GetCRUDSampleHandler())
}
```

```go: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`ディレクトリにぶち込んでくれる。

```bash
$ cd src
$ glide up
```

+ ローカルサーバー起動  
`app`ディレクトリに移動し、`goapp serve app.yaml`を実行

```bash
$ cd app
$ goapp serve app.yaml
```

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

```bash
$ cd app
$ pyenv local 2.7.13
```

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



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