Help us understand the problem. What is going on with this article?

Firestore - Native modeにDatastoreのクライアントライブラリで覗いてみる

📛📛📛最近Firebaseに入門して、WEBアプリ作っています📛📛📛

Firebaseでチャットアプリを作る日記

自分はDatastoreやFirestore(Datastore mode)はそれなりに経験がありますが、Firestore(Native mode)は今回初めて触っています。

コレクションやドキュメント、サブコレクションなど、Datastoreとは大分雰囲気が違うなー、と思っていたのですが、触っているうちにだんだん「あれ?やっぱり中身はDatastoreと似てない?」と思い始めました。

試しにFirestore(Native mode)を、DatastoreのクライアントライブラリでNative modeで覗いたらどうなるだろう?と思いついて、試してみることにしました。
注:推奨はされていないと思います。良い子はマネしないでください😅

準備

こちらの手順でFirebaseのプロジェクトを用意します。今回はHostingとFirestoreのみ使用します。
Hostingのデプロイまで済ませます。

今回Firestoreのデータ登録は、Hostingされたページのコンソールから直接JavaScriptを入力して行います1

覗いてみる

シンプルなドキュメント

コレクション「hoge」に、ドキュメントを一件追加します。

firebase.firestore().collection("hoge")
  .add({text: 'test', number: 999, array: ["aaa", "bbb"]});

保存されました。
スクリーンショット 2019-12-21 午後2.53.50.png

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 さんの↓の仮説があってそうな雰囲気。

サブコレクション

コレクション「hoge」に、ドキュメントを一件追加します。

firebase.firestore().collection("hoge")
  .doc("OopK2m1wJPg7fCYkRGd3")
  .collection("fuga")
  .add({text:"fugatest"});

保存されました。
スクリーンショット 2019-12-21 午後3.19.58.png

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"});

できました(^^)

スクリーンショット 2019-12-21 午後4.30.48.png
スクリーンショット 2019-12-21 午後4.31.06.png

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 groupクエリも実行してみました。
スクリーンショット 2019-12-21 午後4.44.08.png

同様の結果が返りました。

「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にない革新的な機能も持っているので、これから積極的に使っていきたいと思います。


  1. こんなに手軽にデータ登録できてしまうのは怖いですね^^; やはり適切なセキュリティルール設定は必須ですね。 

hogedigo
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした