Engineer in Tokyo

Go! App Engine

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

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>App Engine 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データをテンプレートに渡します。GoはPythonとかより固い言語なので、structの変数型を全部定義しないといけません。面倒くさいからインラインでやっています。

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,
})

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

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