Cloud FirestoreとFirebase Cloud Storageを使ってソーシャル機能を実装する方法
こんにちは、Stamp Incのnoriです。
金曜日の20時くらいからこの記事を書き始めて若干の後悔を感じながら記事を書きました。書き終わったら飲みに行きます。
この記事ではCloud FirestoreとFirebase Cloud Storageを使ってソーシャル機能を実装する方法を紹介します。
どの機能においてもですが、実装方法は__要件と照らし合わせて考えることが重要__です。ソーシャル機能も要件によって実装方法は異なってきます。要件によって実装がどう異なるかについても説明しようと思いますので最後まで読んで頂けると幸いです。
また、ここでのソーシャル機能とは__フォロー、フォロワー__に限定して話を進めます。
ソーシャル機能
早速ですが、フォロー、フォロワーをCloud Firestoreではどのように表現するか見ていきましょう。
__Query__と__SubCollection__が利用可能なCloud Firestoreでは2種類の実装方法が考えられます。
Queryでソーシャル機能を実装
フォロワーを表す
/social/:social_id
// Document data
{
"createdAt": Timestamp,
"updatedAt": Timestamp,
"from": {
"id": "USER_ID",
"name": "USER_NAME",
"thumbnail": "STORAGE_REFERENCE_PATH"
},
"to": {
"id": "USER_ID",
"name": "USER_NAME",
"thumbnail": "STORAGE_REFERENCE_PATH"
}
}
SubCollectionでソーシャル機能を実装
フォロワーを表す
/social/:followee_id/followers/:follower_id
フォローしてる人を表す
/social/:follower_id/followings/:followee_id
// Document data
{
"createdAt": Timestamp,
"updatedAt": Timestamp,
"name": "USER_NAME",
"thumbnail": "STORAGE_REFERENCE_PATH"
}
2つの実装方法の違い
上記の二つの方法に大差はありません。 Read/Writeを考えた時に少しだけ差が出るので説明します。
2つの実装方法にWrite性能に大きな差が出ます。
WriteとReadを考える
Writeでは__SubCollection__の場合はWriteBatch
を使って、2つのReferenceにDocumentを書き込む必要で__Query__の方は不要です。
問題になるのは、連続的なトラフィックが起こった場合です。Collectionへのインサートは秒間500回に制限されています。RootCollectionにSocial
Documentを配置した場合は性能が制限されるので__SubCollection__へ配置した方がいいでしょう。
Readでは__Query__を使う方にはwhere
が必要で、__SubCollection__の方は不要です。
where
のレンジ表現に制限があるCloud Firestoreでは可能な限りwhere
を使わずにかける__SubCollection__の方が若干優位かなとは思ってます。
以上のことから__SubCollection__の実装方法をオススメします。
DataのFieldについて考える
次にFieldについて考えていきましょう。今回はシンプルな__SubCollection__を用いた場合のFieldについて説明します。Fieldについても2種類の方法を紹介します。
JOINを使う実装
Field | Description |
---|---|
createdAt | データが作られた時間を記録 |
isAvailable | 有効フラグ |
isFollowing | フォローフラグ |
JOINを使わない実装
Field | Description |
---|---|
createdAt | データが作られた時間を記録 |
updatedAt | データが更新された時間を記録 |
name | ユーザー名 |
thumbnail | ユーザーのサムネイル |
2つの実装方法の違い
2つの実装では、Readが大きく異なります。どちらの方が適しているのかは要件によって変化すると思います。
InstagramとTwitterのフォロワーリストを比べて見るとInstagramでは表示されているデータが少ないのに対して、Twitterでは多くの情報が記載されているのがわかります。微妙な違いですが、データ構造やデータの取得方法には大きく影響します。
JOINを使う実装のメリット・デメリット
JOINを使う実装のメリットはなんと言っても新鮮なデータであること、デメリットはN+1のReadが発生すること😱
JOINを使わない実装のメリット・デメリット
メリットはReadが一回で終わること、デメリットは更新頻度が低いデータしか入れてはいけないことです。
両者とも優位性はある。
前者のN+1問題が起こる実装なんて考えられないと思うかも知れませんが、Cloud FirestoreのクライアントSDKではCacheが準備されています。
また、フォロワーリストの表示順が最新順であるとするならばデータはローカルにある可能性が高いと考えると通信はさほど発生しない可能性の方が高く、Readについて気にする必要はないかも知れません。例えばiOSのデフォルトでは100MBが割り当てられいるため、なかなかのデータがローカルに保存されています。またフォロワーを確認するタスクがメインとなるアプリはないはずなので、N+1 ≒ 100
くらいと考えてしまってもいいかも知れません。
後者の実装を考えるときユーザーデータの更新頻度は低く、一般的なユーザーのフォロワー数は数百人であることが予想できます、全てのフォロワーのデータを更新したとしても大きな負荷ではありません。
しかし、もしユーザーデータが頻繁に更新される場合では(Nmax+1)x更新回数
Writeが発生することになるので、どちらの実装にするのかは要件に合わせてください。
更新されないデータのみを入れておくのも手段の一つだと思います。
Firebase Cloud Storageを組み合わせる
Fieldにあるthumbnail
にはFirebase Cloud StorageのPathが入ることを想定しています。このPathには以下のようにフォーマットを固定するといいと思います。
/user/:user_id/thumbnail
個人的にはCloud Firestoreの構造とStorageの構造を一致させることは非常に効果的で、開発時に楽になりますし、セキュリティルールの記載も楽になります。またthumbnail
など特定の場所に使う場合のファイル名は固定するといいでしょう。こうすることで画像データの差し替えではDocument dataの更新は行わずに済みます。
Cloud FirestoreとCloud Storageをいい感じで連携できるライブラリ作りました。
iOSとTypeScriptで利用可能です。ぜひ試してみてください!!
https://github.com/1amageek/Ballcap-iOS
https://github.com/1amageek/ballcap.ts
それでは👍🏻