Google Datastore NDB の使い方


*Google Datastore NDB とは

App Engine のアプリから Datastore に接続するためのクライアントライブラリです。
エンティティと呼ばれるデータオブジェクトごとにキーとプロパティを持っていて、親と子の関係性を持たせることもできます。









今回は公式ドキュメントを元に、NDBの基本的な使い方を実際に試してまとめておきました。ローカル環境でNDBを試してみたい場合は、以前に書いた下記の記事を参考にしてください。


*参考



*エンティティの作成

データオブジェクトのクラスを作成し、put()でエンティティの作成を実行することができます。
put()を実行すると一意のキーが返却されるので、そのキーを元にしてget()を実行すると、作成したエンティティを参照することができます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


# エンティティの作成
account = Account(username='Tom', userid=123, email='hoge@aaa.com')
key = account.put()

# エンティティの取得
print(key.get())

<実行結果>
Account(key=Key('Account', 5066549580791808), email=u'hoge@aaa.com', userid=123, username=u'Tom')


*エンティティの取得

キーを指定する方法と、クエリを実行して条件に一致したエンティティを取得する方法があります。
キーを指定する場合はget()、クエリを実行する場合はfetch()を使います。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

# keyを指定
key = ndb.Key(Account, 5066549580791808)
print(key.get())

# クエリで条件を指定
query = Account.query(Account.username=='Tom')
print(query.fetch())

<実行結果>
# keyを指定
Account(key=Key('Account', 5066549580791808), email=u'hoge@aaa.com', userid=123, username=u'Tom')

# クエリで条件を指定
Account(key=Key('Account', 5066549580791808), email=u'hoge@aaa.com', userid=123, username=u'Tom')


*エンティティの更新

対象のエンティティを取得し、プロパティに変更したい値をセットしてput()を実行します。今回は email の値を更新しました。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

# 変更前のエンティティを取得
account = ndb.Key(Account, 5066549580791808).get()
print(account)

# 更新
account.email = 'piyopiyo@aaa.com'
account.put()
print(ndb.Key(Account, 5066549580791808).get())

<実行結果>
# 変更前
Account(key=Key('Account', 5066549580791808), email=u'hoge@aaa.com', userid=123, username=u'Tom')

# 変更後
Account(key=Key('Account', 5066549580791808), email=u'piyopiyo@aaa.com', userid=123, username=u'Tom')


*エンティティの削除

対象のエンティティを取得し、delete()を実行します。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

# 削除するエンティティを取得
account = ndb.Key(Account, 5066549580791808).get()
print(account)

# 削除
account.key.delete()
print(ndb.Key(Account, 5066549580791808).get())

<実行結果>
# 削除前
Account(key=Key('Account', 5066549580791808), email=u'piyopiyo@aaa.com', userid=123, username=u'Tom')

# 削除後
None


*クエリの実行

事前に複数のエンティティを作成しておきます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


account1 = Account(username='Tom', userid=123, email='hoge@aaa.com').put()
account2 = Account(username='Bob', userid=222, email='Bob@aaa.com').put()
account3 = Account(username='Hanako', userid=333, email='Hanako@aaa.com').put()

query()でクエリを作成し、fetch()でクエリを実行すると、条件に一致したエンティティを取得することができます。
クエリはfilter()order()を使って絞り込みや並び替えをすることもできます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


# 条件を指定
account1 =  Account.query(Account.userid ==  222).fetch()
print(account1)

# 複数条件を指定
account2  =  Account.query(Account.userid  >=  200,  Account.userid  <  400).fetch()
print(account2)

# 複数条件を指定
account3  =  Account.query(Account.userid.IN([123, 333])).fetch()
print(account3)

# 絞り込み
account4  =  Account.query().filter(Account.userid  >=  200).fetch()
print(account4)

# 並び替え
account5  =  Account.query().order(Account.userid).fetch()
print(account5)

<実行結果>
# account1
[Account(key=Key('Account', 4785074604081152), email=u'bob@aaa.com', userid=222, username=u'Bob')]

# account
[Account(key=Key('Account', 4785074604081152), email=u'bob@aaa.com', userid=222, username=u'Bob'), Account(key=Key('Account', 5910974510923776), email=u'hanako@aaa.com', userid=333, username=u'Hanako')]

# account3
[Account(key=Key('Account', 6192449487634432), email=u'hoge@aaa.com', userid=123, username=u'Tom'), Account(key=Key('Account', 5910974510923776), email=u'hanako@aaa.com', userid=333, username=u'Hanako')]

# account4
[Account(key=Key('Account', 4785074604081152), email=u'bob@aaa.com', userid=222, username=u'Bob'), Account(key=Key('Account', 5910974510923776), email=u'hanako@aaa.com', userid=333, username=u'Hanako')]

# account5
[Account(key=Key('Account', 6192449487634432), email=u'hoge@aaa.com', userid=123, username=u'Tom'), Account(key=Key('Account', 4785074604081152), email=u'bob@aaa.com', userid=222, username=u'Bob'), Account(key=Key('Account', 5910974510923776), email=u'hanako@aaa.com', userid=333, username=u'Hanako')]


*子エンティティの作成

子エンティティを作成する際に、parent=で親エンティティのキーを指定すると、親キーと紐づいたエンティティを作成することができます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


class Address(ndb.Model):
    address = ndb.StringProperty()


# 親キーを取得
account_key = ndb.Key(Account, 6192449487634432)

# 子エンティティを作成
address = Address(address='Tokyo', parent=account_key).put()
print(address)

<実行結果>
Key('Account', 6192449487634432, 'Address', 5348024557502464)

  • 子エンティティを取得
account_key = ndb.Key(Account, 6192449487634432)
key = ndb.Key(Address, 5348024557502464, parent=account_key)
print(key.get())

<実行結果>
Address(key=Key('Account', 6192449487634432, 'Address', 5348024557502464), address=u'Tokyo')


*クエリの非同期実行

非同期でクエリを実行する場合は、同期実行で使っていたメソッドの代わりに_async()を付けたメソッドを使います。
今回はfetch_async()を使って試しましたが、その他にもget_async()put_async()があります。
非同期で実行すると、まずすぐにFutureオブジェクトが返却されます。実行後の値を確認したい場合は、get_result()で結果を取得することができます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


# クエリを非同期実行
account = Account.query(Account.userid == 123).fetch_async()
print(account)

<実行結果>
<Future 1075b9890 created by fetch_async(query.py:1239) for tasklet _run_to_list(query.py:987); pending>

  • 実行後の値を確認
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


# クエリを非同期実行
account = Account.query(Account.userid == 123).fetch_async()

# 実行後の値を取得
result = account.get_result()
print(result)

<実行結果>
[Account(key=Key('Account', 6192449487634432), email=u'hoge@aaa.com', userid=123, username=u'Tom')]

*Tasklet を使った非同期実行

Tasklet を使ってメソッドや関数を非同期で実行させることができます。主にループで複数の処理を非同期実行させたい場合に使います。
Tasklet を使う場合は、メソッドに@ndb.taskletのアノテーションを付与して非同期にしたいクエリにyieldを付けます。そしてraise ndb.Return(...)で返却します。yield を付けることで、他の操作をブロックすることができます。
from google.appengine.ext import ndb


class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()


class Address(ndb.Model):
    address = ndb.StringProperty()


@ndb.tasklet
def callback(account):
    accounts = yield account.key. get_async()
    raise ndb.Return(accounts)

accounts = Account.query().order(Account.userid)
result = accounts.map(callback)
print(result)

<実行結果>
[Account(key=Key('Account', 6192449487634432), email=u'hoge@aaa.com', userid=123, username=u'Tom'), Account(key=Key('Account', 4785074604081152), email=u'bob@aaa.com', userid=222, username=u'Bob'), Account(key=Key('Account', 5910974510923776), email=u'hanako@aaa.com', userid=333, username=u'Hanako')]


*所感

使い方を覚える必要がありますが、視覚的に何をしてるかがわかりやすい書き方だと思いました。また、非同期実行も簡単にできる点も魅力的でした。
App Engine を使う場合は Datastore を使って効率的なアプリケーション開発をしていきたいと思います。