堀内 晨彦
@hico_horiuchi
Ginとbindataで作る
シングルバイナリWebApp
自己紹介
Akihiko Horiuchi
香川大学 修士2年
bit.ly/hiconyan
研究室のインフラと掃除担当
github.com/sai-lab/mouryou
github.com/sai-lab/mouryou-web
Emacs / Ruby / Rails / Golang
Hubot / Sensu / Ansible
16/01/23 2
シェルスクリプトマガジン!
●  「香川大学SLPからお届け!」シリーズ
○  Vol. 33 Go言語とMithril.jsでWebアプリ開発 バックエンド編
○  Vol. 34 Go言語とMithril.jsでWebアプリ開発 フロントエンド編
○  (Amazonで著者セントラル作りたい…)
16/01/23 3
GoでWebAppを作るメリット
●  シングルバイナリ でデプロイ
○  コンパイルしたバイナリをアップロードするだけ
○  ライブラリのインストールなどが要らない
○  コンテナ や マイクロサービス との親和性が高そう
●  インタプリタと比較して 高速・軽量
○  Railsより50倍の速度と10倍の省メモリという話も[1]
●  コーディング規約 を統一
○  gofmt(goimports)で強制的に直される
○  可読性が上がる、チーム開発がスムーズになる
16/01/23 4
[1] https://plus.google.com/+MattAimonetti/posts/PeZk8FY3PWY
GoでWebAppを作るデメリット
●  Webフレームワーク 戦国時代
○  シンプル < Gin < Goji < Martini < Revel < フルスタック
○  Railsのようなデファクトスタンダードが決まっていない
●  ORマッパーも 戦国時代[2]
○  シンプル < gorp < Genmai < GORM < フルスタック
●  アセット どうするの問題
○  バイナリ + HTML + CSS + JavaScript ?
○  bindata でバイナリ化・ソースコード化できる
○  tdewolff/minify でMinifyができるらしい
16/01/23 5
[2] http://hachibeechan.hateblo.jp/entry/2015/03/25/Go言語でのORMを色々検討してみた
デモンストレーション
「kakuzuke」
http://kakuzuke.hiconyan.com/
https://github.com/hico-horiuchi/kakuzuke
16/01/23 6
デモンストレーション
「yosage」
http://yosage.hiconyan.com/
https://github.com/hico-horiuchi/yosage
16/01/23 7
オススメのパッケージ
●  github.com/gin-gonic/gin
○  構造体をJSONで返せるのでAPIサーバとして使う
●  github.com/yosssi/ace
○  Slimライクに html/template を書ける
●  github.com/jteeuwen/go-bindata
○  ファイルを []byte のソースコードに変換する
●  github.com/pilu/fresh
○  ソースコードの変更を検知してビルドと再起動をする

●  github.com/mattn/gom
○  Gemライクなパッケージ管理ツール
16/01/23 8
僕が考えた最強のディレクトリ構成
16/01/23 9
webapp/
├ app/
│ ├ controllers/ 
Mithril.jsのコントローラ
│ ├ views/ 
Mithril.jsのビュー
│ └ app.js 
Mithril.jsの本体
├ assets/
│ ├ fonts/ 
Webフォント(eot、svg、woff)
│ ├ images/ 
Faviconなど
│ ├ javascripts/ 
mithril.min.jsなど
│ └ stylesheets/ 
bootstrap.min.cssなど
├ lib/
│ ├ controllers/ 
Ginのコントローラ
│ ├ models/ 
構造体の定義
│ └ views/ 
Aceのテンプレート
├ Gomfile
├ Makefile
└ router.go 
Ginのルーティングなど
今回のポイント・工夫
●  GinでMVCデザインのAPIサーバを作成
○  これまでRails + Grapeで開発していたので
○  Viewはヘッダー・ナビゲーションバー・フッターのみ
●  フロントエンドはMithril.jsで作成
○  MVCデザインのJavaScriptフレームワーク
○  仮想DOM 採用で、Reactよりも 高速・軽量
○  モデルはAPIから返ってきたJSONをそのまま使う
○  ソースコードはバイナリ化してGinで配信
バックエンドはGoで最速のフレームワーク
フロントエンドもJavaScriptで最軽量のフレームワーク
16/01/23 10
GinでAPIサーバを作成(1)
●  モデルを構造体で定義
○  lib/models/user.go
○  `json:"login"` のようなタグ(アノテーション)を付ける
16/01/23 11
type User struct {
Login *string `json:"login"`
AvatarURL *string `json:"avatar_url"`
CurrentStreak *int `json:"current_streak"`
Me *bool `json:"me"`
}

type UserList []*User
GinでAPIサーバを作成(2)
●  コントローラでJSONを返却
○  lib/controllers/ranking_controller.go
○  c.JSON で構造体をJSONとして返せる
16/01/23 12
type RankingController struct {
controller
} 


func (_ RankingController) ShowAPI(c *gin.Context) {
username := c.Param("username")
followees := GitHubClient.Users.ListFollowing(username)
users := make([]*models.User, len(followees))
var list models.UserList = users
sort.Sort(list)
c.JSON(http.StatusOK, list)
}
Aceでテンプレートを作成
●  ヘッダーとナビゲーションバーのみ
○  lib/view/layout.ace
16/01/23 13
= doctype html
html lang="en"
head
title kakuzuke
link href="/assets/stylesheets/bootstrap.min.css" rel="stylesheet"
body
nav.navbar.navbar-fixed-top.bg-white
.container
a#app-title.navbar-brand.text-kakuzuke href="/"
i.fa.fa-trophy.m-r-sm
span.bold kakuzuke
.container.m-y-md
= yield main
script src="/assets/javascripts/mithril.min.js"
script src="/app/app.js" type="text/javascript"
bindataでアセットをバイナリ化
●  app/ と assets/ と lib/views/ をバイナリ化
○  Makefile で変換するようにした
○  go-bindata-assetfs を使うと http.FileSystem になる
○  -debug=true ではバイナリ化せずにファイルを開く
○  結果は package main な bindata_assetfs.go に吐き出される
16/01/23 14
bindata:
gom exec go-bindata-assetfs ./app/... ./assets/... ./lib/views/...

debugdata:
gom exec go-bindata-assetfs -debug=true ./app/... ./assets/...
Ginでbindataを配信
●  ここがちょっと面倒
○  router.go
○  Ginでは http.FileServer ではなく static.Serve を使う
○  http.FileSystem も使えないので BinaryFileSystem を作る
○  gin-gonic/contrib/static/example/bindata/example.go
16/01/23 15
func main() {
ranking := controllers.RankingController{}
router := gin.Default()
router.Use(static.Serve("/app", BinaryFileSystem("app")))
router.Use(static.Serve("/assets", BinaryFileSystem("assets")))
router.GET("/api/ranking/:username", ranking.ShowAPI)
router.Run()
}
freshでビルドしながら開発
●  ソースコードの変更を検知してリビルド
○  変更の反映にコンパイルが必要なGoの強い味方
○  JavaScriptだとGrantとかgulpみたいな感じ?
○  監視する拡張子と間隔を設定できる
16/01/23 16
$ gom exec fresh -c runner.conf
Loading settings from runner.conf
22:7:30 runner | InitFolders
22:7:30 runner | mkdir ./tmp
22:7:30 runner | mkdir ./tmp: file exists
22:7:30 watcher | Watching .
22:7:30 watcher | Watching app
22:7:30 watcher | Watching app/controllers
22:7:30 watcher | Watching app/views
まとめ・おわりに
●  Ginとbindataで シングルバイナリ を実現
○  kakuzukeだと最終的なバイナリが 8MB 程になる
○  bindata_assetfs.go で 2MB 、Minifyすれば軽量化できそう
●  GomからGodepに移行したい
○  Gomはシンプルだけどリビジョンを付けてくれない
○  Herokuで .godir は非推奨(Godep使えと言われる)
○  gin-gonic/contrib#75 みたいな時に面倒くさい
●  bindataを生成するタイミング
○  gitignore に追加すべきかどうか
○  複数人で開発するときにはルールが必要そう
16/01/23 17
Golang勉強会 in Kagawa
「Golang勉強会 in Kagawa」を開催します!
●  日時 :2016年2月27日(土) 13:00∼18:00
●  場所 :e-とぴあ・かがわ BBスクエア
●  URL:http://gdgshikoku.connpass.com
○  前半はハンズオン、後半はセッションの予定
○  登壇者は @tenntenn 、@taknb2nch 、@hico_horiuchi
○  発表者募集中です!詳細は @hico_horiuchi まで
16/01/23 18
with

Ginとbindataで作るシングルバイナリWebApp