GAE(Google App Engine) プロジェクト(ディレクトリ)の構成って？ main.go から GAE 用プロジェクトの起動 #golang


以前の記事（[GAE(Google App Engine) で Golang 使った REST API](https://qiita.com/ynozue/items/22967c3c8e12129d7527)）を作りましたが、プロジェクト(ディレクトリ)の構成について見直してみました。
Golang 用のプロジェクト構成については、ルールなどあると思いますが今回は Google App Engine の部分だけにフォーカスして考えてみます。


# プロジェクト(ディレクトリ)の構成の変更
## 変更内容
* appengine ディレクトリを作成
 * GAE 用のプログラム(hello.go)を移動
 * app.yaml(設定ファイル)を移動
 * 細かい修正（ファイル名の変更など）
* ルートディレクトリに main.go を追加
* 既存のプログラムに初期化用の function を追加

## 今までのプロジェクト(ディレクトリ)の構成
```sh
$ tree
.
├── README.md
├── app.yaml
└── hello.go
```

## 色々なサイトを参考にしたプロジェクト(ディレクトリ)の構成
```sh
$ tree
.
├── README.md
├── appengine
│   ├── app.yaml
│   └── init.go
└── main.go
```

## ルートディレクトリに main.go を追加
今までの構成だと GAE の go plugin がインストールされた環境じゃないとサービスを起動することができないため、開発時の動作確認を行うことができませんでした。
そこで、appengine ディレクトリを作成して GAE 用の処理を全て appengine パッケージ内で管理するようにし、 main.go からは HTTP サーバを起動するだけの構成にします。

```go:main.go
package main

import (
	"os"
	"log"
	"net/http"
	"github.com/ynozue/hellow-gae-go/appengine"
	"fmt"
)

func main() {
	log.Printf("start pid %d\n", os.Getpid())
	// appengine package をロード
	fmt.Println(appengine.New)
	http.ListenAndServe(":8080", nil)
}
```

「appengine.New」を呼び出していますが、これは appengine パッケージの特定の処理を呼び出さないと init 関数が動作しないために苦肉の策として実行しています。
うまい方法があれば知りたい。。。


## 既存のプログラムに初期化用の function を追加
```go:init.go
package appengine

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

// Response 用の構造体
type Result struct {
	Status int
	Description string
}

func New() {
	fmt.Println("new")
}

// package 読み込み時に実行される
func init() {
	http.HandleFunc("/hello", handler)
}

// hello に binding された function
func handler(w http.ResponseWriter, r *http.Request) {
	var result Result
	switch r.Method {
	case http.MethodGet:
		v := r.FormValue("get_value")
		result = Result{http.StatusOK, "http get result [" + v + "]"}
	case http.MethodPost:
		v := r.FormValue("post_value")
		result = Result{http.StatusOK, "http post result [" + v + "]"}
	default:
		result = Result{http.StatusNotImplemented, "not implemented http mehtod"}
	}
	res, err := json.Marshal(result)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.Write(res)
	}
}
```
※ New 関数を追加しています。

# 動作確認
## go コマンドからの起動
```sh
$ go run main.go
2017/10/13 09:24:59 start pid 9036
0x1224f80

$ curl http://localhost:8080/hello | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    49  100    49    0     0   7762      0 --:--:-- --:--:-- --:--:--  8166
{
    "Description": "http get result []",
    "Status": 200
}
```

## GAE go plugin からの起動
```sh
$ goapp serve appengine/app.yaml
INFO     2017-10-13 00:26:15,907 devappserver2.py:115] Skipping SDK update check.
INFO     2017-10-13 00:26:16,037 api_server.py:299] Starting API server at: http://localhost:51927
INFO     2017-10-13 00:26:16,044 dispatcher.py:224] Starting module "default" running at: http://localhost:8080
INFO     2017-10-13 00:26:16,049 admin_server.py:116] Starting admin server at: http://localhost:8000

$ curl http://localhost:8080/hello | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    49  100    49    0     0   1080      0 --:--:-- --:--:-- --:--:--  1088
{
    "Description": "http get result []",
    "Status": 200
}
```

## まとめ
go コマンドで実行したケースも GAE の go plungin を利用して実行したケースも同じ結果になりました。
これで go plungin をインストールしていない環境でも開発環境を動かすことができるようになりました。

# Appendix
* [ソースコード](https://github.com/ynozue/hello-gae-go/tree/directory)