Google App Engine で開発する上で Datastore を触りました.
MySQL 等の RDS と勝手が違かったのでメモ程度の情報を書いていきます.
用語
種類 (Kind)
いわゆるテーブルです. RDS の用にテーブル内にレコードが存在するのではなく,
あるデータがなんの種類かという感じなのかな. でもテーブルみたいな認識でとりあえず問題ないかと.
エンティティ (Entity)
RDS でいうレコードです. NoSQL なのでスキーマレスです. 配列や地理位置情報なんてのも入れられます(GoogleMapとかに使うのか?). エンティティをさらに入れ子にすることもできます.
これらの値はエンティティに対してプロパティと呼ばれます. それぞれのプロパティはインデックスに登録することもできます.
キー識別子 (Key)
いわゆるプライマリーキーです. 種類中で一意になります.
作成時は自動生成の数値IDか自分でカスタム名をつけることもできます.
(なぜかGCPコンソールのエンティティ画面では ID と表記されている)
祖先 (Ancestor)
親子関係を簡単に設定できるらしいですが, そこまで複雑なデータ構造を作る気がなかったのであんまり調べてないです.
エンティティを作ってみました
ユーザーデータを管理する UserData の種類を作成しました.
GAE のサービス上で登録済みユーザーかどうかを管理するだけのものです.
ID | Activated |
---|---|
hoge@hoge.com | true |
fuga@fuga.com | false |
moge@moge.com | true |
ここでIDを自動生成にして以下のような構成も考えられましたが
ID | Activated | |
---|---|---|
52902520520 | hoge@hoge.com | true |
52055227822 | fuga@fuga.com | false |
52027859251 | moge@moge.com | true |
ユーザーはサービス内でユニークなので Email 自体をIDにしてしまった方が管理上楽だと思いました.
プロパティにすると同じ Email で登録できてしまうので重複判定のコードを書くのが面倒です.
(スキーマレスにユニーク制約なんてものはない)
Go でデータを取得してみます
import (
"context"
"google.golang.org/appengine/datastore"
)
type UserDataModel struct {
Activated bool `datastore:"Activated"`
}
func GetUserData(ctx context.Context, email string) (*UserDataModel, error) {
var userData UserDataModel
key := datastore.NewKey(ctx, "UserData", email, 0, nil)
err := datastore.Get(ctx, key, &userData)
if err != nil {
if err == datastore.ErrNoSuchEntity {
return nil, nil
} else {
return nil, err
}
} else {
return &userData, nil
}
}
※context は appengine.NewContext で取得したものを使用します.
Datastore は Json の Unmarshal と同様に構造体を定義しておくとよいです.
このとき, datastore:"<<Datastore内のプロパティ名>>"
とできるのも Json と同様です.
データ取得はキーが特定できるている場合は, datastore.Get()
, 条件を指定する場合は datastore.NewQuery().Filter()
のように書きます.
今回は Email がキーなので Get を使用します.
キーは datastore.NewKey() 関数で作成します. 第2引数が種類, 第3引数がカスタムキー, 第4引数が自動生成の数値キー, 第5引数が祖先になります. カスタムキーが空白でなければ第4引数は無視されます.
エンティティが未登録で取得できない場合は err に datastore.ErrNoSuchEntity が返ります. この場合はユーザー登録に誘導したいので, ユーザーデータは nil, err はそのまま返すようにしました. ここは呼び出し元でエラーハンドリングする手もあるので好みかと.
ちなみにクエリで取得する場合は以下のようになります.
func GetActiveUserData(ctx context.Context) (*UserDataModel, error) {
var userDataList []UserDataModel
query := datastore.NewQuery("UserData").Filter("Activated=", true)
_, err := query.GetAll(ctx, &userDataList)
if err != nil {
return nil, err
} else {
return userDataList, nil
}
}
Filter のプロパティ名に条件まで含めるところがなんかいやらしい.
query.GetAll の第一返り値は取得したエンティティのキーリストです.
また, err は appengine.MultiError です. 中身をさらに細かくエラーハンドリングすることもできます.
Filterはメソッドチェーンにより AND 条件を追加していくこともできます.
しかし, ORはできない. NOT もできない. LIKE もできない. IN もできない (とりあえず現状Goではできない)のでRDS勢がデータ設計するときにハマるかもしれません.
まあRDSが欲しいなら普通に Cloud SQL 使えばいいと思います.
入門なので今回はこんなところです.