ご挨拶
ども!弊社もアドベントカレンダーを開催中です。タグから「2024アドベントカレンダー」を選んでもらえれば今年分が覗けるかと思います。温泉行幸旅行から帰宅して、肌つやが最高によい龍ちゃんです。
さて、今回は頭の整理も含めて、Firestoreについて整理をしていこうと思います。Firestoreは個人開発の強い味方だと思っています。フロントエンドエンジニアからすると、手軽に認証とデータベースを追加できるという二点において神ツールです。実際に僕がプロト開発でDBを扱う場合だと、真っ先に選定されます。
今回の記事は、こんな方に読んでほしいです!
- firestoreのデータ構造がいまいちつかめない
- Firestoreを触ったことないけど、どんなもんなん?
- サブコレクションを使いだしたら混乱してきた
では、失敗談も含めてお話していきたいと思います。
僕がしていた勘違いとミス
まずは、僕がしていた勘違いとミスについて書いておこうと思います。
おそらく、クスっと笑ってもらえたかと思います。そんなことあるわけないですよね?やはり導入の手ごろさが癖になって、どんどんノリで開発をしていていました。そして、クエリ混沌を生み出していたわけです。
ここからは反省して改心した僕が書いています。
実際にRDBよりは、ガチガチとした設計を必要としません。特に型定義は、ソースコードで挿入する際に入ってきたデータで構造が決定されます。クエリを使用する場合では、実現したいシナリオにデータ構造を寄せておいた方が良いです。シナリオベースでデータ構造を決める作業を一つ設計として導入するべきですね。
ちょうど、今日の業務内容のメモにあったので共有しておきます。
この整理があるのとないのでは、だいぶ違います。
さて….ここまではポエムなので、そろそろ本題に入っていきます。
ここから本題:Firestoreの図解
Firestoreのイメージは、公式だと以下のイメージで図解されています。
確かに端的に表現しており、良い図ですね。Firestoreを使えるようになってから見ると、この図のイメージで問題はありません。はい!「使えるようになってから」って部分が難しいところですよね。ここに落とし穴があるんです。これだけでも理解できる方向けに、各項目の説明をラフに書いておきます。
- コレクション:フォルダのようなイメージで、ドキュメントを複数保存する
- ドキュメント:ファイルのようなイメージで、データまたはフォルダを保存する
- データ:key-valueで値を保存する
では、図解していきます。
図解:ルートフォルダとコレクションの関係
まずは、一番トップのイメージになります。実際の画面だと、【コレクションを開始】と書かれている列ですね。
これは、【ディレクトリしか保存することができないディレクトリ】が一番イメージが近いと思います。
すべてはコレクションから始まります。すべてを飛ばしてドキュメントを作成することはできないことを押さえておきましょう。
図解:コレクション内部にあるドキュメント
次にコレクションの内部に保存するドキュメントとデータについて図解していきます。画面だとこの部分です。
先ほどの資料では、ドキュメントは一枚の紙のイラスト(もしくはGoogle Doc)で表現されていました。ここが落とし穴です。ドキュメントは、関連してサブコレクションを持つことができます。つまり、一枚のドキュメントと関連するフォルダーからなるグループ群がドキュメントとなります。
ドキュメントでは、サブコレクションとフィールド(データ)を保存することができます。フィールドでは、Key-Value型で情報を保存することができます。サブコレクションの内部では、また同一にこの世界が広がっていきます。
単一の構造で済む場合は、ドキュメントに情報を保存することで公式が出しているスタイルで済みます。ですが、サブコレクションを導入した途端に複雑な構造になってしまいます。
データの取り出し方(クエリを書く前)
ドキュメントやデータの検索の方法について解説しておきます。基本的にDBが欲しい時は、データを保存したい時ですよね。(これは当たり前か)
データの特定方法は、ざっくりと三通り紹介します。
- 単一ドキュメント:ドキュメントのIDを指定して取得
- 複数ドキュメント:コレクションを指定して一括取得
- 複数ドキュメント:コレクショングループを指定して一括取得
各内容について図解していきます。
単一ドキュメント:ドキュメントIDを指定して取得
取得するドキュメントに関する情報がすべてわかっている場合では、こちらの方法を使用して取得することができます。
単一ドキュメントの取得は、どの位置のドキュメントか特定する必要があります。一階層の場合は、コレクションIDとドキュメントIDの二つでドキュメントを取得することができます。
サブコレクション内に存在するドキュメントの場合は、大元のコレクションからすべてのパスを取得する必要があります。二階層の場合は、コレクションIDとドキュメントIDを用いて、所属するサブコレクションを特定します。サブコレクションIDとドキュメントIDを用いてドキュメントを取得することができます。
この手法では、階層が深くなるほどドキュメント取得に必要な情報が多くなります。
複数ドキュメント:コレクションを指定して一括取得
こちらは、複数ドキュメントを取得・検索するときに使用します。単一階層の場合では、コレクションIDと条件(where)を指定しない場合は、所属するドキュメントすべてを取得します。サブコレクションを対象とする倍は、コレクションIDとドキュメントID・サブコレクションIDの三つが必要になります。
ドキュメント取得には、条件や並び替えを指定することができます。ドキュメントに含まれるデータによって検索することができます。こちらの検索条件を含めてクエリと呼んでいますが、クエリの条件に関しては、次の章で解説を入れておきます。
複数ドキュメント:コレクショングループを指定して一括取得
こちらも、複数ドキュメントを取得・検索するときに使用します。先ほどの『コレクションを指定して一括取得』とほぼ同等ですが、コレクションの指定の仕方が異なります。コレクショングループを指定して取得します。
こちらは、シナリオベースで理解するほうが早いです。コレクションIDは自動でIDを振る方法と自分で任意の名前を付ける方法があります。「ユーザーコレクション内にそれぞれTODOを保存しているサブコレクション」があると仮定します。先ほどの方法では、何度もクエリを叩く必要があります。取得のイメージは以下になります。
コレクショングループで、『todo』を指定します。すべてのドキュメントのサブコレクション(todo)からドキュメントを取得します。一致するコレクションIDすべてから、ドキュメント群をすべて取得します。
もし、ほかのコレクションやサブコレクションにtodoがあった場合は、そこからも情報が取得されます。そのため、コレクショングループを使用する場合は、コレクションIDを付けるのは注意して行いましょう。
クエリの制限
基本的な情報はすべて公式リファレンスに記載があります。開発の際に詰まった内容としては、コレクションに対しては、単純にwhere句を使用することができます。ですが、コレクショングループを使用する場合は、indexを作成する必要があります。
エラーメッセージが出てきて、URLが出てきた場合はアクセスするだけでビルドしてくれます。3~10分程度待てばビルドされるのでおとなしく待ちましょう。
Firestoreでは配列をそのままデータとして保存することができます。配列に対しては、[array-contains/array-contains-any/in/not-in](ここです)といったメソッドを使用することができます。
こちらのメソッドすべてに共通することですが、配列にはオブジェクトを保存することができます。検索系のメソッドの場合はオブジェクトが完全一致するなどの処理でしかできません。いい説明が思いつかないので、コードで表現してみます。
const string_array = ["A","B","C","D","E"] as string []
// Aを検索
// string_arrayには含まれる
const object_array = [{label:"A", value:1},{label:"B", value:2}, {label:"C", value:3}]
// {label "A"}といった検索はできない
// objectの場合は、すべての情報が一致しなければいけない
そのため、オブジェクト配列を使用する際は考えて実装する必要があります。
また、配列の長さに関してはクエリを発行することができません。解決策としては、こちらの情報が参考になります。日本語訳にすると、配列の長さを保存するKey-Valueを保存してそちらをキーにして情報を精査します。
どのように開発していくと良いか?
とりあえず必要な話は、全て図解しました。自分の失敗談を含めて、今後開発をするためにはどのような手順を踏むべきかについて書いていこうと思います。
- 実現したい内容をシナリオベースで言語化する
- 仕様が変わるときは、DBの構造自体を変更することを厭わない
- 色々なシナリオで失敗パターンを経験しておく
一番大事なのは、『シナリオ化』になります。詳細に言語化することが一番大切だと思います。このシナリオ化の程度は、メモレベルでも十分だと思います。コードレベルで構造を決定することもあり、走り出して作り上げることが可能です。その一方で、仕様変更に耐えることができるのか?という疑問があります。経験則ですが、勢いで作り上げたFirestoreは特殊な構造になったいると思います。後から、構造や仕様を変更しようとすると迷子になるのでメモは取っておきましょう。
次の『仕様が変わるときは、DBの構造自体を変更することも厭わない』と『いろいろなシナリオで失敗パターンを経験しておく』は関係深いと思います。Firestoreでは、できること・できないこと・できるけど実装しにくいことの三つがあります。そういったパターンを網羅することは、難しいですし、自分の実装パターンを持っておくことが大切だと思います。先ほども「作り上げることができる」とは書きましたが、これにはできるけど実装しにくいことが含まれる場合は、いっそのことDBを一から構築しなおしましょう。案外、構造を変えることですっきりするかもしれません。
終わり
最後まで読んでいただき、ありがとうございます。本記事がFirestoreの理解の一助になれば幸いです。特に個人開発やプロトタイプ開発では、Firestoreの柔軟性と使いやすさは大きな武器となります。
次回は、実際のプロジェクトでの具体的な実装例についても触れていければと思います。もし質問やご意見がありましたら、コメント欄でお待ちしています。
それでは、また次回の記事でお会いしましょう!