Cloud Firestoreのデータ構造とデータ取得の基本

Firestoreのデータ構造
FirebaseのDBには2つあり、一つはRealtimeDatabase、もう一つはその進化版的な感じのCloud Firestoreです。
Firestoreのデータ構造のイメージ図。

collectionの中に、documentというjsonっぽい形式のデータ(RealtimeDatabaseはjsonだが、Firestoreはcollectionが上位概念としてあるので正確にはjsonではない)が格納されている感じです。
別の書き方をするとこんな感じ。collectionの中にdocumentがあります。従来のRDBでいうと、collectionがスキーマ、documentがテーブル、data(フィールド)がレコード、って感じ。

で、RealtimeDatabaseの頃から進化したFirestoreの素晴らしい点の一つとして、サブコレクションという概念があります。ようは、collectionをネスト(documentの中にcollectionを持つことができる)することができるのです。(下図)

さて。全体的なイメージがつかめたところで、もうちょっと具体的にデータ構造をみせると、

こういう感じです。黄色のCitiesがcollectionで、city_123とcity_456がdocument、その中にあるname、population、stateがdataです。
右に書いてあるcitiesRefというのはデータ(document)への参照(パス)のことです。
?ってなった人、すみません。これから説明します。
まずは、実際にFirestoreにデータがどのように格納されているかはコンソール(下図)から見ることができ、これを見てもらえればわかりやすいかと思います。
こんな感じ。コレクションの中にドキュメントがあり、ドキュメントにはフィールド(データ)とサブコレクションを入れることができる、という感じになっています。
サブコレクションのおかげで、RealtimeDatabaseの頃では難しかった複雑なデータ構造にも耐えられるようになったのです!ぱちぱちぱち…
なので、データへの参照というのは以下のように、必ずcollectionとdocumentが交互にくることになります。ルートのcollectionから、該当documentまでのパス、ってことですね。
従来のRDBとは全く異なる構造なので、最初はいまいちピンとこないかもしれませんが、すぐ慣れるので大丈夫です。
ちなみに、これからFirebaseでスキーマレスDBを使うならFirestore一択。なぜなら、https://novelel.com/index.php/2019/05/20/4946/で述べたように、一言で言ってしまえば「より新しいから」です。
女の子ならカワイイが正義ですが、IT業界では新しさこそ正義!(笑)
Firestoreからデータを取得する流れ
①データへの参照を作成
データへの参照(Referenceを略してRefって呼んでる人が多い)、というのは前述した通り、Firestore内にある当該documentまでのパスのこと。これね。
②参照からスナップショットを取得
スナップショット、というのは、データの塊です。collection単体の場合、document単体の場合、documentが持つ配下のサブコレクションまですべて含む場合など、いろいろな形態があります。
具体的な取得の方法は、データの参照に.get()メソッドもしくは.onSnapshot()メソッドをくっつけるだけ。
たとえば。
<ドキュメント単体を取得する場合>
${documentRef}.get() => DocumentSnapshot
<コレクション単体を取得する場合>
${collectionRef}.get() => QuerySnapshot
コレクションの中には複数のドキュメント(DocumentSnapshot)が含まれています。

<ドキュメントのサブコレクション一覧を取得する場合>
${documentRef}.getCollections() => CollectionReference
※.getCollections()メソッドはモバイル、ウェブクライアントのライブラリでは使えないので注意。Cloud functionでは使えます。
③スナップショットからデータを取り出す
DocumentSnapshotからフィールド(データ)を取り出す時は.get()メソッドを使います。こんな感じ。
${DocumentSnapshot}.get(<field名>)
QuerySnapshotは、collectionのスナップショットです。つまり、QuerySnapshotのdocsプロパティの中に、DocumentSnapshotの配列が格納されています。
なので、QuerySnapshotからドキュメントを取り出す時はこんな感じで書きます。ここからさらに上述した.get(<field名>)メソッドでデータを取り出すイメージ。
record = querySnapshot[0].data()
ちなみに、今回ご紹介しているデータの取得は一回読み取りです。リアルタイムアップデートのリッスンについては別の記事でご紹介します。