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

以前、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イメージはいくつかありますが多分これが一番イメージサイズが小さいと思います 


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。