Engineer in Tokyo

Go! Appengine

昨日、 Go! 言語 の trusted tester 権限を頂いたので、以前に作っていた Guestbook アプリを本番 Appengine にデプロイしてみた。

http://golang-guestbook.ian-test-hr.appspot.com/

結構シンプルなアプリだけど、いくつかのところのコードをピックアップして紹介しようと思います。

まずは、ログイン。 Google アカウントの認証を使っているのですが、こんな感じでできています。

c := appengine.NewContext(request)
current_user := user.Current(c)
if current_user == nil {
    login_url, _ := user.LoginURL(c, "/")
    http.Redirect(w, request, login_url, http.StatusFound)
    return
}

次はデータの読み込み。ここで Greeting エンティティのデータをクエリーで取得しています。 データを取得した後に GetAll() メソッドで greetings というスライス (配列ポインター) に突っ込んでいます。

greetings := &[]Greeting{}
datastore.NewQuery("Greeting").
          Order("-Date").
          GetAll(c, greetings)

データの書き込みはこんな感じ。POST したデータを FormValue() メソッドで取得して、新しい Greeting struct のインスタンスに突っ込んで、 datastore.Put() 関数で、データストアに新しいエンティティを書き込んでいます。 新しいデータを書き込むときに、 datastore.NewIncompleteKey() を使います。未確定キーオブジェクトで、 datastore.Put() は新しいキーを取得するかどうかを判断するみたいです。

body := request.FormValue("body")
if (len(body) > 0) {
    g := &Greeting{
        Body: body,
        AccountId: current_user.Id,
        AccountEmail: current_user.Email,
        Date: datastore.SecondsToTime(time.Seconds()),
    }
    datastore.Put(c, datastore.NewIncompleteKey("Greeting"), g)
}

次は template パッケージの使いを紹介します。html テンプレートをレンダーしてくれるパッケージです。こんな感じで書けます。 if 文の処理は section どいうタグで実現できます。 for は repeated section で実現できます。

<body>
  <div style="float:right">
    {.section CurrentUser} {CurrentUser} <a href="{LoginUrl}">Sign Out</a>
    {.or}
    <a href="{LoginUrl}">Sign In</a>
    {.end}
  </div>

  <h1>Appengine Go! Guestbook <img src="/static/img/appengine-go.png" /></h1>
  <a href="https://bitbucket.org/IanLewis/golang_guestbook/">Source Code</a>
  <form action="/save" method="POST" style="margin-bottom: 50px">
    <div><textarea name="body" rows="10" cols="80"></textarea></div>
    <div><input type="submit" value="Save" /></div>
  </form>
  {.repeated section Greetings}
  <div>
    User: {AccountEmail|userName}<br />
    {Date|date}
    <p style="padding-left:10px">{Body|html}</p>
  </div>
  {.end}

  <div style="text-align:center"></div>
</body>

デートの表示フォーマットを変更するのに、データフォーマッターという機能があります。 {{ hoge|fuga }} みたいに、バーで hoge データを fuga フォーマッターでデータ変更ができる。 HTML をエスケープする html というフォーマッターが標準にあります。上に Body データをエスケープしています。

他のフォーマッターはこんな感じで登録できます。テンプレートを解析するときに、 FormatterMap オブジェクトを渡してあげます。

func userNameFormatter(wr io.Writer, formatter string, data ...interface{}) {
    for _, item := range data {
        s, _ := item.(string)
        splits := strings.Split(s, "@", 2)
        fmt.Fprint(wr, splits[0][0:len(splits[0])/2] + "...@" + splits[1])
    }
}

func dateFormatter(wr io.Writer, formatter string, data ...interface{}) {
    for _, item := range data {
        date, _ := item.(datastore.Time)
        fmt.Fprintf(wr, TimeToTime(date).Format("2006-01-02 15:04:05"))
    }
}

// ...

fm := template.FormatterMap{}
fm["date"] = dateFormatter
fm["userName"] = userNameFormatter
t, _ := template.ParseFile("templates/base.html", fm)

テンプレートをレンダーするときに、 struct データをテンプレートに渡します。 golang は Python とかより固い言語なので、stuct の変数型を全部定義しないといけません。面倒くさいからインラインでやっています。

w.Header().Set("Content-Type", "text/html")
err := t.Execute(w, struct{
     CurrentUser *user.User
     Greetings *[]Greeting
     LoginUrl string
}{
    CurrentUser: current_user,
    Greetings: greetings,
    LoginUrl: login_url,
})

本番 Appengine で見るとアプリのインスタンスレイテンシーは、最大50ms、 平均は大体 20ms。データストアからデータ取得しているにも関わらず、結構早いなと思いました。スピンアップ時間も全然気付きませんでした。もしかして、もっと大きいアプリを作ると変わりますが、 golang は appengine に動かすのが結構面白いかなと思った。

それでは、みんな、 Guestbook でメッセージ残してください!