GAE
golang
datastore
gcp

GCP Datastore に GAE/Go で入門

More than 1 year has passed since last update.

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 Email 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 使えばいいと思います.

入門なので今回はこんなところです.