【公式ドキュメントを噛み砕く】Firestore編（ベータ版）

# はじめに
案件で触れるから、学習がてらアウトプットしてみるよ。（本は無いし、公式ドキュメントは読みにくいし・・・）
個人の理解メモなので、正しい情報は公式ドキュメント見てくださいね。
（そして誤りあれば指摘してもらえると嬉しいです）

# 本文
## 概要
「[ここ](https://firebase.google.com/docs/firestore/?hl=ja)」の話

公式文言（抜粋）
>
- 柔軟でスケーラブルな NoSQL クラウド データベース
- リアルタイム リスナーを介してクライアント アプリ間でデータを同期し、モバイルとウェブのオフライン サポートを提供します。
- Cloud Firestore は現在、ベータ版リリースです。 

「リアルタイム リスナーを介してクライアント アプリ間でデータを同期し、モバイルとウェブのオフライン サポートを提供します。」ってのが良く分からんけど、とりあえず読み進めていく。

### 主な機能

- 柔軟性
    - 「柔軟な階層型データ構造に対応」ってのが言いたいだけっぽい。「KVS」って理解で大枠は間違ってないらしい。
- 高機能なクエリ処理
    - 「複数の連鎖フィルタ」「フィルタ処理」「並べ替え処理」あたりがポイントになる機能っぽい。
    - 「デフォルトではクエリにはインデックスが付いている」らしい。
- リアルタイム アップデート
    - 「データ同期を使用して、すべての接続端末のデータを更新します。」・・・すごいけど、どういうことだってばよ？
- オフライン サポート
    - 「アプリでアクティブに使用されるデータをキャッシュします。」・・・それで、オフラインでもCRUD操作ができる、と。
    - でも、それはどうやって実現するんだ？クライアント側になんか書かないとダメなんじゃないか？
- 拡張性のある設計
    - 背後にGCPが居るから、「過酷なワークロードに対応できる」と言いたいらしい。

### 仕組み

> REST API と RPC API の他に、ネイティブ Node.js、Java、Python、および Go SDK で使用可能です。

スマホアプリないしは、サーバサイド系からのアクセスができるってことか。
なんなら、APIサポートもあるし、静的HTML（javascript）からでもアクセスできるのか。

> Cloud Firestore の NoSQL データモデルに従い、値に対応するフィールドを含むドキュメントにデータを格納します。

基本的には、「KVS」って理解で良いんだな。

> ドキュメントでは、単純な文字列や数値から複雑なネスト オブジェクトまで、さまざまなデータタイプがサポートされています。
> これらのドキュメントはコレクションに格納されます。
> また、ドキュメント内にサブコレクションを作成し、データベースの拡大に合わせて拡張できる階層型データ構造を構築できます。

構造的には
`コレクション > ドキュメント > データ` って階層構造になるらしい。
その上で、
`コレクション > ドキュメント > サブコレクション > ドキュメント > データ`　もできるってことか。

**【疑問】**：サブコレクションはいくつでも挟めるのかしら？　→　基本的にはいくつでも挟めるらしい（限界は有る）


> 更新が発生するたびにデータベース全体を取得することなく、アプリ内のデータを最新の状態に保つには、リアルタイム リスナーを追加します。
> リアルタイム リスナーをアプリに追加すると、クライアント アプリがリッスンしているデータが変更されるたびに、アプリからデータ スナップショットにより通知され、新しい変更だけが取得されます。

へー。更新分だけ更新されるってのは、スマホアプリ的にもすごく嬉しそう。

> Cloud Firestore のデータへのアクセスを保護するには、Android、iOS、および JavaScript では Firebase Authentication と Cloud Firestore セキュリティ ルールを使用し、サーバー側言語では Identity and Access Management（IAM）を使用します。

アクセス保護はしましょうね。と。

### 実装パス
特筆すべき点も無いので、割愛。

## スタートガイド
「[ここ](https://firebase.google.com/docs/firestore/quickstart?hl=ja)」の話

操作ガイドな感じなので、ここは公式を読んだほうが早い。

## データの追加と管理
### データモデル
「[ここ](https://firebase.google.com/docs/firestore/data-model?hl=ja)」の話

再掲になるけど、構造的には
`コレクション > ドキュメント > データ（キー＆バリュー）` って階層構造になるらしい。
その上で、
`コレクション > ドキュメント > サブコレクション > ドキュメント > データ（キー＆バリュー）`　もできるらしい。

RDBMSと無理やり対比させるなら、以下なイメージ。

|RDBMS|Firestore|
|---|---|
|コレクション|テーブル（ちょっと違うけど）|
|ドキュメント|レコード|
|データ|データ（レコード内の1カラム？）|
|サブコレクション|（該当するものが無い・・・）|


**ドキュメント**

要はJSONらしい。（公式にもそう書いてある）

- 各ドキュメントは名前で識別される。（ドキュメント毎に名前がつくらしい。）
- 値にマッピングされるフィールドを含む軽量のレコード
- ドキュメント内で階層構造にしたデータは、通常のデータと分けて、「マップ」と呼ぶ。
- ストレージの単位である（どういうこと？）

```:通常のデータ（呼び方は定義されてなかった。単に「データ」？）
first : "Ada"
last : "Lovelace"
born : 1815
```

```:マップ
name :
    first : "Ada"
    last : "Lovelace"
born : 1815
```

**【疑問】**：ドキュメントの名前は重複不可能ってことか？　→　重複不可らしい。要はIDだ。

**コレクション**

- ドキュメントはコレクションの中にある。
- 同じコレクションでも、異なるデータ構造を持ったドキュメントを保持することが可能。（一般的なNoSQLと同じ話）
- コレクションにはドキュメントだけが含まれます。（データおよびマップは保持できない）
- 複雑な階層構造を実現したいなら [公式ドキュメント：階層構造](https://firebase.google.com/docs/firestore/data-model?hl=ja#hierarchical-data) を見てくれよな
- コレクション内のドキュメントの名前は一意です。
- コレクションを意識的に「作成」したり「削除」したりする必要はない。（勝手に作られ、勝手に消えるらしい。）

**【疑問】**：コレクションが勝手に作られる時の「コレクションの名前」はランダムに作成されるってことか？　→　YES

**リファレンス**

- データにアクセスするためには、「リファレンス」を作成する必要がある。
- 「リファレンス」はデータの**存在有無に関わらず**作成することができる。
- 「リファレンス」を作成しても**ネットワーク操作は実行されない**。
- 「リファレンス」には「コレクションリファレンス」と「ドキュメントリファレンス」が存在する。
    - それぞれ別のオペレーションを実行できる。

```javascript:コード例1.js（usersコレクションのalovelaceドキュメントのデータにアクセスする時のリファレンス）
var alovelaceDocumentRef = db.collection('users').doc('alovelace');
```

```javascript:コード例2.js（usersコレクションのalovelaceドキュメントのデータにアクセスする時のリファレンス）
var alovelaceDocumentRef = db.doc('users/alovelace');
```

```javascript:コード例3.js（usersコレクションにアクセスする時のリファレンス）
var alovelaceDocumentRef = db.collection('users');
```

コード例2が一番使いやすそうだな。
でも、検索とかを考えると、まずは「コレクションリファレンス」を作成するケースのが多いのか？

**サブコレクション**

- サブコレクション内のドキュメントにもサブコレクションを格納できるため、データをさらにネストすることができます。
- データは最大 100 レベルまでネストできます。

> 注意：現在、Cloud Firestore では複数のサブコレクションにまたがるクエリはサポートされていません。複数のコレクションに対してデータのクエリを実行する必要がある場合は、ルートレベルのコレクションを使用してください。

「現在」ってことは、将来実装されるんかな？

> 警告：ドキュメントを削除しても、そのサブコレクションは削除されません。

コレクションは勝手に削除されるのに、サブコレクションは削除されないんかーい。

### データ構造の選択
「[ここ](https://firebase.google.com/docs/firestore/manage-data/structure-data?hl=ja)」の話

データを構造化する場合、以下選択肢があるらしい。

**ドキュメント内のネストデータ**

ドキュメントの中で、以下のように構造化すること。

- メリット
    - 固定データならこれで十分。らくちん。
- デメリット
    - ネストされたリストにはクエリを実行できない
    - スケーラビリティが無い（他と比べて）
    - リストが増大 → ドキュメントが大きくなる → 検索時間が遅くなる。（可能性がある）
- ユースケース
    - チャットアプリで、ユーザーが最近閲覧した 3 つのチャットルームを、ネストされたリストとしてプロフィールに保存する。

```
    name :
      first : "Ada"
      last : "Lovelace"
    born : 1815
    rooms :
      0 : "Software Chat"
      1 : "Famous Figures"
      2 : "Famous SWEs"
```

**サブコレクション**

ドキュメントの配下にサブコレクションを定義すること。

- メリット
    - リストが大きくなっても、親ドキュメントのサイズが変わらない。＝　検索時間の遅延の可能性が少ない
    - サブコレクションに対しては、すべてのクエリ機能が利用可能。
- デメリット
    - サブコレクションを簡単に削除することはできない。
    - サブコレクション間で複合クエリを実行することもできない。
- ユースケース
    - 同じチャットアプリで、ユーザーやメッセージのデータをチャットルームのドキュメント内のコレクションとして作成する。

**ルートレベルのコレクション**

要は、そもそも別のコレクションにするってこと。

- メリット
    - 強力なクエリを実行でき、柔軟性とスケーラビリティに優れている。
- デメリット
    - データが階層的になっていることから、データベースが拡大するにつれ、データの取得が難しくなる可能性がある。
- ユースケース
    - 同じチャットアプリで、ユーザー用に 1 つ、チャットルームとメッセージ用に 1 つそれぞれコレクションを作成する。


**オレオレ基準**

ドキュメントを読んだ理解として。

```
データの性質が異なる　→　YES　→　ルートレベルのコレクション
　　↓
　　NO
　　↓
データが時間とともに増加する → YES → サブコレクション
　　↓
　　NO
　　↓
ネストデータ（マップ）
```

### データ型
「[ここ](https://firebase.google.com/docs/firestore/manage-data/data-types?hl=ja)」の話

ソート順は以下らしい。（データ型も一覧になってるし、何となく分かるっしょ。）

1. Null 値
2. ブール値
3. 数値順に並べ替えられた整数値と浮動小数点値
4. 日付の値
5. テキスト文字列の値
6. バイト値
7. 参照
8. 地理的座標値
9. 配列値
10. マップ値

### データの追加・更新
「[ここ](https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja)」の話

`set()`と`add()`と`update()`の3つの選択肢がある。

| メソッド | 何ができるのか？ | 補足 |
|:--|:--|:--|
| set() | ドキュメント全体を上書き | ドキュメントが存在しないときは新規作成扱い。  |
| add()  | ランダムIDでの新規追加 | IDでソートはされないので、作成順にソートしたい場合はデータにタイムスタンプの保持が必要。 |
| update()  |  ドキュメントの一部を更新 | set()だと、予期せぬデータの上書きがあるので。  |

**カスタムオブジェクト**

当たり前だけど、Mapをそのまま使うより、Javaとか、独自クラスが定義できる場合は、そういったので、ラップしてあげたほうが使いやすいよ。

### トランザクションと一括書き込み
「[ここ](https://firebase.google.com/docs/firestore/manage-data/transactions?hl=ja)」の話

データの原子性（ACIDのA）を担保の仕方として、2つの選択肢があるらしい。
「トランザクション」と「一括書き込み」。

「一括書き込み」の方が、メリットが多いので、なるべく「一括書き込み」に寄せたほうが良さそう。

**トランザクション**
普通にDBのトランザクション-コミットの話。実際のコードは公式を御覧ください。

「`get()`」からの、「`set()`か`add()`か`update()`」ってのが、基本構成らしい。

【注意事項】

- 「読み取りオペレーション」は「書き込みオペレーション」の**前に**実行する必要があります。
- 「トランザクションが読み取るドキュメント」に対して**同時編集が影響する場合**は、「トランザクションを呼び出す関数（トランザクション関数）」が**複数回実行される**ことがあります。
- 「トランザクション関数」はアプリケーションの状態を**直接変更してはなりません**。
- クライアントが**オフラインの場合、トランザクションは失敗**します。

**一括書き込み**

単純に「`set()`か`add()`か`update()`の組み合わせ」ってのが、基本構成らしい。

- 一括書き込みには最大 500 のオペレーションを含めることができる。
- 接続のオーバーヘッドが少なくなるので、高性能。
- トランザクションよりも失敗が少ない

**データの検証**

[Cloud Firestore セキュリティ ルール](https://firebase.google.com/docs/firestore/security/get-started?hl=ja)を使用してデータを検証できるらしい。
（モバイル / ウェブ クライアント ライブラリの場合に限る）

`getAfter()`ってメソッドを使えばなんかできるらしい。

### データの削除
「[ここ](https://firebase.google.com/docs/firestore/manage-data/delete-data?hl=ja)」の話

サンプルコードは公式を見よう。

| やりたいこと | 手段 |
|:--|:--|
| ドキュメントの削除 | delete()  |
|ドキュメント内の一部のデータの削除|FieldValue.delete()|
|コレクションの削除|※クライアント側からの実施は非推奨。サーバサイドで実行しよう。|
|なんでもできる|Firebase CLI |

### FirebaseコンソールでのFirestoreの管理
「[ここ](https://firebase.google.com/docs/firestore/using-console?hl=ja)」の話

まぁGUIだし、ここは触ったほうが早い。

**できること**

- データの表示、追加、編集、削除
- Cloud Firestore セキュリティ ルールの作成と更新
- インデックスの管理

### エクスポート・インポート
「[ここ](https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja)」の話

GCPで課金プロジェクト扱いにすると使えるようになる。
（いろいろ面倒な設定があるっぽい。）

今回はひとまず『「インポート・エクスポート」ができるんだなー』って理解でとどめておく。

### データの取得
「[ここ](https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja)」の話

データを取得するには 2 つの方法がある。

- メソッドを呼び出してデータを取得する。
- データ変更イベントを受信するリスナーを設定する。（？？？）

**ドキュメントの取得**

1. ドキュメントリファレンスを作成する
2. リファレンスオブジェクトで `get()` を実行する

> 注: docRef により参照される場所にドキュメントがない場合、結果の document は空になり、そのドキュメントに対して exists を呼び出すと false が返されます。

```javascript:イメージ
db.collection("cities").doc("SF").get()
```


**【再掲】カスタムオブジェクト**

当たり前だけど、Mapをそのまま使うより、Javaとか、独自クラスが定義できる場合は、そういったので、ラップしてあげたほうが使いやすいよ。

**コレクションから複数のドキュメントを取得する**

1. コレクションリファレンスを作成する
2. リファレンスオブジェクトで `where()` を使って条件を設定する
3. リファレンスオブジェクトで `get()` を実行する

```javascript:イメージ
db.collection("cities").where("capital", "==", true).get()
```

**コレクションのすべてのドキュメントを取得する**

1. コレクションリファレンスを作成する
2. リファレンスオブジェクトで `get()` を実行する

```javascript:イメージ
db.collection("cities").get()
```

**ドキュメントのサブコレクションの一覧を取得する**

クライアント側では取得できない！
サーバー側でしか取得できない！

### リアルタイム アップデートを入手する
「[ここ](https://firebase.google.com/docs/firestore/query-data/listen?hl=ja)」の話

> 注: リアルタイム リスナーは、Python、Go、PHP クライアント ライブラリではまだサポートされていません。

`onSnapshot()` で **ドキュメント** をリッスン（監視的なこと）できる。
（コレクション内全てのドキュメントにリッスンすることで、コレクションをリッスンしているのと同義な状況を作り出すことは可能っぽい）

```javascript:イメージ
db.collection("cities").doc("SF").onSnapshot()
```

**ローカル変更のイベント**

以下順番で処理が行われる

1. ローカルでの変更処理
2. ローカルのスナップショットリスナーの起動
3. 変更データをバックエンドに送る

スナップショットリスナーのイベントキックが、サーバーによるものか、ローカルによるものかは、判別可能。

**メタデータ変更のイベント**

「データの中身に変更は無いけど、上書きイベントが走ったよ！」とか言う時に使うっぽい。
必要そうになったら、ちゃんと読み込もうかな。。。

**コレクション内の複数のドキュメントのリッスン**

こんなこともできる。

```javascript:イメージ
db.collection("cities").where("state", "==", "CA").onSnapshot()
```

**スナップショット間の変更の表示**

発生したイベントが「追加」「変更」「削除」のいずれなのか、判別が可能。

> 重要: 最初のクエリ スナップショットには、クエリに一致する既存のすべてのドキュメントの added イベントが含まれています。

**リスナーのデタッチ**

`unsubscribe()` 関数を使用すると、更新のリッスンを停止できます。

**リッスンエラーの処理**

> エラーが発生すると、リスナーはそれ以上イベントを受信しなくなるため、リスナーをデタッチする必要はありません。

ほう。

### 単純なクエリと複合クエリを実行する
「[ここ](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja)」の話

**単純なクエリ**

以下の例以上に説明するポイントが見つからん。

```javascript:例
citiesRef.where("state", "==", "CA")
citiesRef.where("population", "<", 100000)
citiesRef.where("name", ">=", "San Francisco")
```

**複合クエリ**

複数の`where()`を使って複合クエリが作れるが、注意事項がある！

- AND条件しかできない（orは無理）
- 等価演算子（`==`）と範囲比較（`<`、`<=`、`>`、`>=`）を組み合わせる場合は、必ずカスタム インデックスの作成が必要
- 範囲比較（`<`、`<=`、`>`、`>=`）は、1 つのフィールドでのみ実行できます。

```javascript:◯：有効な例（単一のフィールドで、範囲比較が使われている）
citiesRef.where("state", ">=", "CA").where("state", "<=", "IN")
citiesRef.where("state", "==", "CA").where("population", ">", 1000000)
```

```javascript:×：無効な例（複数のフィールドで範囲比較が使われている）
citiesRef.where("state", ">=", "CA").where("population", ">", 100000)
```

**クエリの制限事項**

- 複数のフィールドに範囲フィルタを適用するクエリ（前述の通り）。
- 複数のコレクションまたはサブコレクションにわたる単一のクエリ。
    - 各クエリは、ドキュメントの単一コレクションに対して実行されます。
    - 複数のクエリで実現する必要がある。
- 個々の配列メンバーのクエリ。
    - ただし、[配列、リスト、セットの操作](https://firebase.google.com/docs/firestore/solutions/arrays?hl=ja)を使用すれば、できなくもない。
- OR条件でのクエリ。
    - アプリケーションで、クエリ結果をくっつければ実現可能。
- `!=`演算子。
    - `>`とか`<`を上手く使って実現してください。

### データの並び替えと制限
「[ここ](https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja)」の話

- `orderBy()`で並び順を指定可能
- `limit()`で取得件数を指定可能

注意事項あり

- 範囲比較（<、<=、>、>=）のフィルタがある場合、最初の並べ替えは同じフィールドで行う必要があります。

```javascript:◯：有効な例（範囲比較と「最初の並べ替えのフィールド」が一緒）
itiesRef.where("population", ">", 100000).orderBy("population")
itiesRef.where("population", ">", 100000).orderBy("population", "country")
```

```javascript:×：無効な例（範囲比較と、「最初の並べ替えのフィールド」が異なる）
citiesRef.where("population", ">", 100000).orderBy("country", "population")
```

**【疑問】**： desc とか asc とかの指定は？　→　できない。強制ASC。


### クエリカーソルを使用したデータのページ設定
「[ここ](https://firebase.google.com/docs/firestore/query-data/query-cursors?hl=ja)」の話

クエリの開始点と終了点を定義することで、以下のことを行えます。

- データのサブセットを返す。
- クエリ結果にページを設定する。

開始地点の指定：`startAt`、`startAfter`・・・何ドキュメント目**から**取得するかを指定できる
終了地点の指定：`endAt`、`endBefore`・・・何ドキュメント目**まで**取得するかを指定できる

**ドキュメント スナップショットを使用したクエリカーソルの定義**

以下例のように、取得したドキュメントを基準に、クエリを作成できる。
以下は、「population」が事前に取得した「doc」よりも多いドキュメントを取得するクエリ。（抜粋）

```javascript:例
    var biggerThanSf = citiesRef
        .orderBy("population")
        .startAt(doc);
```

**クエリのページ設定**

今まで登場してきた`limit`、「開始地点」、「ドキュメントスナップショットを使用したクエリカーソルの定義」を活用すると、ページングの実装が以下のように可能になる。

```javascript:公式のサンプルソースそのまま
var first = db.collection("cities")
        .orderBy("population")
        .limit(25);

return first.get().then(function (documentSnapshots) {
  // Get the last visible document
  var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
  console.log("last", lastVisible);

  // Construct a new query starting at this document,
  // get the next 25 cities.
  var next = db.collection("cities")
          .orderBy("population")
          .startAfter(lastVisible)
          .limit(25);
});
```

**複数のカーソル条件の設定**

「開始地点」「終了地点」をうまく活用すれば、検索条件のような使い方が可能。
以下の様なデータがあったとする。

|名前|州|
|---|---|
|スプリングフィールド|マサチューセッツ|
|スプリングフィールド|ミズーリ|
|スプリングフィールド|ウィスコンシン|

そうすると以下のようにできる。

```javascript:公式サンプルそのまま
// Will return all Springfields
db.collection("cities")
   .orderBy("name")
   .orderBy("state")
   .startAt("Springfield")

// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db.collection("cities")
   .orderBy("name")
   .orderBy("state")
   .startAt("Springfield", "Missouri")
```

### インデックスのタイプ
「[ここ](https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja)」の話

基本的なインデックスは、自動で作成される。
ただし、より効率的な検索を望むなら、カスタマイズが可能。

**インデックスのタイプ**

【インデックス モード】

|インデックス モード|説明|
|---|---|
|昇順|範囲検索の使用と、そのフィールド値での並べ替え（昇順）がサポートされます。|
|降順|範囲検索の使用と、そのフィールド値での並べ替え（降順）がサポートされます。|
|配列の内容|フィールドでの array_contains クエリ句の使用がサポートされます。|

昇順モードと、降順モードは「別のインデックス」として定義される点に注意。

**【疑問】**：「array_containsクエリ句」ってなんぞ？　→　一通り読んだけどわからんかった。

【単一フィールド インデックス】
特定の 1 つのフィールドを含む、コレクション内のすべてのドキュメントの並べ替え済みマッピング。

- 基本的には自動的に生成される。自動生成にはルールがある。（公式ドキュメント参照のこと）
- インデックスを除外することも可能。除外することによるメリットは、[インデックスに関するベストプラクティス](https://firebase.google.com/docs/firestore/query-data/indexing?hl=ja#manage_single-field_index_exemptions)を参照のこと。
- これがあることで以下クエリが実現できる。
    - 単一フィールドに対する等式比較
    - 単一フィールドに対する**範囲**比較
    - 複数フィールドに対する等式比較
- 以下クエリは実現できない（複合インデックスが必要）
    - 複数フィールドに対する**範囲**比較

【複合インデックス】
1 つだけではなく複数の特定のフィールドが含まれる、コレクション内のすべてのドキュメントの並べ替え済みマッピング

> 注: 1 つの複合インデックスに含めることのできる配列フィールドは 1 つだけです

**インデックスと価格**

インデックスはストレージ費用の対象。
計算方法は[こちら](https://firebase.google.com/docs/firestore/storage-size?hl=ja#index-entry-size)。

容量削減のプラクティスとして「[インデックスマージ](https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja#taking_advantage_of_index_merging)」という手法が存在する。
（公式ドキュメント参照：理解しきれてないため・・・）

**インデックスの制限**
他の制限と合わせて、[こちら](https://firebase.google.com/docs/firestore/quotas?hl=ja)を参照のこと。

**インデックスに関するベストプラクティス**
（公式そのまま）

|ケース|説明|
|---|---|
|大きな文字列フィールド|クエリには使用しない長い文字列値を保持することが多い文字列フィールドがある場合は、インデックス作成からそのフィールドを除外することでストレージの費用を削減できます。|
|コレクションへの書き込みレートが高く、連続した値を持つドキュメントが含まれる|コレクション内のドキュメント間で順次に増加または減少するフィールド（タイムスタンプなど）のインデックスを作成する場合は、コレクションへの最大書き込みレートは 500 回/秒です。連続した値を持つフィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外すれば、この制限を回避できます。たとえば、書き込みレートが高い IoT のユースケースでは、タイムスタンプ フィールドを持つドキュメントを含むコレクションは、500 回/秒の書き込み上限に近づく可能性があります。|
|大規模な配列フィールドまたはマップ フィールド|大規模な配列フィールドまたはマップ フィールドの場合、ドキュメントごとに 20,000 件のインデックス エントリの上限に近づく可能性があります。大規模な配列フィールドまたはマップ フィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外してください。|

### インデックスの管理
「[ここ](https://firebase.google.com/docs/firestore/query-data/indexing?hl=ja)」の話

**不足しているインデックスをエラー メッセージから作成する**
Firebase コンソールでエラーを確認できるので、そこから不足しているインデックスを作成しよう！

**インデックスの構築時間**
データ量に応じて時間がかかるよ。

**インデックス構築エラー**
[インデックスの上限](https://firebase.google.com/docs/firestore/quotas?hl=ja)に達するとエラーがでるので、注意してね。

## データを保護する
「[ここらへん一帯](https://firebase.google.com/docs/firestore/security/overview?hl=ja)」の話

**概要**
構築するモノによって、保護の仕方が異なる。

- モバイルおよびウェブ クライアント ライブラリ：Firebase Authentication と Cloud Firestore セキュリティ ルールを使用して、サーバーレスな認証、承認、データ検証を処理します。
- サーバー クライアント ライブラリ：Cloud Identity and Access Management（IAM）を使用して、データベースへのアクセスを管理します。

**何ができるか**

細かい書き方は、公式ドキュメント参照。ここでは少しサマリした情報を記載するに留める。

- アクセス制御
    - 標準でできること
        - 権限制御(allow)
            - 読み取り許可/禁止
                - さらに「get」「list」といった詳細な指定が可能
            - 書き込み許可/禁止
                - さらに「create」「update」「delete」といった詳細な指定が可能
        - 許可/禁止条件指定(if)
            - boolean型で記述可能
        - 操作対象指定(match)
            - 操作対象のドキュメントを条件指定可能（ワイルドカードも使用可能）
            - サブコレクションには個別で指定が必要。（ドキュメントを指定しても、配下のサブコレクションは対象にならない）
                - ただし、「再帰ワイルドカード構文（`{name=**}`）」を使用した場合は、サブコレクションも対象になる。
        - 注意事項
            - いずれかの条件がtrueであれば、許可される。（ホワイトリスト方式）
            - 制限事項：別表
    - 標準でできないこと
        - ユーザー/ロールベースの制御：[Firebase Authentication](https://firebase.google.com/docs/auth/?hl=ja)も使えば可能
- データ検証
    - 許可/禁止条件指定(if)の中で、合わせて検証が可能（と言いたいっぽい。）
        - [詳細1](https://firebase.google.com/docs/firestore/security/rules-conditions?hl=ja#data_validation)
        - [詳細2](https://firebase.google.com/docs/firestore/security/rules-query?hl=ja#evaluating_constraints_on_queries)

**制限事項**
公式そのまま

|制限|詳細|
|---|---|
|評価ごとの一意の呼び出し（exists()、get()、getAfter()）の最大数|それぞれ 3 ですが、最大合計数は 5 です。同じドキュメントに対する複数のリクエストは、個別のリクエストとしてカウントされません。|
|関数の呼び出しの深さの最大数|20|
|関数の再帰的な呼び出し、または循環的な呼び出しの最大数|0（許可されていません）|
|ルールセット内の式の最大数|10,000|
|ルールセットの最大サイズ|64 KB|

**ルールのテスト**
Cloud Firestore には、ルールセットのテストに使用できるルール シミュレータが用意されています。
シミュレートされるリクエストは、現在デプロイされているルールセットではなく、エディタ内のルールセットと照らし合わせて実行されます。

**ルールのデプロイ**
モバイルアプリから Cloud Firestore を使用するには、セキュリティ ルールをデプロイしておく必要があります。

## オフラインデータを有効にする
「[ここ](https://firebase.google.com/docs/firestore/manage-data/enable-offline?hl=ja)」の話

> 注: オフラインの永続性は Android、iOS、ウェブアプリのみでサポートされています。

端末がオンラインに戻ると、アプリがローカルで行った変更とリモートの Cloud Firestore に保存されたデータが同期されます。

**設定**

- Android と iOS の場合、オフラインの永続性はデフォルトで有効になっています。永続性を無効にするには、PersistenceEnabled オプションを false に設定します。
- ウェブの場合、オフラインの永続性はデフォルトで無効になっています。永続性を有効にするには、enablePersistence メソッドを呼び出します。

あとはだいたい今までの復習に近いので割愛。
詳細は公式参照。

## ソリューション
「[ここ](https://firebase.google.com/docs/firestore/solutions/?hl=ja)」の話

TIPSみたいなもの。読んでおくと良い。

- 配列、リスト、セットの操作：配列のようなデータ構造のドキュメントにデータを保存し照会します。
- 集約クエリ：トランザクションと Cloud Functions を使用して、データの集約を構築します。
- 分散カウンタ：現在サポートされているよりも頻繁にドキュメントを更新する方法。
- 全文検索：Cloud Firestore のドキュメントに含まれているテキストを検索します。
- プレゼンスを構築する：ユーザーがアクティブに接続しているかどうかを示すプレゼンス システムを追加します。
- ユーザーとグループのデータアクセスを保護する：ユーザー役割に基づいてドキュメントへのアクセスを制御する

## Cloud Functions で拡張する
「[ここ](https://firebase.google.com/docs/firestore/extend-with-functions?hl=ja)」の話

いろんなトリガーが用意されてる。

**注意事項**

あくまでベータ版なので、以下に注意しよう。

- Cloud Firestore のデータが変更されてから関数がトリガーされるまで 5 秒以上かかる場合があります。
- 現在、関数呼び出しの配信は保証されていません。Cloud Firestore と Cloud Functions の統合が向上した段階で、「at-least-once」配信を保証する予定です。ただし、ベータ版ではこれが保証されるとは限りません。1 つのイベントで複数の呼び出しが発生する可能性があるため、関数で高い精度が求められる場合には、関数の書き込みをべき等にする必要があります。
- 順序は保証されません。急激な変更を行うと、予期しない順序で関数が呼び出される可能性があります。

## APIを使用する
「[ここ](https://firebase.google.com/docs/firestore/use-rest-api?hl=ja)」の話

認証の仕方が他と違うので、注意しよう。
（今回使うとは思えないので、読み込まない。）

## 使用量、制限、料金
### 使用量と制限
「[ここ](https://firebase.google.com/docs/firestore/quotas?hl=ja)」の話

流石に公式を見ましょう。
無料枠あるよ。

### 課金
「[ここ](https://firebase.google.com/docs/firestore/pricing?hl=ja)」の話

課金対象

- 実行する読み取り、書き込み、および削除の回数。
- データベースにより使用されるストレージの容量（メタデータとインデックスのオーバーヘッドを含む）。
- ネットワーク帯域幅の使用量。

### ストレージ計算
「[ここ](https://firebase.google.com/docs/firestore/storage-size?hl=ja)」の話

細かいので、公式見てー。

### ロケーション
「[ここ](https://firebase.google.com/docs/firestore/locations?hl=ja)」の話

Cloud Firestore を使用する前に、データベースのロケーションを選択する必要があります。
レイテンシを低減し可用性を高めるため、データを必要とするユーザーとサービスに近いロケーションにデータを保存します。

> 警告: プロジェクトのロケーションを選択した後は、そのロケーションを変更することはできません。 プロジェクトのロケーションの設定は複数のプロダクトに適用されます。

日本に近いトコロがあるわけじゃないし、現時点では「マルチリージョン」「us-central」でいい気がする。

## FireStoreとRealtime Databaseを比較する
「[ここ](https://firebase.google.com/docs/firestore/rtdb-vs-firestore?hl=ja)」の話

Google的には、新しい人は「Firestore」使えば良いよって話らしいので、それに従う。

## FireStoreとRealtime Databaseを使用する
「[ここ](https://firebase.google.com/docs/firestore/firestore-for-rtdb?hl=ja)」の話

Firestoreへのデータ移行の話ばっかり。


# さいごに
4～8時間くらいかかった・・・

以上