Edited at

GAE + Go + Datastoreでハマったこと

More than 1 year has passed since last update.

Google App EngineからDatastoreにアクセスする時に、ローカル環境では動いていたものが全く動かなくてハマりました。解決できたのでまとめておきます。言語はGoを使ってます。


必要とするContextはappengineパッケージのものを使う

ドキュメントのサンプルには、context.Background()で作ったcontextをdatastoreパッケージの関数に渡していますが、GAE上ではappengine.NewContext(request)で生成したものを渡す必要があります。でないと動きません。

ローカルの開発環境ではDatastoreエミュレータを使うと思いますが、こちらはcontext.Background()で大丈夫でした。


appengine.NewContextを呼ぶのは1回だけにする

複数回呼ぶとエラーになるようです。基礎的なことかな?


cloud.google.com/go/datastoreを使う

Datastoreにアクセスするパッケージは、google.golang.org/appengine/datastoreというものもあります。GAEではappengine.NewContextを使うから、Datastoreもappengine配下のこちらを使ったほうがいいのかなと思いましたが動きませんでした(笑)一部は動くんですが・・・

こちらは公式ドキュメントでもimportされているcloud.google.com/go/datastoreを使いましょう。


Load()でエミュレータと本番で日付型が違う

Datastoreからデータをロードして、それをstructに入れる時、Kindのプロパティ名とstructのフィールド名が一致しない時は、structにLoadを実装します。

func (self * MyKind) Load(properties []datastore.Property) error {

for _, props := range properties {
switch props.Name {
case "title":
v, b := props.Value.(string)
if b {
self.Title = v
}
break

日付型は、エミュレータ環境ではstring(ISO8601)で渡ってくるのですが、本番ではtime.Timeで来ます。よって分岐しておく必要があります。(自分の環境がおかしいのだろうか・・・)

        case "published_at":

dateString, b := props.Value.(string)
if b {
// datastoreエミュレータでは文字列で返ってくる
// Parseしてtime.Timeに変換して入れる
// TODO: self.PublishedAt = date
} else {
// 本番ではTimeで返ってくる
date, b := props.Value.(time.Time)
if b {
self.PublishedAt = date
}
}
break


動かない時はとにかくログに出力しまくる

google.golang.org/appengine/logのlog.Debugf()、Errorf()などで出力すると、GAEのログに出力されます。動かない時、ログを随所に埋め込んで調査しました。


panicの内容はログ出力されない?

このせいで動かない理由を探すのが大変でした。panicを以下のようにハンドルしてログ出力する必要があります。

func handlePanic() {

if err := recover(); err != nil {
// appengine.logを使って出力する
log.Errorf(aeContext, "Handle panic. %+v", err)
}
}

func indexAction() {
defer handlePanic()
// 以下の処理でpanicが起きたらhandlePanicが呼ばれる
}