📛📛📛最近Firebaseに入門して、WEBアプリ作っています📛📛📛
自分はDatastoreやFirestore(Datastore mode)はそれなりに経験がありますが、Firestore(Native mode)は今回初めて触っています。
コレクションやドキュメント、サブコレクションなど、Datastoreとは大分雰囲気が違うなー、と思っていたのですが、触っているうちにだんだん「あれ?やっぱり中身はDatastoreと似てない?」と思い始めました。
試しにFirestore(Native mode)を、Datastoreのクライアントライブラリで覗いたらどうなるだろう?と思いついて、試してみることにしました。
注:推奨はされていないと思います。良い子はマネしないでください😅
準備
こちらの手順でFirebaseのプロジェクトを用意します。今回はHostingとFirestoreのみ使用します。
Hostingのデプロイまで済ませます。
今回Firestoreのデータ登録は、Hostingされたページのコンソールから直接JavaScriptを入力して行います1
覗いてみる
シンプルなドキュメント
コレクション「hoge」に、ドキュメントを一件追加します。
firebase.firestore().collection("hoge")
.add({text: 'test', number: 999, array: ["aaa", "bbb"]});
Datastoreクライアントライブラリで覗いてみます。
言語はGoを使います。
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/datastore"
"google.golang.org/api/iterator"
)
func main() {
ctx := context.Background()
// Set your Google Cloud Platform project ID.
projectID := "native-ds"
// Creates a client.
client, err := datastore.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
q := datastore.NewQuery("hoge")
t := client.Run(ctx, q) for {
var entity datastore.PropertyList
key, err := t.Next(&entity)
if err == iterator.Done {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n\nkey:%s\n", key)
for _, p := range entity {
fmt.Printf(" %+v\n", p)
}
}
}
出力↓
key:/hoge,OopK2m1wJPg7fCYkRGd3
{Name:text Value:test NoIndex:true}
{Name:array Value:[aaa bbb] NoIndex:false}
{Name:number Value:999 NoIndex:true}
コレクションがKind名で、IDがKey名になっています。この辺は結構直感的ですね。
フィールドもそれぞれプロパティとして保存されています。こちらもほぼ直感的ですが、よく見ると殆ど NoIndex:true
になっていますね。実際フィルター指定しても no matching index found
というエラーが出ます。arrayフィールドのみなぜかNoIndex:falseとなっているのが不思議ですが、こちらもDatastoreのフィルターは効いていません。
@apstndb さんの↓の仮説があってそうな雰囲気。
Cloud Firestore Native mode は Cloud Firestore Datastore mode の上に構築されているかというと、 entity は上に構築されていても矛盾がなさそうだけど index はなんだか違うなという感じがありますよね
— _ (@apstndb) December 20, 2019
サブコレクション
コレクション「hoge」に、ドキュメントを一件追加します。
firebase.firestore().collection("hoge")
.doc("OopK2m1wJPg7fCYkRGd3")
.collection("fuga")
.add({text:"fugatest"});
Datastoreクライアントライブラリで覗きます。
先のGoコードのKind名を fuga
に変えるだけです。
出力↓
key:/hoge,OopK2m1wJPg7fCYkRGd3/fuga,saijWPqk7zwRV0yTKAmi
{Name:text Value:fugatest NoIndex:true}
hogeコレクションが親KeyのKind名、fukaサブコレクションが子KeyのKind名になっていて、ちょうどDatastoreのKeyのパスの階層とマッチしてますね。
ついでに孫も作ります。
firebase.firestore().collection("hoge")
.doc("OopK2m1wJPg7fCYkRGd3")
.collection("fuga")
.doc("saijWPqk7zwRV0yTKAmi")
.collection("piyo")
.add({text:"piyotest"});
Datastoreクライアントライブラリ出力
key:/hoge,OopK2m1wJPg7fCYkRGd3/fuga,saijWPqk7zwRV0yTKAmi/piyo,O03MQHQMDVV3uo1aH2Nm
{Name:text Value:piyotest NoIndex:true}
孫も同様にKeyパス階層に組み込まれました。
グローバルクエリで結果が返ってくるということは、EntitiesByKindテーブルが作られているということでしょうか。
最近サポートされた Collection group queries がDatastoreのグローバルクエリ相当な気がしています。
いきなり孫を作る
ここまで見てきて、Native modeのドキュメントとDatastore modeのエンティティは内部的にはほぼ同じものではないかと推測できます。
Datastore modeでは親Keyのエンティティが存在しなくても子Keyのエンティティや孫Keyのエンティティを保存することが出来ます。
Native modeでもCloud Firestore Data model に、
Collections and documents are created implicitly in Cloud Firestore. Simply assign data to a document within a collection. If either the collection or document does not exist, Cloud Firestore creates it.
と記されていますので、試してみます。
firebase.firestore().collection("hoge")
.doc("nodoc")
.collection("fuga")
.doc("nodoc")
.collection("piyo")
.add({text:"piyotest2"});
できました(^^)
Datastoreクライアントライブラリで覗いてみます。
piyoに対するクエリも普通に結果が返ってきます。
key:/hoge,OopK2m1wJPg7fCYkRGd3/fuga,saijWPqk7zwRV0yTKAmi/piyo,O03MQHQMDVV3uo1aH2Nm
{Name:text Value:piyotest NoIndex:true}
key:/hoge,nodoc/fuga,nodoc/piyo,QFWX2rjTszwaBUyUfeVA
{Name:text Value:piyotest2 NoIndex:true}
同様の結果が返りました。
「Collectionが存在しない場合は暗黙的に作られる」とあり何かかデータが作成されそうな風に読めますが、何の事は無い、単にドキュメントKeyの祖先Keyになっているだけな気がします。
Kindlessクエリ
いままでに保存した内容をDatastoreのKindlessクエリで覗いてみます。KindlessクエリはKindを指定しないで全てのKindにまたがって検索できる機能です。
GoプログラムのKind名を空にするだけです。
出力↓
key:/hoge,OopK2m1wJPg7fCYkRGd3
{Name:text Value:test NoIndex:true}
{Name:array Value:[aaa bbb] NoIndex:false}
{Name:number Value:999 NoIndex:true}
key:/hoge,OopK2m1wJPg7fCYkRGd3/fuga,saijWPqk7zwRV0yTKAmi
{Name:text Value:fugatest NoIndex:true}
key:/hoge,OopK2m1wJPg7fCYkRGd3/fuga,saijWPqk7zwRV0yTKAmi/piyo,O03MQHQMDVV3uo1aH2Nm
{Name:text Value:piyotest NoIndex:true}
key:/hoge,nodoc/fuga,nodoc/piyo,QFWX2rjTszwaBUyUfeVA
{Name:text Value:piyotest2 NoIndex:true}
登録したドキュメント相当のエンティティが全て出力されました。それ以外のナニカは(少なくともエンティティとしては)保存されていなさそうです。
おまけ
DatastoreでKind一覧を返す __kind__
にクエリかけてみました。
出力
key:/__kind__,fuga
key:/__kind__,hoge
key:/__kind__,piyo
ちなみに __property__
へのクエリは結果0でした。
mapフィールド
(2020.01.11追記)
mapフィールドを試してみました。
firebase.firestore().collection("maptest")
.add({text: 'test', map: [aaa: "aaa", bbb: "bbb"]});
出力
key:/maptest,QQf0nBscdc8QdrBDLKkQ
name:text, value(string):test
name:map, value(*datastore.Entity):&{Key: Properties:[{Name:bbb Value:bbb NoIndex:true} {Name:aaa Value:aaa NoIndex:true}]}
```
Embeded Entity(Keyなし)の形式で保存されました。
# まとめ
旧Cloud DatastoreやFirestore Datastore mode経験者は、Firestore native modeを初めて見てかなり異質なものをイメージしてしまうかもしれません(自分がそうでした)。
ただ蓋を開けてみると中身の構造はかなり共通しているので、勘所を抑えればすんなり理解できるのではないかと思います。
Native modeはリアルタイム更新リスナーや、セキュリティルールなどDatastoreにない革新的な機能も持っているので、これから積極的に使っていきたいと思います。
-
こんなに手軽にデータ登録できてしまうのは怖いですね^^; やはり適切なセキュリティルール設定は必須ですね。 ↩