Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

前回はパッケージ管理にglideを使用したが、今時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

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

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

app.yaml

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

app.yaml
runtime: go
api_version: go1.8

handlers:
- url: /.*
  script: _go_app

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

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

ハンドラー作成

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

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にやらせる

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でやった。

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

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

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

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

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でインストール

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

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

$ dep init

今回のプロジェクトだと、main.gogorilla/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で定義しておく。

.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

動作確認

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

$ gcloud app browse --version 1

動作確認.png

ちゃんと動いた。

まとめ

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

2017年12月29日 追記

There are too many files in your application forをなんとかしたい

コメントにもあるように、GOPATH内のパッケージが多くなるとローカルサーバ実行中のファイル監視が上手く動いてくれない。
普通に開発していると、当然GOPATH内にはいろんな開発ツールやらが入っていると思うので、かなりの確率でこの状態になったりする。

ローカルサーバ起動時のWarning

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

解決方法

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

なので、GAE/Goが実行可能なDockerイメージを作ってみた。1
DockerHub - ryutah/gcloud-gaego

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

試してみる

コンテナでローカルサーバを実行できるように、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でサーバー起動

警告文が出なくなった。

この状態でファイルを編集して、変更を検知できているか確認してみる。
ファイル変更

やったぜ。

参考

Go でツール書くときの Makefile 晒す - Qiita
Makefileを自己文書化する - POSTD


  1. すでにGAE/GoのDockerイメージはいくつかありますが多分これが一番イメージサイズが小さいと思います 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away