GAE/Goで
Webアプリ開発入門
2016/11/26(土)
@GDG石巻×GCPUG仙台
Devfest 2016
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license.
自己紹介
メルカリ/ソウゾウ
上田拓也
twitter: @tenntenn
■ Go歴 / GAE歴
Go:5〜6年くらい?
GAE:最近再開、GCPUG Tokyoのスタッフ
■ 業務
GAE/Goでメルカリアッテを作ってます
Goのコミュニティを盛り上げる仕事
Gopherを描く仕事(LINEスタンプ)
アジェンダ
■ Go入門(30分)
● Goの特徴
● Tour of Goをやってみよう
● Goを学習する方法
■ GAE/Go入門(2時間)
● 開発環境を用意しよう
● ゲストブックを作ってみよう
● デプロイしてみよう
Go入門
Goの特徴
Googleが開発しているプログラミング言語
■ 特徴
● 強力でシンプルな言語設計と文法
● 並行プログラミング
● 豊富な標準ライブラリ群
● 周辺ツールの充実
● シングルバイナリ/クロスコンパイル
A Tour of Go
■ Web上で行えるチュートリアル
● https://go-tour-jp.appspot.com
● 日本語版もある
● まずはBasicsとFlowControlをやってみよ
う!
Goを学習する方法
公式ドキュメント
■ パッケージドキュメント
● https://golang.org/pkg
● 標準パッケージの使い方が書いてある
■ FAQ
● https://golang.org/doc/faq
● なぜGoに◯◯がないのか?など
■ 言語仕様
● https://golang.org/ref/spec
公式ドキュメントを読もう!!
Playground
■ Go Playground
● http://play.golang.org/
● Web上でGoを実行できる
● Share機能で、SNSで共有したり質問する
その他
■ コミュニティ
● Gophers Slack #japan
● Google+ Golang JP
■ Qiita
● Goタグでまとまっている
● Go言語の初心者が見ると幸せになれる場所
■ 書籍
● プログラミング言語Go
● Go in Action
● みんなのGo言語
GAE/Go入門
Google App Engine (GAE)
■ Google が提供するPaaS
● 高いスケーラビリティ
● メンテナンスコストが低い
■ スタンダード環境とフレキシブル環境
● スタンダード環境
○ 従来からあるGAEの環境、SEとも
○ Go、Java7、Python 2.7、PHPが使える
○ インスタンスの起動が恐ろしく早い
● フレキシブル環境
○ 旧MVMs、FEとも
○ 実際はGCE上で動いている
○ Go、Java8、Python 2.7/3.4、Node.js、Ruby
SDKのインストール
■ 公式サイトからダウンロード
● https://cloud.google.com/appengine/docs/
go/download
● WindowsはPython2.7も必要
○ すでにPython 3.x系が入ってる場合は注意
■ zipファイルの展開
● ダウンロードしたzipをホーム以下に展開
■ PATHの設定
● go_appengineにPATHを通す
$ export PATH=$HOME/go_appengine:$PATH
SDKの確認
■ goappの確認
■ dev_appserver.pyの確認
■ appcfg.pyの確認
$goapp version
go version go1.6.3 (appengine-1.9.46) darwin/amd64
$dev_appserver.py -h
usage: dev_appserver.py [-h] [-A APP_ID] [--host
HOST] [--port PORT]
...
$appcfg.py -h
Usage: appcfg.py [options] <action>
...
ゲストブックを作ろう
■ GAE/Goの公式チュートリアル
● https://cloud.google.com/appengine/docs/
go/getting-started/creating-guestbook
● 1ステップごとに学習できる
● httpパッケージの学習
● html/templateパッケージの学習
手元で動かしてみる
■ ソースコードのクローン
■ ローカルサーバで動かす
$git clone
https://github.com/GoogleCloudPlatform/appengine-g
uestbook-go
$git fetch
$git checkout part4-usingdatastore
$goapp serve
http://localhost:8080
をブラウザで開こう
止める際はCtrl+C
Hello, World
■ Part1を動かしてみる
■ ファイル構成
$git checkout part1-helloworld
$goapp serve
$tree .
.
├── app.yaml
└── hello.go
app.yaml
■ アプリケーションの設定ファイル
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
すべてのリクエストをGoで処理
ルーティングやログインの有無など
を設定することができる
hello.go
■ init関数
func init() {
http.HandleFunc("/", handler)
}
パッケージで最初に呼ばれる関数
/というパターンのパスで来たリクエストを
ハンドリングする関数を登録
GAE/Goではmain関数は使用しない
hello.go
■ ハンドラ
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, world!")
}
レスポンスを書き込むWriter
GAE/GoとGo標準でやり方は同じ!
リクエスト
レスポンスとして
"Hello, world!"を返している
ユーザ情報の取得
■ Part2を動かしてみる
■ ファイル構成
$git checkout part2-usingusers
$goapp serve
$tree .
.
├── app.yaml
└── hello.go
コンテキストの取得
■ コンテキストの取得
■ コンテキストとは?
● GAEのAPIを使うのに必要
● 第1引数に渡すことが多い
● 基本的にラップして情報を付加するので、構
造体のフィールドなどには記録しない
c := appengine.NewContext(r)
rはリクエスト
コンテキストの種類
■ appengine.Context
● 初代コンテキスト
● Goの標準とは関係ない
■ golang.org/x/net/context.Context
● Go1.6までのGo準標準のコンテキスト
● 最近のGAEで使用されている
○ google.golang.org/appengine
■ フレキシブル環境対応がされたラッパー
■ context.Context
● Go1.7からのGo標準のコンテキスト
● 現在GAEはGo1.6なので使えない
今回はこれを使う
GAEの主流はこれ
Goの主流はこれ
時代の流れ
ユーザ情報の取得
■ Googleのユーザ情報の取得
■ ログインURLの取得とリダイレクト
u := user.Current(c)
cはコンテキスト
url, err := user.LoginURL(c, r.URL.String())
if err { /* エラー処理 */ }
w.Header().Set("Location", url)
w.WriteHeader(http.StatusFound)
リダイレクト
http.Redirectを使っても大丈夫
ログインとリダイレクト
handler Google
ログイン
リダイレクト
/
ログイン成功
開発環境は開発用の
ログインURLが生成される
ユーザ入力を受け付ける
■ Part3を動かしてみる
■ ファイル構成
$git checkout part3-handlingforms
$goapp serve
$tree .
.
├── app.yaml
└── hello.go
ユーザ入力を受け付ける
■ リクエストからFormデータを取得する
func handler(w http.ResponseWriter,
r *http.Request) {
v := r.FormValue("myvalue")
fmt.Fprint(w, v)
}
<form action="/post" method="post">
<input type="text" name="myvalue">
<input type="submit" value="post">
</form>
Go
HTML
POSTで送られた
値を取得できる
HTMLの表示
■ ResponseWriterにHTMLを書き込む
func root(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, guestbookForm)
}
const guestbookForm = `
<html>
...
</html>
`
HTMLを書き込めばブラウザが
HTMLをレンダリングしてくれる
テンプレートエンジンの利用
■ html/templateを使う
● Go標準のテンプレートエンジン
● text/templateのhtml特化版
■ テンプレートの生成
■ テンプレートに埋め込む
template.New("sign").Parse(signTemplateHTML)
テンプレート名
HTML
template.Mustはエラーを
panicに変換する関数
signTemplate.Execute(w, r.FormValue("content"))
リクエストから貰った値を埋め込む
よく使うテンプレートの記法
■ その文脈でトップレベルのデータを埋め込む
■ フィールドやメソッド
■ 条件分岐
■ 繰り返し
{{.}}
{{.Filed}}
{{.Method arg1 arg2}}
{{if .}}{{.Filed}}{{else}}NO{{end}}
{{range .}}{{.}}{{end}
rangeの中の
{{.}}は要素になる
データの埋め込み
■ HTMLにリクエストの値を埋め込む
const signTemplateHTML = `
<html>
<body>
<p>You wrote:</p>
<pre>{{.}}</pre>
</body>
</html>
`
signTemplate.Execute(w, r.FormValue("content"))
ここに埋め込まれる
Datastoreを使う
■ Part4を動かしてみる
■ ファイル構成
$git checkout part4-usingdatastore
$goapp serve
$tree .
.
├── app.yaml
├── hello.go
└── index.yaml
Datastoreについて
■ Cloud Datastoreとは?
● Googleの提供するKey-Valueストア
■ 各種用語のRDBとの対応
概念 Cloud Datastore RDB
オブジェクトのカテゴリ Kind Table
1つのオブジェクト Entitiy Row
1つのオブジェクト各データ Property Field
オブジェクトに対するユニークなID Key Primary Key
Kindと構造体
■ Kindを表す構造体を作る
type Person struct {
ID int64 `datastore:"-"`
Name string `datastore:"name"`
Age int `datastore:"age"`
}
構造体のフィールドが
Kindのプロパティに対応
structタグでDatastore上の
プロパティ名を指定
`datastore:"-"`にすると
Datastoreのプロパティにはしない
structタグを省略すると
フィールド名がそのままプロパティ名になる
Datastoreにデータを保存する
■ Incompleteなキーを作る
■ 保存する
const k = "Person" // Kind名
key := datastore.NewIncompleteKey(c, k, nil)
親のキーコンテキスト
p := &Person{Name:"A", Age:30}
newkey, err := datastore.Put(c, key, p)
if err != nil {/*エラー処理*/}
p.ID = newKey.IntID()
エンティティのIDの入った完全なキー
Datastoreからデータを取得する
■ キーを指定して取得する
const k = "Person" // Kind名
nID := 100
sID := ""
key := datastore.NewKey(c, k, sID, nID, nil)
var p Person
err := datastore.Get(c, key, &p)
if err != nil {/*エラー処理*/}
p.ID = key.IntID()
どちらかを指定する
複数一気に取得する場合は
GetMultiを使う
Datastoreからデータを取得する
■ クエリで取得する
const k = "Person" // Kind名
q := datastore.NewQuery(k) // クエリの作成
q = q.Filter("Age >=", 20) // フィルター
q = q.Order("Age") // 並べ替え
var ps []*Person
keys, err := q.GetAll(c, &p) // すべて取得
if err != nil {/*エラー処理*/}
for i := range keys {
ps[i].ID = keys[i].IntID() // IDを設定
}
結果整合性と強い整合性
■ 結果整合性
● 更新結果が必ずしも即時読み取り処理に反
映されない
● スケールしやすい
■ 強い整合性
● 更新結果が即時次の読み取り処理に反映さ
れる
● スケールしにくい
参考
:https://cloud.google.com/datastore/docs/articles/balancing-
strong-and-eventual-consistency-with-google-cloud-datastor
Cloud Datastoreは
選ぶことができる
結果整合性の概念図
強い整合性の概念図
Datastoreと結果整合性・強い整合性
■ Cloud Datastore APIと整合性の関係
Cloud Datastore API エンティティの読み取り インデックスの読み取り
グローバルクエリ 結果整合性 結果整合性
キーのみのグローバルクエリ なし 結果整合性
Ancestorクエリ 強い整合性 強い整合性
キーによる検索(get) 強い整合性 なし
Ancestorクエリを
使うと強い整合性が実現できる
参考
:http://www.slideshare.net/enakai/google-cloud-datastore-insid
eout?ref=https://gcpja.connpass.com/event/44024/presentation
エンティティグループ
■ 強い整合性を実現
Ancestorクエリ
■ 祖先キーを指定してAncestorクエリを実行
// 祖先キーの作成 => guestbookKey()
const akind = "Guestbook"
const asid = "default_guestbook"
akey := datastore.NewKey(c,akind,asid,0,nil)
q := datastore.NewQuery("Greeting")
q = q.Ancestor(akey).Order("-Date").Limit(10)
Ancestorクエリになる=強い整合性
エンティティグループへの更新
■ 祖先キーを指定してPutを実行
// 祖先キーの作成 => guestbookKey()
const akind = "Guestbook"
const asid = "default_guestbook"
akey := datastore.NewKey(c,akind,asid,0,nil)
const kind = "Greeting"
key := datastore.NewIncompleteKey(c,kind,akey)
newkey, err := datastore.Put(c, key, &g)
同一エンティティグループへの
更新は1回/秒の制限がある
デプロイしてみよう
このハンズオンでは
■ アカウントを作るのを省きます
● Google グループに入って頂きます
● このグループは私のGCPプロジェクトのメン
バーに設定されています
● [消しました]
あとで消します!
SNS拡散はダメ!
GCPのコンソールを開く
■ プロジェクトを開く
● https://console.cloud.google.com
● 初めての場合は同意するを選ぶ
● プロジェクトを開く→さらに表示
● [project-id]を開く
app.yamlの編集
■ プロジェクトIDとモジュールなどを設定
application: [project-id]
runtime: go
api_version: go1
module: モジュール名
version: main
handlers:
- url: /.*
script: _go_app
プロジェクトID
モジュール名
今回は重複を避けるため
モジュール名はtwitterアカウント
とかにしておいて下さい
バージョン
デプロイする
■ デプロイしてみる
■ ダメだったら(一度だけ)
■ 開いてみる
● https://[modulename]-dot-[project-id].app
spot.com
$goapp deploy .
$rm $HOME/.appcfg_oauth2_tokens
$appcfg.py update --noauth_local_webserver .
Thank you!
twitter: @tenntenn
Qiita: tenntenn
connpass: tenntenn

GAE/GoでWebアプリ開発入門