Cloud Firestoreは進化したFirebase Realtime Database

# Cloud Firestoreファーストインプレッション

リリース発表をついに来たか！って思いました。そしてFirestoreとFirebase Realtime Databaseがどう違うのかを読んで、大きく進化したなぁって思いました。そして実際に使ってみて、これはもはや別物って思いました。

FirestoreとFirebase Realtime Databaseの違いについてはすでにこちらでまとめられていたのでこちらをご覧ください。
https://qiita.com/Yatima/items/54ea22d0cea1acc6cbcb

# Cloud Firestoreのすごいところ

## Firestoreには[BoringSSL](https://boringssl.googlesource.com/boringssl/)が使われている

BordingSSLはGoogleによるOpenSSLのfork。2014年にForkを発表して、すでにAndroidなどでは使われていたようですが、本格的にGoogleはBordingSSLを導入していくんですねぇ。素晴らしい。

## [gRPC](https://github.com/grpc/grpc)も使われている


gRPC は、RPC (Remote Procedure Call) を実現するためにGoogleが開発したプロトコルの1つです。Protocol Buffers を使ってデータをシリアライズし、高速な通信を実現できる点が特長です。

https://qiita.com/oohira/items/63b5ccb2bf1a913659d6

## 保存できるデータ型が増えた。
Cloud Firestore では、ブール値、数値、文字列、地理的位置、バイナリ blob、タイムスタンプなど、さまざまな値のデータ型がサポートされています。

地理的位置を保存できると言うことは！？おそらくここのソリューションも今後提供して来ることが予想されます。

https://firebase.google.com/docs/firestore/manage-data/data-types?authuser=0

## SubCollectionは分離されている
Firebase Realtime Databaseが単純なツリー構造だったのに対しFirestoreのデータ構造は__Collection__, __Document__, __SubCollection__があります。

https://firebase.google.com/docs/firestore/data-model?authuser=0#hierarchical-data

Firebase Realtime Databaseでも同じような構造をツリー構造で構築することは可能です。しかし、SubCollectionには決定的な違いがあります。Firebase Realtime Databaseでは上位のツリー構造の上位のノードを削除するとそれに付随する下位のデータは全て削除されました。Firestoreでは違います。


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

>サブコレクションが関連付けられているドキュメントを削除しても、そのサブコレクションは削除されません。その後もサブコレクションには、リファレンスによるアクセスが可能です。たとえば、db.collection('coll').doc('doc') によって参照されるドキュメントは存在しなくなったにもかかわらず、db.collection('coll').doc('doc').collection('subcoll').doc('subdoc') によって参照されるドキュメントは存在する場合があります。ドキュメントを削除するときにサブコレクション内のドキュメントも削除する場合は、コレクションを削除するに示すように、手動で削除する必要があります。

### つまり親ノードのデータ量が重くならない
FirestoreのSubCollectionは__値__ではないく__参照__です、Firebase Realtime Databaseでは上位ノードでデータを取得すると下位のデータを全て取得していたためデータが非常に重くなっていました。
Firestoreではこれが解決されています。ちなみにFirebase Realtime Databaseでも同じことはできます。

Firebase Model Frameworkの[Salada](https://github.com/1amageek/Salada)ではRelationクラスでこれを実現しています。
https://github.com/1amageek/Salada/blob/master/Salada/Relation.swift

## バッチ処理は強力な機能
Firebase Realtime Databaseにも`updateValues`と言って複数のデータを同時に更新するメソッドは準備されていました。Firestoreではそれがさらに使いやすくなっています。

``` swift
// Get new write batch
let batch = db.batch()

// Set the value of 'NYC'
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)

// Update the population of 'SF'
let sfRef = db.collection("cities").document("SF")
batch.updateData(["population": 1000000 ], forDocument: sfRef)

// Delete the city 'LA'
let laRef = db.collection("cities").document("LA")
batch.deleteDocument(laRef)

// Commit the batch
batch.commit() { err in
    if let err = err {
        print("Error writing batch \(err)")
    } else {
        print("Batch write succeeded.")
    }
}
```

## クエリはRealmのような使い心地
定義したクエリの参照を監視し続けることができます。
クエリの使い心地よりも注目すべきはこのプロパティ`hasPendingWrites`。
いつ使うかと言うとチャットです。

例えばチャットでメッセージを送信した瞬間を想像してください。
Firebase Realtime Databaseではバックエンドのイベントをオブザーブしていたので、データがバックエンドに保存された後UIが更新されることになり若干の遅れを体感していたはずです。
しかし、Firestoreでは、バックエンドに保存される前であってもクライアントから書き込みがあったことを取得できるのでその遅れを感じなくてよくなります。

``` swift
db.collection("cities").document("SF")
    .addSnapshotListener { documentSnapshot, error in
        guard let document = documentSnapshot else {
            print("Error fetching document: \(error!)")
            return
        }
        let source = document.metadata.hasPendingWrites ? "Local" : "Server"
        print("\(source) data: \(document.data())")
    }

// After 2 seconds, make an update so our listener will fire again.
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
    self.db.collection("cities").document("SF").updateData([
        "population": 1000000
        ])
}

// RESULT:
// Server data: ["state": CA, "name": San Francisco, "population": 999999]

// Local data: ["state": CA, "name": San Francisco, "population": 1000000]
// Server data: ["state": CA, "name": San Francisco, "population": 1000000]
```

完了だけを監視することもできるようです。
> 書き込みが完了したことだけを知りたい場合、hasPendingWrites を使用する代わりに、完了コールバックをリッスンすることができます。

### やっぱりFirebaseのQueryは。。
Firebase Realtime Databaseよりは表現力は格段に上がっていますが、これだけのためにFirestoreを選択する理由にはならないと思います。

https://firebase.google.com/docs/firestore/query-data/order-limit-data?authuser=0


## Realtimeに制限ができた
> Cloud Firestore では、1 つのドキュメントを 1 秒間に約 2 回しか更新できません。
そんなに強烈な制限ではないですが、時間的な制限があるようです。

## データ数の取得は今まで通り
Collectionに入っているデータ数を取得したいときは次のようにする必要があります。

``` swift
        let db = Firestore.firestore()
        let citiesRef = db.collection("cities")

        citiesRef.getDocuments { (snapshot, error) in
            print(snapshot?.count)  // データ数の取得
        }
```

例えばフォロワーカウントをとりたい場合を想像してください。カウントなので数値だけが取れればいいはずですが、Firebaseでは全てのデータを取得してからでないと取得できません。
また、カウントを効率的に取得するためにはデータを保存したタイミングで別に数値型のプロパティを用意しトランザクションをしながらインクリメントしていくしかありません。

Firebase SDKにFeature Requestを送っているので、ここで__いいね__を押してもらえるとプライオリティが上がるかも。

https://github.com/firebase/firebase-ios-sdk/issues/257

ちなみにSaladaではRelationにインクリメントする機能を持たせているため、`count`ってするだけで取得できるようにしています。

https://github.com/1amageek/Salada/blob/master/Salada/Relation.swift#L118

## まだSaladaを使う理由はあります。

![logo.png](https://qiita-image-store.s3.amazonaws.com/0/80287/f2f42245-f82d-0c93-3ac4-c9c89218ea65.png)

SaladaはSwiftで作られたFirebase Model Frameworkです。
https://github.com/1amageek/Salada

- Firebase Realtime Databaseの方が早い
- モデルを全て面倒見てくれる
- Firebase Storageとの連携
- RelationはSubCollectionと同じ機能
- QueryはないがElasticSearchと連携する

でもFirestoreいい感じだから、Saladaをアップデートします。

SaladaをFirebase Realtime Databaseのために残して
Pringと言うCloud FirestoreのFrameworkを作りました。

PringはSwiftで作られたFirestore Model Frameworkです。

![Pring.png](https://qiita-image-store.s3.amazonaws.com/0/80287/da68a565-89d3-6a7b-ff75-2847cbd26216.png)

https://github.com/1amageek/Pring

まだまだ不安定なのでぜひ、バグの報告をいただければと思います。