Google App EngineでGoのウェブアプリケーションをまず動かしてみる

google app engineでウェブアプリケーションを
とりあえず動かしてみたいときに行う場合。

# アプリケーションを動かす

まずはアプリケーションを動かすところまで。

## SDKのインストール

デプロイやローカルで動かすためにSDKをインストールする。
[Google App Engine SDK](https://cloud.google.com/appengine/downloads#Google_App_Engine_SDK_for_Go)

```bash
brew install go-app-engine-64 # 32bitの場合はgo-app-engine-32
```

## configを置く

configファイルとなる`app.yaml`を置く

```yaml
application: gaesample
version: 1
runtime: go
api_version: go1

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

アプリケーションは、プロジェクトidを入れる。
https://console.developers.google.com/project から作ったりする。

## goファイルを置く

```go:main.go
package gaesample

import (
  "fmt"
  "net/http"
)

func init() {
  http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "%s", "Hello World")
}
```

`http.ListenAndServe()`を書く必要がない。

## ローカルで実行

```bash
goapp serve
# アクセスできるようになる
$ curl  http://localhost:8080                                                                     
Hello World
```

## deploy

`goapp deploy`で、アプリケーションをデプロイできる。

# Routing

`http.HandleFunc()`を使って、設定もしていけるが、
[Goji](https://goji.io/)を使って、ルーティングできるようにする。

```bash
go get github.com/zenazn/goji
```

```go:main.go
import (
  "fmt"
  "net/http"
  "github.com/zenazn/goji"
  "github.com/zenazn/goji/web"
)

func init() {
  http.Handle("/", goji.DefaultMux)

  goji.Get("/", handler)
  goji.Get("/user", handler2)
  goji.Get("/user/:name", handler2)
}

func handler(c web.C, w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "%s", "/")
}

func handler2(c web.C, w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "%s/%v", "/user", c.URLParams["name"])
}
```

結果

```bash
$ curl  http://localhost:8080
/
$ curl  http://localhost:8080/user
/user/
$ curl  http://localhost:8080/user/123
/user/123
```

# View

`html/template`を使った場合,

```html:views/layout.html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    {{ template "content" . }}
  </body>
</html>
```

```html:views/home.html
{{ define "content" }}
<p>Main Page {{ .Name }}</p>
{{ end }}
```

```go:main.go
func init() {
  http.Handle("/", goji.DefaultMux)

  goji.Get("/", homeHandler)
}

func render(v string, w io.Writer, data map[string]interface{}) {
  tmpl := template.Must(template.ParseFiles("views/layout.html", v))
  tmpl.Execute(w, data)
}

func homeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
  data := map[string]interface{}{
    "Name": "home",
  }
  render("views/home.html", w, data)
}
```

ファイル構成

```
$ tree .
.
├── app.yaml
├── main.go
└── views
    ├── home.html
    └── layout.html
```

`goapp serve`したあとにアクセスすれば、きちんとhtmlが返ってくる。

```html
% curl http://localhost:8080/
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>

<p>Main Page home</p>

  </body>
</html>
```

# Debug log

`appengine.context`にデバッグ用の関数が一通り用意されている。

```go
func (c *context) Debugf(format string, args ...interface{})    { c.logf("DEBUG", format, args...) }
func (c *context) Infof(format string, args ...interface{})     { c.logf("INFO", format, args...) }
func (c *context) Warningf(format string, args ...interface{})  { c.logf("WARNING", format, args...) }
func (c *context) Errorf(format string, args ...interface{})    { c.logf("ERROR", format, args...) }
func (c *context) Criticalf(format string, args ...interface{}) { c.logf("CRITICAL", format, args...) }
```

# Datastore API

`appengine/datastore` を使ってデータを保存したり、取り出したりする場合。

## modelの定義

ひとまずどんなデータを保存するかを決める

```go:models.go
package gaesample

import "time"

type User struct {
  Name     string
  Role     string
  HireDate time.Time
}
```

## データの保存

`appengine/datastore`を使って、データを保存できる。

```go
import (
  // ...省略
  "appengine"
  "appengine/datastore"
)

// ...

func homeHandler(c web.C, w http.ResponseWriter, r *http.Request) {

  context := appengine.NewContext(r)

  el := User{
    Name:     "Joe",
    Role:     "Manager",
    HireDate: time.Now(),
  }

  key, err := datastore.Put(context, datastore.NewIncompleteKey(context, "users", nil), &el)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // ...
}
```

詳細は https://cloud.google.com/appengine/docs/go/datastore/ 

## データの取り出し

```go
func usersIndexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
  context := appengine.NewContext(r)
  q := datastore.NewQuery("users").Limit(10)

  users := make(map[string]User, 0)

  for t := q.Run(context); ; {
    var user User
    key, err := t.Next(&user)
    if err == datastore.Done {
      break
    }
    if err != nil {
      http.Error(w, err.Error(), 500)
      return
    }
    users[key.String()] = user
  }

  data := map[string]interface{}{
    "Name":  "users/index",
    "Users": users,
  }
  render("views/users/index.html", w, data)
}
```

```html:views/users/index.html
{{define "content"}}
<h2>Users List</h2>

<table class="table">
  <thead>
    <tr>
      <td>Key<td>
      <td>Name<td>
      <td>Role<td>
    </tr>
  </thead>
{{range $index, $group := .Users}}
  <tr>
    <td>{{$index}}<td>
    <td>{{$group.Name}}<td>
    <td>{{$group.Role}}<td>
  </tr>
{{end}}
</table>
{{end}}
```

データを入れてアクセスすれば、それっぽく表示される

![image](https://qiita-image-store.s3.amazonaws.com/0/3163/333488fc-9acc-6324-2c59-9fae35347f29.png)

# テスト

Unit Testを書く場合は[appengine/aetest](https://cloud.google.com/appengine/docs/go/tools/localunittesting/#Go_Introducing_the_aetest_package) を使う。
(app engine testでaetestっぽい)

```go:main_test
package gaesample

import (
  "testing"
  "time"

  "appengine/aetest"
  "appengine/datastore"
)

func TestUserIndexHandler(t *testing.T) {
  c, err := aetest.NewContext(nil)
  defer c.Close()
  if err != nil {
    t.Fatal(err)
  }

  key := datastore.NewKey(c, "users", "stringId", 0, nil)
  el := User{
    Name:     "Joe",
    Role:     "Manager",
    HireDate: time.Now(),
  }

  if _, err := datastore.Put(c, key, &el); err != nil {
    t.Fatal(err)
  }

  user := User{}
  if err := datastore.Get(c, key, &user); err != nil {
    t.Fatal(err)
  }
  if user.Name != "Joe" {
    t.Errorf("Not equal user.Name")
  }
  if user.Role != "Manager" {
    t.Errorf("Not equal user.Name")
  }
}
```

ローカルでのテスト実行の仕方は

```bash
$ goapp test
```

# 参考

* [Go Runtime Environment](https://cloud.google.com/appengine/docs/go/)
* [Go Datastore API](https://cloud.google.com/appengine/docs/go/datastore/)
* [Local Unit Testing for Go](https://cloud.google.com/appengine/docs/go/tools/localunittesting/)