完全にアドベントカレンダーの存在を忘れて遅れてしまいました。
関係者各位様本当に申し訳ございません。。。。
ということで。
今年のAdvent CalenderはGAE/Goネタが多いですね、とても素敵です。
願わくば早くβの文字が外れてほしい限りです。
今回はGAE/GoのDatastore周りを楽にするgoonの紹介をしたいと思います。
ちょこちょこ昔書いたQiitaにも登場させているのですがあまーつ使っている人を見たことがないので...
GAE/Goの標準Datastoreパッケージとちょっとした不満
GAE/GoのDatastore周りは、appengine SDKのdatastoreパッケージが結構素敵で、
structを定義すればかなり簡単にDatastoreを扱うことができます。
//GAE/Goのdatastore周り
//https://cloud.google.com/appengine/docs/go/datastore/より
type Employee struct {
Name string
Role string
HireDate time.Time
Account string
}
func handle(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
e1 := Employee{
Name: "Joe Citizen",
Role: "Manager",
HireDate: time.Now(),
Account: user.Current(c).String(),
}
key, err := datastore.Put(c, datastore.NewIncompleteKey(c, "employee", nil), &e1)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var e2 Employee
if err = datastore.Get(c, key, &e2); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Stored and retrieved the Employee named %q", e2.Name)
}
ただ幾つか面倒臭いこともあって
- memcacheへの自動的にキャッシュして欲しい
- keyとか特定プロパティから自動生成して欲しい
- Kind名とかType名と同じで良いからわざわざ指定して作りたくない
あたりの小さなストレスが有ります。
そこでGoonの登場です。
Goon
GoonはGAE/Go用の自動キャッシュ付きDatastoreライブラリで、NDBを参考にしており以下の様な特徴を持ちます。
- DatastoreのKeyをキャッシュキーとしたin-memoryとmemcacheへの自動キャッシュ
- Type名を利用したKind名の自動解決
- Datastore KeyやParent KeyをTypeの特定タグが付けられているフィールドを利用して自動生成
殆どの場合において標準のDatastoreパッケージを利用するよりGoonを利用したほうがDatastore周りのコードは短くなります。
使い方
では使い方を書いていきます。
今回は昔会社向けに作った勤務時間の入力アプリで使っていた「稼働時間」を表すEntityを例に上げていきたいと思います。
goonのインストール
とりあえずgo getします。
$ go get github.com/mjibson/goon
Typeの作成
まずEntity用のTypeを作ります。
//稼働時間Entity
type Worktime struct {
Ymd string `datastore:"-" goon:"id"`
MemberKey *datastore.Key `datastore:"-" goon:"parent"`
StartTime string `datastore:"startTime,noindex"`
EndTime string `datastore:"endTime,noindex"`
RestTime string `datastore:"lateRestTime,noindex"`
Status WorkStatus `datastore:"status"`
Etc string `datastore:"etc,noindex"`
Worktime int `datastore:"worktime,noindex"`
}
通常のdatastore
に加えて、YmdフィールドとMemberKeyフィールドにgoon
タグがついています。
goon:"id"タグはそのフィールドがdatastore.KeyのName値として利用することを宣言しています。
goon:"parent"タグはそのフィールドがdatastore.KeyのParent Keyとして利用することを宣言しています。
共にDatastoreのEntityとしては保存しないため(取得する場合はKeyから取得する)datastore:"-"タグが付与されています。
今回の場合はMemberKey(メンバーを表す)をParent KeyとしてYmd(日付)をKey Nameとした*datastore.Key
を作成し、それをEntityのKeyとしています。
Goonインスタンスの作成
次にGetやPutをするためのGoonインスタンスを作成します。
Goonインスタンスは*http.Request
か、appengine.Context
から作成します。
func makeGoon(r *http.Request) goon.Goon {
g := goon.NewGoon(r)
return g
}
なおaetest(GAE/GoのUnitTest)などと利用する場合はappengine.Contextから直接作成します。
func Test() error {
if c, err = aetest.NewContext(&aetest.Options{}); err != nil {
panic(err.Error())
}
g = goon.FromContext(c)
}
Put
では上記を利用して実際にDatastoreへPutしたいと思います。
基本的な手順は
- Goonインスタンスを作る
- 対象のEntityに値を詰める
- GoonインスタンスのPutメソッドでPutする
です。
func PutWorktime(r *http.Request, memberKey *datastore.Key) (*Worktime, error) {
g := goon.NewGoon(r)
//goonタグの値以外を作る ※サンプルコードのため省略
w, err := createWorktime(r)
if err != nil {
return nil, err
}
//Parent Keyを詰める
w.MemberKey = memberKey
//Datastore KeyのNameを詰める
w.Ymd = "2014-12-24"
//g.PutでPut KeyやKind名はGoon側で解決
//またPut時にMemcacheやIn-Memory Cacheしてくれる
//なお返却値は *datastore.Key, error
if _, err := g.Put(w); err != nil {
return w, err
}
return w, nil
}
通常だとKeyの作成や、Kind名の直打ち、その後のMemcacheへ詰めたりとがありますが
その辺りが無くなって割とすっきりします。
Get
次にGetです。
Getも基本的に*datastore.Keyは意識する必要がなくgoon:"id"が付いているフィードに値を抓るだけでよいです。
基本的な手順は
- Goonインスタンスを作る
- 対象のEntityのgoon:"id"フィールドにKey名を詰める
- GoonインスタンスのGetメソッドでGetする
func GetWorktime(r *http.Request, ymd string, memberKey *datastore.Key) (*Worktime, error) {
g := goon.NewGoon(r)
//goon:"id"とgoon:"parent"(ある場合)に値を詰める
w := &Worktime{
Ymd : ymd,
MemberKey : memberKey
}
//Getは自動的にmemory→memcache→datastoreの順で検索してくれます。
//時々memcacheが恐ろしく遅い場合もある程度のところでtimeoutしてdatastoreを検索しに行ってくれます。
if err := g.Get(w); err != nil {
return nil, err
}
return w, nil
}
GetMultiを使うと複数のKeyに対して一気にGetを行うことができ、それ自体もcacheからの取得をサポートしています。
割とDatastoreの定番技ですが、
今回のような「日付」をKeyとして使っていて、日付の範囲検索を行いたい場合、(例えば2014年5月のデータを全て取得など)
下手なdatastore.Queryを使ったIndex検索より、対象期間全ての日付を作成してGetMultiで検索するとmemoryに乗っているものはmemoryから、Memcacheに乗っているものはMemcacheから、残りをDatastoreから取得のようなことができ、
検索コストを抑えることができたりします。
Cursor
コード自体はgoonのドキュメントに乗っているので、掲載しませんが、
goonではCursorを使っている場合でもキャッシュ上に存在するデータはキャッシュから取得してくれるため検索コストを抑えることができます。
まとめ
GAE定番のDatastoreの結果はとりあえずMemcacheへというのが楽にできるgoonは結構素敵です。
元々のdatastoreパッケージからそこまでコードも変わらないため、移行も結構楽にできます。
また開発自体も活発ではないですがほそぼそと続けているようです。
※というより薄いライブラリなのでそこまで発展していかない?
是非GAE/Goを使うときは使ってみてください。