再考 GAE/Goのプロジェクト構成

以前、GAE/Goでglideを使用する場合のプロジェクト構成についての記事を書いた。
[GAE/Go+glide的な構成での環境構築 ~ローカルサーバー立ち上げまで~ - Qiita](https://qiita.com/ryutah/items/d864310c62f0385d876d)

あれからしばらくGAE/Goで開発を続けていて、こんな構成もよさそうだなーと思うものが出てきたので、改めてまとめてみる。

前回はパッケージ管理に[glide](https://github.com/Masterminds/glide)を使用したが、今時[dep](https://github.com/golang/dep)だろjkという気分なので、今後は`dep`を使って行くことになると思う。

# ディレクトリ構成
前回の記事ではこんな感じの構成だった。

```
$GOPATH(PROJECT_ROOT)
  ├── app
  │   ├── app.yaml
  │   └── main.go
  └── src
      ├── glide.yaml
      ├── glide.lock
      ├── PACKAGE
      └── vendor
```

つまりは、プロジェクトルートにGOPATHを設定する感じ。

それで今回試すのがこんな構成

```
$GOPATH
  └── src
       └── PROJECT_ROOT
            ├── Gopkg.lock
            ├── Gopkg.toml
            ├── Makefile
            ├── app
            │   ├── app.yaml
            │   └── main.go
            ├── SUB_PACKAGE
            └── vendor

```

違いとしては、プロジェクトルートにGOPATHを設定するのをやめ、基本的なGoFlowに沿って、グルーバルなGOPATHにプロジェクトを配置するやり方。

src直下じゃなくて、`github.com/{USER}/{REPOSITORY}`のように通常のGoプロジェクトのようにしてもOK。

この構成の利点としては、わざわざプロジェクトごとにGOPATHの変更が必要無くなって、`goenv`とか`direnv`で変更する必要が無いから色々と楽。

Makefileはアプリのデプロイとかテスト実行だったり、開発が進むに連れて色々と発生してくる手間を省くためのタスクランナーとして使ってる。
シェルスクリプト書いてもいいけど、その場合でもシェル叩くのはmakeタスクからやらせるようにしている。
タブ保管ができるし、同じ構文で様々なタスク走らせられるので色々捗る。

ぶっちゃけこれで話は終わりなんだけど、せっかくなのでこの構成でアプリデプロイするところまで解説してみようと思う。

# サンプルアプリの作成
というわけで、サンプルの作成。
[完成版 - github.com](https://github.com/ryutah/gae-structure-sample)

上で解説してるように、まずはこんな感じでプロジェクトを作成する。
サブパッケージ名はわりと適当。

```
$GOPATH
  └── src
       └── github.com/...
            ├── app
            └── gae
```

## app.yaml
これがなくては始まらないので`app.yaml`を作成する。

```yaml:app.yaml
runtime: go
api_version: go1.8

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

`app.yaml`を追加した後はこうなる

```
$GOPATH
  └── src
       └── github.com/...
            ├── app
            │   └── app.yaml
            └── gae
```

## ハンドラー作成
リクエストを処理するハンドラーを書く。
ハローワールド返すだけ。

```go:sayhello.go
package gae

import (
	"fmt"
	"net/http"
)

func SayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello world")
}
```

これは、サブパッケージ直下に配置してあげる。

```
$GOPATH
  └── src
       └── github.com/...
            ├── app
            │   └── app.yaml
            └── gae
                └── hello.go
```

## main.go作成
ハンドラーを作っただけじゃアプリは動かない。
作ったハンドラーをルーティングとして設定しなきゃいけないわけで、その設定は`app`以下に配置する`main.go`にやらせる

```go:main.go
package main

import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/ryutah/gae-structure-sample/gae"
)

func init() {
	r := mux.NewRouter()
	r.HandleFunc("/", gae.SayHello)
	http.Handle("/", r)
}
```

せっかくなので、ルーティング処理には`gorilla/mux`を使ってる。
ライブラリの取得は普通に`go get`でやった。

```console
$ go get -u github.com/gorilla/mux
```

ここまで終わると、ディレクトリ構成はこうなる

```
$GOPATH
  └── src
       └── github.com/...
            ├── app
            │   ├── app.yaml
            │   └── main.go
            └── gae
                └── hello.go
```

ここまで終わると、`goapp serve app`でローカルサーバは立ち上がるようになる。

## Makefile
せっかくなので、いろんなタスクをMakefileに書いていく

```makefile
.PHONY: all

help: ## Print this help
	@echo 'Usage: make [target]'
	@echo ''
	@echo 'Targets:'
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

serve: ## ローカルサーバ実行
	goapp serve app
```

このMakefileはプロジェクトルートに配置しておく。

```
$GOPATH
  └── src
       └── github.com/...
            ├── Makefile
            ├── app
            │   ├── app.yaml
            │   └── main.go
            └── gae
                └── hello.go
```

ローカルサーバを起動したい場合はこんな感じでmakeタスクを実行する

```
$ make serve
```

## dep
やっと`dep`を使う。
dep自体は普通に`go get`でインストール

```console
$ go get -u github.com/golang/dep/cmd/dep
```

vendoringのために、プロジェクトルートで`dep init`を実行

```console
$ dep init
```

今回のプロジェクトだと、`main.go`が`gorilla/mux`に依存してるので、勝手にパッケージ取ってきてくれる。
`dep init`が終わるとこんな感じの構成になる

```
$GOPATH
  └── src
       └── github.com/...
           ├── Gopkg.lock
           ├── Gopkg.toml
           ├── Makefile
           ├── app
           │   ├── app.yaml
           │   └── main.go
           ├── gae
           │   └── hello.go
           └── vendor
               └── github.com
                   └── gorilla
                       ├── context
                       │   ├── LICENSE
                       略...
```

というわけで、サンプルアプリ完成。


# デプロイしてみる
早速できたアプリをデプロイしてみる。
せっかくなので、これも`Makefile`で定義しておく。

```Makefile
.PHONY: all

project_id := ${PROJECT_ID}
version := ${GAE_VERSION}

help: ## Print this help
	@echo 'Usage: make [target]'
	@echo ''
	@echo 'Targets:'
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

serve: ## ローカルサーバ実行
	goapp serve app

deploy: ## gaeへデプロイ OPTIONS: project_id=${PROJECT_ID} version=${VERSION}
	goapp deploy -application ${project_id} -version ${version} app
```

プロジェクトIDとバージョンを環境変数を設定するか、引数として指定するようにしてみた。
`gcloud app deploy`を使いたいところだが、そっちはまだGoのVendoringに対応してないので仕方ないので`goapp deploy`を使う

というわけでデプロイ。

```
$ make deploy project_id={PROJECT_ID} version=1
  goapp deploy -application {PROJECT_ID} -version 1 app
  07:28 PM Application: {PROJECT_ID} (was: None); version: 1 (was: None)
  07:28 PM Host: appengine.google.com
  07:28 PM Starting update of app: {PROJECT_ID}, version: 1
  07:28 PM Getting current resource limits.
  07:28 PM Scanning files on local disk.
  07:28 PM Cloning 13 application files.
  07:28 PM Uploading 4 files and blobs.
  07:28 PM Uploaded 4 files and blobs.
  07:28 PM Compilation starting.
  07:28 PM Compilation: 10 files left.
  07:28 PM Compilation completed.
  07:28 PM Starting deployment.
  07:28 PM Checking if deployment succeeded.
  07:28 PM Deployment successful.
  07:28 PM Checking if updated app version is serving.
  07:28 PM Completed update of app: {PROJECT_ID}, version: 1
```

OK

# 動作確認
せっかくなので、ちゃんと動くか確認する

```console
$ gcloud app browse --version 1
```

![動作確認.png](https://qiita-image-store.s3.amazonaws.com/0/36379/dcf3441d-87ad-ced1-6a9e-212c2e434dac.png)


ちゃんと動いた。


# まとめ
というわけで、駆け足でプロジェクト構成からサンプルアプリの作成までやってみた。
GAE/Goのプロジェクト構成に関するベストプラクティスは未だに無いと思ってるので、今後も色々と試行錯誤していければと思う。


# 2017年12月29日　追記
## `There are too many files in your application for`をなんとかしたい
コメントにもあるように、GOPATH内のパッケージが多くなるとローカルサーバ実行中のファイル監視が上手く動いてくれない。
普通に開発していると、当然GOPATH内にはいろんな開発ツールやらが入っていると思うので、かなりの確率でこの状態になったりする。

![ローカルサーバ起動時のWarning](https://qiita-image-store.s3.amazonaws.com/0/36379/e1a8b1bb-2306-7e6f-70b4-cd1070dbdb66.png)

動作自体に問題はないが、ファイル変更を検知してくれなくなるため地味に不便。

### 解決方法
GOPATHにパッケージがありすぎるのが問題なのでそれを解消する必要がある。
つまり、プロジェクトごとにクリーンなGOPATHを用意して、余計なファイルが存在しない状態にすればいいわけだ。
とはいえ、コーディングを行う環境だと大体何かしらのツールや他プロジェクトのコードがGOPATH内にあるものなので、ローカルサーバ起動のたびに、わざわざそんな状態を用意するのも大変だ。

なので、GAE/Goが実行可能なDockerイメージを作ってみた。[^1]
[^1]: すでにGAE/GoのDockerイメージはいくつかありますが多分これが一番イメージサイズが小さいと思います

[DockerHub - ryutah/gcloud-gaego](https://hub.docker.com/r/ryutah/gcloud-gaego/)

Dockerコンテナを利用することで、常にクリーンなGOPATHでアプリケーションが実行できるため、よほどのことがなければちゃんとファイル監視してくれるようになる。

### 試してみる
コンテナでローカルサーバを実行できるように、Makefileにタスクを追記する

```Makefile
serve-docker: ## ローカルサーバをDockerコンテナで実行する
	$(eval src := $(shell pwd))
	@docker run --rm \
	  -it  \
	  -v ${src}:/work/src/github.com/ryutah/gae-structure-sample \
	  -p 8080:8080 \
	  -p 8000:8000 \
	  ryutah/gcloud-gaego \
	  dev_appserver.py --host 0.0.0.0 --admin_host 0.0.0.0 /work/src/github.com/ryutah/gae-structure-sample/app

```

コンテナを利用してローカルサーバを起動してみる
![Dockerでサーバー起動](https://qiita-image-store.s3.amazonaws.com/0/36379/df5d5a86-f46b-c956-4f6f-7cdba5757759.png)


警告文が出なくなった。

この状態でファイルを編集して、変更を検知できているか確認してみる。
![ファイル変更](https://qiita-image-store.s3.amazonaws.com/0/36379/d96bc0a0-7af2-f3bf-49e0-b49e9a13bc60.png)


やったぜ。


# 参考
[Go でツール書くときの Makefile 晒す - Qiita](https://qiita.com/dtan4/items/8c417b629b6b2033a541)
[Makefileを自己文書化する - POSTD](http://postd.cc/auto-documented-makefile/)