FirestoreでFieldにフラグを持たせるかCollectionを分けるか
問題提起
面白い!適当に考えるとFirestoreに出題済みかどうかのフラグを持たせる実装になりそうだけれど、DBを2つ用意したのは何のためなんだろう
更新受け取りの都合上未出の問題も見えてしまうから?
状況
Firestoreのドキュメントに対して、
ユーザーに対する未公開(管理者には公開)
閲覧可能なユーザー全員に対する公開
を管理したい。
そのために、
手法① Admin用のCollectionと一般用のCollectionの2つを用意し、ドキュメントを移動させる
手法② 共通のCollectionを用意し、Documentのフィールドにステータスを持たせる
といった手段がある。どちらを選ぶのが良いか。
結論
どちらでも良い。いや、手法①だな
ルールが複雑になると管理が地獄っぽくて、手法①の方が拡張してもルールが複雑になりにくいため。
ルールについて
手法①では、Collectionごとにルールを指定する
手法②では、ドキュメントのフィールドに基づいて変更する。
手法①
省略
手法②
code:firestore.rules
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to read data if the document has the 'visibility'
// field set to 'public'
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
}
}
ドキュメントのフィールドを用いてルールを設定した場合、
ルールを満たすようなフィルタを掛けたクエリしか受け付けないという仕様になっている。
クライアントが読み取り権限を持たないドキュメントをクエリが返す可能性がある場合、リクエスト全体が失敗します。
where文の中とfirestore ruleという、文法チェックが働かない部分に
同じ内容の条件文を異なる記法で異なる場所に2回書き、
実行するまでの機械的なチェック機構が存在しないため、
実際にクエリを実行するまでエラーが現れず、実行した際にも
結果としては、コレクションのデータが返らないだけのため、
一見してエラーがわかりにくく、
firebaseのバグの温床となる部分である。
(呼び出し部分に単体テストを書くことで解決?)
今回のケースのように、Collectionの名前とそのstatusやaccessRoleなど、getの際のフィルタに妥当性がある際には用いても良いが、
安易に用いると拡張時に困ることがあるかもしれない。
料金について
補足するとフラグ管理でセキュリティルールを設定することもできますが、その判定にも(documentを参照するため)課金が発生するのでcollection単位でセキュリティルールを適用する方が多分お得です(あんまり計算してないけど)
午前0:23 · 2023年10月13日
---
FirestoreのCollectionの読み取りはクエリでヒットした個数
つまり、1000件のレコードがあるコレクションをwhereクエリによって
10件に絞り込んで取得した場合、
「10件の読み取り」としてカウントされる。
また、セキュリティルールでのドキュメント読み取りも1件としてカウントされる。
Cloud Firestore セキュリティ ルール
モバイル ライブラリおよびウェブ クライアント ライブラリの場合、Cloud Firestore セキュリティ ルールで exists()、get()、getAfter() を使用して 1 つ以上のドキュメントをデータベースから読み取ると、以下のように追加の読み取り料金が課金されます。
exists()、get()、getAfter() はドキュメントのセキュリティルールから、別のドキュメントを参照する際に用いられる。
セキュリティ ルールでは、get() 関数と exists() 関数を使用して、データベース内の他のドキュメントに対する受信リクエストを評価できます。get() 関数と exists() 関数は、完全なドキュメント パスを指定します。変数を使用して get() と exists() のためのパスを構築する場合は、$(variable) 構文を使用して明示的に変数をエスケープする必要があります。
参照するドキュメントのフィールドからの読み取りは、追加の読み取りとしてカウントされない
code: rules
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
手法①でも手法②でもユーザー数でスケールする部分の読み取り数、
特別に料金は変わらない。
(管理側でドキュメントの移動分(追加&削除)分だけ手法①の方が書き込み数が多いが誤差程度)
拡張性について
フィールドアクセスのコントロール
firestoreのドキュメントは「フィールドごとの読み取りアクセス制御」を行うことができない。
例えば、管理用のドキュメントには「問題データに解答データ」がついており、
ユーザーに送信用のドキュメントには「解答データ」を載せたくない場合
は、
コレクションを分けることが公式の推奨ソリューションとなっている。
フィールドのアクセスコントロールは手法①と相性が良い。
ロールやステータスの追加
例えば複数部屋での同時開催や、もっと言うとユーザーごとに出題問題が分岐したりなど。
手法①ではロールやユーザーごとにコレクションを分ける必要があり、
コレクションが膨大になるが
手法②ではフィールドを切り替えるだけで済むので手法②に分がある?
......と思ったのですが、
手法①はサブコレクションを利用すればロールやステータスも十分管理しやすいし、
ユーザーごとならuidをIDにした使ったドキュメントのサブコレクションにコピーすれば良い。
そして、その際でも、
ルールがコレクションと対応しているため、ルールの条件がコレクション内で複雑になることはない。
一方で手法②では、どんどんコレクションの中でどんどんルールの分岐が増えていき、書き換えるたびに、これまでの条件すべてに影響する。
書き込み数の見積もり的に問題なければ手法①以外ちょっと考えたくない。
結論
ルールはシンプルに保とう。