iPhoneで Grafanaの グラフを 参照できる アプリ Grafanizer 作ってます。 詳しくは こちらへ

このページ からの続き記事になってますので、先にそちらを見てみておかないと意味がわからないかもです。

  1. go testgoapp test 用にテストを分離 ← 前のページ
  2. DataStore用にContextを切り替え ← 前のページ
  3. テスト時特有の設定 ← 前のページ
  4. goonとの違いとデータ変換
  5. 自動MemCache

4. goonとの違いとデータ変換

さて、AppEngineでプログラムを作ると一番お世話になるのはDataStoreで、DataStoreへのアクセスは goon を利用しているって人は多いと思います。

ところが、goon はDataStore専用のライブラリなので luci/gae に付いてくるテスト用のInMemoryデータベースには使うことができません。

なので、必然的に luci/gae に付属しているDataStoreライブラリを利用することになります。いや、正しくは「 luci/gae を使ったテストのときは」ですが、 goonluci/gae を併用するということは無いと思いますのでいいでしょう。

luci/gae に付属しているDataStoreもほぼ goon と同じように使え、自動MemCacheもついてきます。ちなみに、ドキュメントにも「 goonにインスピレーションを受けている 」と書かれてますね。

GetやPutなどについては付属ドキュメントを読めばすぐ分かるのですが、移行についてちょっとわかりづらくてハマった点がありますので対応方法列挙しておこうと思います。

Structのタグ

luci/gaegoon と同じくStructからのKey自動採番の機能を持ってます。

type Content struct {
  ID   string `json:"id" goon:"id" gae:$id`
  Body string `json:"body"`
}

と書けば、IDの値をKeyとしてくれるので便利です。

ですが、DataStoreに投入されるデータに違いがあります。

goonKey | ID | Body というようにIDとKeyを別々に持つのですが、luci/gae の方は Key | Body となりフィールドが少なくなっちゃいます。 保存データサイズにも違いが出るのでフィールドが少なくなるのは大歓迎なのですが、データが既に入っていると 「DataStore - フィールド削除してハマった(´・ω・`)」 というページにあるように問題が発生します。

同じページの追記にあるとおり ErrFieldMismatch を無視すれば良さそうなのですが、コンバートしてしまったほうが速いと思います。

コンバートと言っても、goon で全件Getして、削除した後に luci/gae でPutしてやるだけです。件数が多いときはもう少し考えなきゃいけないとは思いますがそれは各自でどうぞ。

// convert.go
func convert(r *http.Request) error {
    var entity = []Content{}
    q := datastore.NewQuery("Content")

    n := goon.Newgoon(r)
    keys, err := n.GetAll(q, &entity)
    if err != nil {
        return err
    }

    err = n.DeleteMulti(keys)
    if err != nil {
        return err
    }

    ctx := prod.Use(appengine.NewContext(r), r)
    err = luci.Put(ctx, entity)
    if err != nil {
        return err
    }

    return nil
}

長いテキスト

先程のStructのBodyフィールドですが、もし1,500byte以上あるとエラーになります。なんで、noindex タグを付けることになると思うのですが、 goon とちょっと挙動が違います。

goon の場合は DataStore:",noindex" があればいいのですが、 luci/gae はDataStore用のタグを見てくれないようです。そこで、下記のように luci/gae 用にも同じタグを追加してあげます。

type Content struct {
  ID   string `json:"id" goon:"id" gae:$id`
  Body string `json:"body" DataStore:",noindex" gae:",noindex"`
}

細かい点ではGetにMultiがPutにAllが無いという点等もありますが、そこはコンパイル時にエラーが発生するのでそれほど問題ではないでしょう。

Queryの組み立てついて

Queryについても違いがあります。goon ではDataStoreのQueryをそのまま使うのですが、 luci/gae ではQueryの指定もオリジナルになってます。

で、僕みたいな人が問題になるのは .Eq.Lte 等のフィルター用メソッド。専用メソッドが用意されているのは符号の間違い防止に効果てきめんなのですが、いかんせん >Lt とか一瞬で出てこない頭なので効率が悪い...

ということで、

type Filter struct {
    Field    string
    Operator string
    Value    interface{}
}

なるStructを作り、

for _, filter := range queryVars.Filter {
    f := filter.Field
    v := filter.Value
    switch filter.Operator {
    case "=":
        q = q.Eq(f, v)
    case ">":
        q = q.Lt(f, v)
    case ">=", "=>":
        q = q.Lte(f, v)
    case "<":
        q = q.Gt(f, v)
    case "<=", "=<":
        q = q.Gte(f, v)
    }
}

な処理を作ってみました(笑

5. 自動MemCache

は超簡単。GetやPutする前に ctx = dscache.FilterRDS(ctx) を入れるだけのお手軽さ。

(長くなってきて書くのがそろそろ面倒になってきたので、このくらいでそろそろ...)


なかなか良いライブラリを発見した。テストの時間が長いって相当ストレスが貯まるよね。