えいのうにっき

a-knowの日記です

GAE本の 3.2 章を Go で写経したので、そこでの学びをメモするよ

GAE本。

Google API Expertが解説する Google App Engine for Java実践ガイド

Google API Expertが解説する Google App Engine for Java実践ガイド

台風が、自分の出身地である岡山を珍しく直撃しているこの連休、その岡山に私用で帰省しているんだけど、先週も参加させて頂いた #golangcafe が今週も開催されるということで(本来ならば日曜夜なんだけど、無理言って今日にしていただいたm( )m)、2週連続で参加させて頂いた。

今回はもくもく形式で、ということだったので、ちょうど先週から取り組んでいる GAE本の Go での写経の続き・3.2章をやることに。#golangcafe は2時間ほどで切り上げる形となったんだけど、家に戻ってからも少し手を付けて、なんとかこの章は終えられたので、この章の写経を通じて学んだことをメモしておくことにする。

3.2章の概要

スクショは↓なかんじ。

f:id:a-know:20141013205213p:plain

f:id:a-know:20141013205229p:plain

学んだことメモ

Datastore に記録される struct には、デフォルトでは そのエンティティの key は含まれない

例えば下記のような struct を使って Datastore に登録したのちにそのエンティティを get したとき、そのエンティティの key の情報は含まれない。

type Minutes struct {
    Title   string
    CreatedAt time.Time
}

何らかの理由で key の情報が必要な場合は、そのためのエンティティを struct 内に設けてやる必要がある。ということで、単純に考えると下記のようになると思う。

type Minutes struct {
    Key *datastore.Key
    Title   string
    CreatedAt time.Time
}
   key := datastore.NewIncompleteKey(c, "minutes", nil)

    m1 := Minutes{
        Key:       key,
        Title:     title,
        CreatedAt: time.Now(),
    }

    // put
    _, err := datastore.Put(c, key, &m1)

ただこの実装だと、Datastore に登録されるエンティティのプロパティに Incomplete な key を登録してしまう形となり、BadValueError: Incomplete key found for reference property Key. エラーが出て admin console から datastore の中身を覗けなくなってしまう(これの解消については後述)。

これを避けるためには、エンティティの登録前に完全な key を作成するか、「エンティティを get したときに key を struct にもたせてやる」というようなアプローチが必要になる。

下記のツイートを起点としたやりとりが、とても参考になった。公式クライアントとかで見てみて欲しい。

これの理想的なアプローチとしてはやはり、「エンティティ取得時に key を持たせてくれるような仕組みを作る」ということなんだろうけど(それくらいしか思いつかない)、とはいえ今回作ってるのはガチのアプリケーションではなく、サンプルアプリの写経なので(言い訳)、UUID を key_name にして完全な key を作って対応することにした。泣きながら

  • $ go get code.google.com/p/go-uuid
    • Mercurial がないと go get できないので入れる...(´・ω・`)
    • $ brew install mercurial
  • 気を取り直して、 $ go get code.google.com/p/go-uuid

最終的な実装は、下記のような形で落ち着いた。

   key := datastore.NewKey(c, "memo", uuid.New(), 0, nil)

dev server の Datastore の中身をクリアしたいときは

ローカルでの動作確認時に Datastore に put したデータは、ローカルでファイルとして管理されており、内容をクリアすることもできる。 上で後述、と書いたケースのような、incomplete な key をプロパティに持つエンティティを登録してしまった、などといったときに Datastore をまるごとクリアしたいときは、 dev_appserver.py --clear_datastore=yes myapp で、dev server の datastore をクリアしてからの dev server の立ち上げができる。

The Go Development Server  |  App Engine standard environment for Go  |  Google Cloud も参考に。

リクエストパラメータの取得方法について

クライアント(今回は html(JS))からのリクエストパラメータは Request#FormValue で取れる。POST / GET 両方とも、これで取れる :)

func Post(w http.ResponseWriter, r *http.Request) {
    title := r.FormValue("title")

おまけ。取った値の validation を if value == nil とかでやろうとしてしまったけど、string のゼロ値は空文字 "" だった...。これ、しばらく何度か同じミスしそう。

HTTP メソッドでの切り分けはいい方法あるかな?

お手本では GET/POST のメソッドで処理の切り分けができるようにしてるけど、 gae/g でこれをスマートにやるとしたらどんな方法がいいのかな。とりあえず今回はそこは意図的に無視した。

プロパティに Key を持つエンティティに対するクエリフィルタって...

どうやるのかなと思って、単純に下記のように書いてみた。 あと、降順のときはそのプロパティ名称の前に - を付けてたので、その逆だから + かな、と思ったんだけど。

q := datastore.NewQuery("memo").Filter("Minutes", minutesKey).Order("+CreatedAt")

結論、これだとダメで、下記のようにするのが正解。Filter のときに等号がいるのと、昇順の場合は記号は不要だった。ドキュメント見てねって話。

q := datastore.NewQuery("memo").Filter("Minutes =", minutesKey).Order("CreatedAt")

そのプロパティが key であることを気にする必要はないような書き方だけど、フィルタの対象が「そのエンティティのキー」の場合だと、下記のように書く必要があるみたい。

q := datastore.NewQuery("Person").Filter("__key__ >", lastSeenKey)

(これって、key での get になるのかな。それとも query?)

slim3 でいう Datastore#stringToKey(keyToString)は...

Key#Encode() Key#DecodeKey(string) で可能。

minutesKeyString := r.FormValue("minutes")
minutesKey, err := datastore.DecodeKey(minutesKeyString)

しかも、Key プロパティを持つエンティティを Json#Marshal してクライアントに返す場合でも...

素敵。抱いて!!

次は 3.3章、Users Service。

今度も同じような感じで写経&わかったことをメモしようと思う。