🔥

【図で解説】Firestoreでできること・できないこと

2021/10/16に公開

Firestoreとは

Google社が提供するNoSQL型のデータベースです。
特徴として、リアルタイムのデータを受信できる、つまりDB側のデータが変更されるとすぐに、クライアント側に反映される仕組みを提供しています。

例)チャットアプリ

FirestoreはNoSQLの1つ

NoSQLとはざっくり言えば、RDB以外のデータベース製品です。FirestoreもNoSQLの1つです。Firestoreのデータ構造は、個人的な印象でいえば階層化されているKVSです。

KVSとは

KVS(Key-Value Store)とは、JavaでいうMap、Pythonでいう辞書、PHPでいう連想配列にあたるデータ構造です。キーとそれに紐づけられたデータを管理します。

kvs-get

KVSは一般的に逆引きに弱いです。逆引きとは、条件を満たすデータを取得することです。例えば、ユーザIDがキーで名前と電話番号が関連付けられたデータのとき、名前が"鈴木"で始まるデータをとるという操作です。

kvs-search

逆引きに対応するため、Firestoreではインデックスを作成することができます。フィールド単位のインデックスは自動で作成されます。ただし、すべての逆引きのケースに対応できるわけではありません。詳しくは以下をご覧ください。

Firestoreのデータ構造

Firestoreのデータ構造はRESTのパスのような構造のようになります。例えば、twitterのような投稿システムを考えてみましょう。各ツイートのパスを
/user/{user_id}/tweets/{tweet_id}
のように定義しましょう。
このとき、userstweetsの部分をコレクション、{user_id}{tweet_id}の部分をドキュメントIDと言います。/user/{user_id}/user/{user_id}/tweets/{tweet_id}で取得されるデータをドキュメントといいます。
このようなパスで表されるとき、実際のデータはこのような階層データになっています(followsコレクションも追加しています)

documents

以下ではこのデータ構造の時にできることとできないことを見ていきます。

検索系

IDでの検索

FirestoreではドキュメントIDを指定し、データを取得する処理を簡単に書くことができます。

IDに紐づくデータを全て取得 : 〇

SQLでいうところのselect * from {table_name} where id={id}に相当するデータが取れます。
ちなみに親のドキュメントID(以下の例では/users/xys)を指定した場合、サブコレクションのデータは取得されません。

get_by_key

IDに紐づくデータを一部取得 : ×

SQLでいうところのselect aaa, bbb from {table_name} where id={id} のように、フィールドのうちの一部のみを取得することはできません。

get_partial

(サブ)コレクションに紐づくデータを全部取得

あるユーザのtweetの全てを取得することは可能です。検索条件を指定することも可能です(以下の逆引きのところで指定できるもののみ)。

get_children

逆引き(検索クエリ)

Firestoreはデフォルトでは各フィールドに対してインデックスが張られているため、逆引きが可能です。また、複合インデックスを作成することも可能です。
また、比較演算子には < <= == => > などが使えます。

単一フィールドに対する検索クエリ : 〇

クエリに含まれるフィールドが1つであれば検索できます。
例えば、

などはそれぞれ検索が可能です。

複数フィールドに対する検索クエリ : △

複数フィールドに対する検索には大きな制限があり、==での比較は複数のフィールドにまたがって行えますが、範囲検索(< <= >= >で検索するもの)は1つのフィールドに対してしか発行することはできません。

できること:

  • messageが"おはよう"のものでlike数が10以上50以下のもの
    • 範囲検索が1つのフィールドに対してなのでOK

できないこと:

  • like数が50以上かつretweetが100以上
    • 範囲検索が2つ以上のフィールドにまたがっているのでNG

同一コレクション名で、ドキュメントをまたいだ検索 : 〇

全ユーザのtweetの中でlikeが100以上、のような検索はデフォルトではできませんが、インデッ
クスを追加することで可能となります。
複数フィールドで検索できない点は前述のものと同じです。

collection_group

そのほか検索でできないこと

Firestoreは基本的にKVSなので、RDBでできることは基本的にできないと考えたほうがよいです。

ドキュメントのjoin : ×

ドキュメント同士をFirestore側でjoinすることはできません。クライアントサイドでjoinするか、非正規化してデータを持つ必要があります。

LIKE句検索 : ×

LIKE句検索にも対応していません。検索は別のシステムに任せましょう。

COUNT, SUMなどの集約関数 : ×

countやsumなどの集約関数もサポートされていません。クライアント側で計算するか、非正規化する、Cloud Functionsを組み合わせるなどして実現します。

更新系

transaction : 〇

Firestoreではtransaction処理を行うことができます。likeの数を1プラスするなどの動作にtransactionが使えます。(ただしこの場合はincrement機能を使ったほうがよいです)

設計系

FirestoreはDBを(アプリを介さず)そのまま外部に公開しているようなものです。悪意のあるユーザにデータを改ざんされないようにセキュリティルールを定めることができます。

データ型の指定 : 〇

Firestoreはスキーマレスですが、設定ファイルでスキーマのようなものを設定することができます。
指定できる型はboolbytesfloatintlistlatlng(緯度経度)、numberpathmapstringtimestampです。

enum型 : △

stringにはmatch関数が用意されています。この関数は正規表現で文字列がマッチするか判定します。enumのどれかに合致するかという正規表現を書けば疑似的にenumを実現できます。

// statusが"TODO"か"INPROGRESS"か"DONE"でないといけない
document.status.matches("^(TODO|IN_PROGRESS|DONE)$")

Not null制約 : 〇

ドキュメントに必ずフィールドがあることを保証することもできます。

uniq制約 : △

例えばユーザのemailを一意に制限するといった制約を持たせる設定はありません。

uniq

FirestoreのIDが一意になる制約と、バッチ書き込みを組み合わせると可能です。

https://zenn.dev/yucatio/articles/2bcca0da587aa0

デフォルト値 : ×

デフォルト値はサポートされていません。Cloud Functionsを組み合わせる方法でデータの作成時に値をセットすることができます(ただし、値がセットされるまで若干の遅延あり)。

外部キー制約 : 〇

例えば、フォローできるのは実在するユーザであること、といった制約を書くことができます。

foreign_key

フィールドの制限 : 〇

悪意のあるユーザやtypoなどによって意図しないフィールドが作成されるのを防ぐことができます。

データへのアクセス制御 : 〇

例えば、ツイートの内容を変更できるのはツイートした本人のみ、といったルールを書くことが可能です。
閲覧についても、ある特定の条件を満たす場合にのみ閲覧できるというルールを書くことができます。

一度登録したデータを変更させない : 〇

例えば、一度ツイートした内容を変更できないようにする、といったことは可能です。

終わりに

自分がFirestoreを触る前に知りたかったことをまとめてみました。
公式サイトに書かれていることと重複する内容も多いですが、なるべく図を多くしてわかりやすくしました。お役に立てれば幸いです。

Discussion