Google Cloud DatastoreをGoogle App Engineから利用する
前提知識はProgateでのPythonレッスン修了レベルです。Pythonで説明しますが、他の言語でも読み替えられると思います。
Google Cloud Datastoreとは
Google Cloud Platformの誇る最先端のデータベースです。
Google Cloud Datastore の概要 | Google Cloud
これまでデータベースといえばSQL(Progateでも習えますね)でしたが、今はNoSQLといわれる、つまりSQLでないデータベースがあります。SQLと違ってAPI的に簡単に扱えるのがメリットです。
ちなみにGoogle Cloud PlatformにはNoSQL・SQLともにいろいろな種類があります。
公式ページに比較があります。想定される用途なども書いてあるので、これを見て選べばいいでしょう。
ストレージ オプションの選択 | Google Cloud
ユーザーデータなどのシンプルで低容量のデータベースにはGoogle Cloud Datastoreがぴったりでしょう。
構造
Key-Value型というらしいです。データベース全体を辞書型と思えばわかりやすいですね。
Propertyはいくつでも作れます。また、ひとつのエンティティの中で、ひとつのPropertyに対して複数のValueを持てます。しかもそれらは同じ型である必要はありません。
Propertyに設定できる型
文字列、数値、他のEntityへのリンクをはじめとしていろいろな型を利用できます。こちらの公式ドキュメントにリストがあります。
Entity Property Reference | App Engine standard environment for Python | Google Cloud
Keyだけは文字列(string)か整数(integer)である必要があります。
また、当然ながら「key」という名前のPropertyをつくることはできません。
また__
(アンダースコア2つ)ではじまるProperty名はシステムに予約されているので自分でつけることはできません。
GAEからGoogle Cloud Datastoreを利用する方法
Google App EngineではデフォルトのデータベースとしてGoogle Cloud Datastoreを利用できるようになっています。
同じGCPアカウント間でのやりとりなので認証が不要です。ライブラリに定義されているメソッドを実行するだけでOK。これはかなり楽ですね。
import
ライブラリ(パッケージ)は前回の手順でセットアップしていればあとはimportするだけです。
from google.appengine.ext import ndb
なおndb
の他にdb
というライブラリもありますが、ndb
の方が新しいライブラリになります。いまからわざわざdb
を使い始めるメリットはありません。
▷ndb
とdb
の対照表はこちらをご覧ください。
データベースへのアクセス
公式リファレンスはこちらです。
Creating, Retrieving, Updating, and Deleting Entities | App Engine standard environment for Python | Google Cloud
新しいデータを格納する
以下のようなステップになります。
- データベースを表すクラスを作成する(クラス名=Kindとなる)
- 作成したクラスのインスタンスを作成する(インスタンス=Entityを示す)
- インスタンス変数(=Property)に値(Value)を入れる
- 作成したインスタンスをGoogle Cloud Datastoreへ送信する
まずデータベースそのものをあらわすクラスを定義しましょう。クラス=kindとなります。
ndb
ライブラリに定義されているModel
クラスを継承します。今回は「People」というkindを作ってみましょう。
class People(ndb.Model):
そうしたら、その際にPropertyも同時に定義します。今回は名前と生年月日をPropertyとして保存できるようにしてみましょう。
Propertyにはそれぞれ型を指定します。今回は、
- name:文字列(str)型
- birthday:日付(datetime.date)型
というようにします。型の指定にはライブラリに定義されたメソッドを使います。指定したい型ごとにメソッドが決まっているので、こちらのリファレンスから選んで使いましょう。
class People(ndb.Model): name = ndb.StringProperty() birthday = ndb.DateProperty()
これで、こんなイメージのクラスができあがりました。
そうしたらEntityをつくりましょう。インスタンスがEntityそのものとなります。
taro = People(name='太郎', birthday=datetime.date(1956, 7, 3)) yoshiko = People(name='よし子', birthday=datetime.date(1923, 1, 1))
あとは作成したインスタンスをGoogle Cloud Datastoreに送信すれば完了です。
これはput()
メソッドひとつで完了。ちょう楽。
taro.put() yoshiko.put()
データベースからデータを取得する(Keyがわかっている場合)
Keyがわかっていればとても簡単にデータベースにアクセスできます。
なお、keyが分かっている場合というのはそんなにないはずです。上でEntityを作成したときにKeyの話がでてきませんでしたね。Entity作成時にKeyを特に指定しなかった場合、Keyは自動で生成されます。Keyを指定してEntityを作成することもできるのですが、よほど自信があるのでなければおすすめしません。なぜならKeyは完全にユニークである必要があり、また変更できないからです。
Keyがわかっていればデータの取得はとても簡単です。
michael = キー.get()
これで、変数michael
はEntityそのものを表すインスタンスになりました。
PropertyのValueを取得してみましょう。
print( michael.name ) #出力結果:マイケル
michael
の名前を変更してデータベースを更新してみましょう。
michael.name = 'マイケール'
michael.put()
データベースからデータを取得する(Keyがわからない場合)
Keyが分かっていない場合は3段階の手順が必要です。
- クエリを作成する
- フィルタでクエリの範囲を絞り込む
- 作成したクエリを.get()もしくは.run()する
まずはクエリを作成します。クエリとは「こんな性質のEntity」というイメージです。作成したクエリを.get()
or.run()
すれば、該当するEntityがGoogle Cloud Datastoreから返されます。
query_for_taro = People.query(People.name == '太郎')
これでKind
がPeople
のEntityのうち、name
が太郎
である集団のイメージができました。
今回は太郎
は一人しかいない自信があります。一発で目的のEntityを掴めるという自信があるなら、次のフィルタは特に必要ありません。
もし太郎
が複数存在し、今回はbirthday
が1956-7-3
の太郎
だけが欲しいような場合は、さらにフィルタをします。
query_for_taro = query_for_taro.filter(People.birthday == datetime.date(1956, 7, 3))
ちなみに比較演算子は==
以外も利用できますし、AND
やOR
のような表現も利用できます。詳しくはこちらのドキュメントをご覧ください。
クエリを作成できたら、それをGoogle Cloud Datastoreに送信して該当するEntityを受け取ります。
このときに.get()
メソッドか.run()
メソッドのどちらかを利用します。
.get()
メソッドは該当するEntityのうち、最初のひとつのみを返します。
taro = query_for_taro.get()
つまり、返されるのはEntityそのものを表すインスタンスです。(対象のEntityがない場合はNone
(PythonではnullではなくNoneといいます)が返ります)
.run()
メソッドは該当するEntityすべてをリスト(Pythonでは配列ではなくリストと呼びます)で返します。
list_of_taros = query_for_taro.run()
つまりこういうことです。
query_for_taro.get() == query_for_taro.run()[0] #この式は常にTrue
.run()
メソッドは引数をつけなければ該当するEntityをすべて返します。返す数の上限を設定するには引数を利用します。
list_of_taros = query_for_taro.run(limit=10) #list_of_tarosには最大で10個しか入らない
ちなみに、.run()
とよく似た.fetch()
というメソッドもあります。
違いはキャッシングのみです(.fetch()
はクエリに結果をキャッシュさせます)。Googleでは特に理由のない場合は.run()
を推奨しています。
EntityのPropertyを更新する
Propertyを更新するには、取得・または作成したEntityオブジェクトのインスタンス変数を変更して.put()
すればOKです。
class People(ndb.Model): name = ndb.StringProperty() #taroはKindがPeopleでnameが'太郎'のEntity taro = People.query(People.name == '太郎').get() #taroのnameを'三郎'に変更 taro.name = '三郎' #変更を反映 taro.put()
以下の方法も利用できます。変数でPropertyを指定したいような場合に便利です。
#taroはKindがPeopleで'name'が'太郎'のEntity taro = People.query(People._properties['name'] == '太郎').get()
NDB Queries | App Engine standard environment for Python | Google Cloud
データベースからEntityを削除する(Keyが必要)
データベースからEntityを削除するにはKeyが必要です。Keyの取得方法はあとで説明します。
ちなみにEntity自体は削除せずいずれかのValueのみ削除するならデータベースからデータを取得するの応用でできますね。インスタンスの特定のPropertyにNone
を代入して.put()
すればいいのです。
Entity自体を削除するには.delete()
メソッドを使います。
taro.キー.delete()
ちなみに.delete()
メソッドの返り値は常にNone
です。
その他のオペレーション
ここから下は必要な時にリファレンスがわりにみてもらえればいいです。ここまでの内容でもう十分に利用できます。
Keyの取得
実はkeyとはKey
クラスのインスタンスです。
Key
クラスは最低2つのインスタンス変数を持ちます。
Key(kind, id)
kind
にはそのEntityが所属するKindが入ります。ここまでの文脈では「Key」という言葉を「Entity固有の文字列か整数」と説明してましたが、これはid
のことです。
.get()
などでEntityを示すインスタンス(Model
クラスのインスタンス)を取得できていれば、インスタンス変数.key
でkeyを取得できます。
キー = インスタンス.key
また、クラス名(kind
)やid
も取得できます。
クラス = インスタンス.key.kind() id = インスタンス.key.id()
Keyクラスについて詳しくはこちらのリファレンスをご覧ください。
※db
ライブラリのリファレンスなので注意してください。このページの最後にndb
/db
ライブラリ対照表へのリンクを張っているので、必ずそこでndb
ライブラリに変換して利用してください。
他のEntityへのリンク
Kindが異なる場合を含め、他のEntityへのリンクをPropertyに保有できます。Model
クラスのインスタンス(kindを示すインスタンス)を定義する際に.KeyProperty(kind)
メソッドでインスタンス変数を定義しておきましょう。
class People(ndb.Model): film_key = ndb.KeyProperty(Films)
このときfilm_key
はKindがFilms
のEntityのKeyです。
例えば以下のコードではEntitytaro
からEntitydeath_in_Venice
の特定のPropertyを取得することができています。
例:太郎の好きな映画の監督の名前を表示したい
class Films(ndb.Model): director_name = ndb.StringProperty() class People(ndb.Model): name = ndb.StringProperty() favorite_film_key = ndb.KeyProperty(Films) death_in_Venice = Films(director_name='Visconti') taro = People(name='太郎', favorite_film_key=death_in_Venice.key) death_in_Venice.put() taro.put() #↑このような状態になっているとして taros_favorite_film = taro.favorite_film_key.get() #taro.favorite_film_key == death_in_Venice.key なので、taro.favorite_film_key.get() == death_in_Venice print( taros_favorite_film.director_name ) #出力結果:Visconti
実は、上のfavorite_film_key
にあたる専用の変数が最初から用意されています。Model
クラスにはparent
という引数が用意されているので、以下のようにいきなり指定してしまえば大丈夫です。
class Films(ndb.Model): director_name = ndb.StringProperty() class People(ndb.Model): name = ndb.StringProperty() death_in_Venice = Films(director_name='Visconti') taro = People(name='太郎', parent=death_in_Venice.key)
death_in_Venice
をparent
にもつEntityを選ぶには.query()
メソッドにancestor
という引数を入れます。
query = People.query(ancestor=key_of_death_in_Venice)
NDB Queries | App Engine standard environment for Python | Google Cloud
KindのEntityをすべて取得する
.query()
メソッドに引数をいれなければKindに所属するすべてのEntityが含まれます。
all_people = People.query().fetch()
#run()は利用できないようです
Entityの順番の指定
クエリは条件にあてはまるすべてのEntityを含みます。なのでその中で順番を指定したいこともありますね。
順番の指定は.order()
メソッドを使います。Propertyごとに昇順(asc)・降順(desc)で指定できます。
例えばkindがPeople
のEntityを全て取得したいが、その順番は
- まず
name
について昇順で name
が同じ場合はbirthday
について降順で並べる
query = People.query().order(People.name, -People.birthday)
降順の場合は-
(マイナス)をつけるわけですね。
特定のPropertyのみ取得する
これまで扱ってきたクエリやフィルタでは、「あるPropertyについて特定のvalueをもつEntity」という絞り込み方をしてきました。
つまり、例えばPeopleのうちname
とbirthday
についてのみ興味がある場合でも、それ以外のfavorite_film
とかage
とかのPropertyも含んだ結果が返されます。
もちろんだからといって困ることはないですね。しかし不要なPropertyは最初から除外してもらうことでパフォーマンスが向上(値が返されるまでの時間の短縮、Google Cloud Datastoreの使用料の節約)します。
これはProjection Queriesという機能で実現できます。
.run()
/.fetch()
メソッドにはprojection
という引数があるので、ここに指定したいPropertyをいれればOKです。
list = People.query().run(projection=[People.name, People.birthday])
projection
はリストです。ひとつでもprojection=[People.name]
のようにします。
ちなみに以下のような書き方も可能です。
list = People.query().run(projection=["name", "birthday"])
Projection Queriesについて詳しくはこちらのリファレンスをご覧ください。
リストPropertyに特定の値が含まれるEntityをfetchする
comingsoon
NDB Queries | App Engine standard environment for Python | Google Cloud
Expandoクラス
これまでKindを示すクラスはModel
クラスを継承して作成していました。実はもうひとつExpando
というクラスがあります。
Expando
クラスはModel
クラスの子クラスです。
Model
クラスを継承して作成したクラスのインスタンス変数はすべて固定Property(Fixed Property)でした。つまり、作成したPropertyに入るvalueの型は固定で、またクラス作成時に定義しなかったインスタンス変数は利用できません。
class People(ndb.Model): name = ndb.StringProperty() #nameにはstring型しか入れられない taro = People(name=['Tanaka', 'Taro']) #エラー:nameにはlist型は入れられない。 yoshiko = People(age=23) #エラー:インスタンス変数ageは未定義
Expando
クラスを利用すると、上の2つとも可能になります。つまりクラス作成時にインスタンス変数を定義する必要がなく、後から自由な型でインスタンス変数を作成できます。
class People(ndb.Expando): pass taro = People(name=['Tanaka', 'Taro']) #list型の変数nameを作成 yoshiko = People(age=23) #integer型の変数ageを作成 yoshiko.favorite_food = 'ラーメン' #yoshikoの新しいPropertyとしてfavorite_foodを追加 taro.favorite_food = ['カレー', 'パッタイ'] #同じ名前のPropertyでも異なる型を利用可能
このような動的なPropertyをDynamic Propertyといいます。Dynamic PropertyはExpando
クラスのインスタンスでしか利用できません。
それぞれのDynamic Propertyはvalueが代入されたタイミングで新たに生成され、それまでは存在しません。
Dynamic Propertyは削除できます。
del taro.favorite_food
Expando
クラスでもModel
クラス同様にFixed Propertyも定義できます。
class People(ndb.Expando): age = ndb.IntegerProperty() taro = People(age=67, name='Taro') #Fixed Propertyであるageにvalueを代入しつつDynamic Propertyとしてnameを作成 yoshiko = People(age='23歳') #エラー:ageはFixed Propertyとしてinteger型で定義しているので、string型は入れられない del taro.age #エラー:Fixed Propertyは削除できない
Expando
クラスについて詳しくはこちらのリファレンスをご覧ください。
※db
ライブラリのリファレンスなので注意してください。このページの最後にndb
/db
ライブラリ対照表へのリンクを張っているので、必ずそこでndb
ライブラリに変換して利用してください。
その他・詳細
クエリの正体はQuery
クラスのインスタンスです。
クエリに対して利用可能なメソッド(.get()
など)の詳細はこちらのリファレンスをご覧ください。
※db
ライブラリのリファレンスなので注意してください。このページの最後にndb
/db
ライブラリ対照表へのリンクを張っているので、必ずそこでndb
ライブラリに変換して利用してください。
デプロイ
アプリケーションのデプロイの際に、index.yaml
ファイルを一緒にアップロードする必要があります。これがない場合、存在しないKind
に対して.query()
をかけるとエラーになります。
なお、Admin Server (dev_appserver.py app.yaml
で起動するローカルの開発環境) ではこの作業は必要なく、index.yaml
はそれぞれのKind
に対しての初回の.query()
時に自動で生成・更新されます。
このAdmin Serverで自動生成されたindex.yaml
をそのままアップロードすることもできますし、手動でindex.yaml
を編集することもできます。
いずれにしろ作成したindex.yaml
をデプロイ(実際に稼働するアプリケーションにアップロード)するには、ローカルのアプリケーションフォルダの直下(dev_appserver.py app.yaml
を行うのと同じ場所)で以下のコマンドを入力します。
gcloud datastore create-indexes index.yaml
なおDatastore Indexesについてはこちらが公式リファレンスになります。手動でindex.yaml
を設定する場合はこちらを参照してください。
インデックス設定 | Cloud Datastore ドキュメント | Google Cloud
gcloud ツールを使用したアプリケーション テストとインデックス管理 | Cloud Datastore ドキュメント | Google Cloud
補足:リファレンスについて
2018/6/1現在、Google Cloud Datastoreの公式ガイドはほぼ英語です(概要のページなど一部は日本語もあり)。
Cloud Datastoreのガイドやドキュメントは正直みにくいです。ライブラリはndb
が新しい方でdb
は古い方です。それぞれドキュメントも別になっているのでndb
をの方をみようとするのですが、実はdb
から変更のない部分については書いていなかったりします。ややこしい。
なので公式ドキュメントをみる際はndb
のページを中心に、書いていないことがあればdb
を参照しつつ対照表でndb
に変換してください。めんどいですね。
左側のグローバルメニューに全てのページが並んでいるので、根気強く探してください。基本的には下に貼ったページより下の階層に全部あるはずです。
リンクはPython用ですが、ページ上部に他の言語への切り替えボタンがあるはずです。
- Cloud Datastore概要:Cloud Datastore Overview
ndb
ドキュメント:The Python NDB Client Library Overviewdb
ドキュメント:The Python DB Client Library for Cloud Datastoreindex.yaml
についてインデックス設定 | Cloud Datastore ドキュメント | Google Cloud- Google Cloud SDK (
gcloud
)によるindex.yaml
のアップロード:gcloud ツールを使用したアプリケーション テストとインデックス管理 | Cloud Datastore ドキュメント | Google Cloud