概要
FirestoreはNoSQLの中でもデータ構造に特徴があります。 本記事では私自身が実際に設計したデータ構造がサブコレクションでどう変わったかをみていくことで、サブコレクションでできることを書いていきたいと思います。
開発環境
- Node (14.15.4)
- Firebase JavaScript SDK (8.2.4)
Firestoreについて
Googleが提供しているNoSQLのマネージドサービスです。 他のNoSQLと比較してWeb, Mobileからローカルデータベースのように利用できることやデフォルトで単一フィールドのインデックスが作成されていることが特徴です。
データ構造
今回はチャット機能のデータを考えてみます。 登場人物は以下になります。
- ユーザー
- チャットルーム
- 投稿(コメント、画像を含む)
チャットルームには2人以上のユーザーが参加することができます。
Before
改善する前の設計方法を書いていきます。
Posts
コレクションに Rooms
のIDを持たせてクライアントで結合していました。
特定の Rooms
に含まれる投稿を作成順に取得するときは以下のようになります。
const db = firebase.firestore() const collection = db.collection('posts') collection .where('roomID', '==', '0ff04b17-5e8f-11eb-ac15-067462cbca66') .orderBy('createdAt', 'asc') .get() .then((docs) => { docs.forEach((doc) => { const post = doc.data() // { text: 'こんにちは'... } }) })
この場合、以下のような課題がありました。
- 投稿を取得するために
roomID
およびcreatedAt
のフィールドを条件にしているため複合インデックスを作成する必要がある Posts
コレクションに対してセキュリティルールを作る情報が足りていない
1について、Firestoreはデフォルトで単一フィールドのインデックスが有効になっています。しかし、複数条件に対してはインデックスの条件ををあらかじめ指定しておく必要があります。そのため今後新しく検索したいパターンが増えるたびに複合インデックスを追加する必要があります。
2について、 Posts
コレクションはチャットルームに参加しているユーザーデータを持っていません。そのためセキュリティルール上で参加者以外閲覧不可のような設定ができなく、Rooms
に含まれるユーザーデータを複製して持たせないといけないです。複製するとユーザーが Rooms
から抜けたりしたときに、 Rooms
内の全 Posts
コレクションのユーザーデータを更新する必要があります。
これらを改善するためにサブコレクションを使いました。
After
Beforeでは Rooms
と Posts
のパスはそれぞれ /rooms
、/posts
とそれぞれ別のルートから作成していました。サブコレクションを使うとパスは /rooms/{roomID}/posts
として作成します。
改善前でも書いた Posts
コレクションを取得する処理は以下のようになります。
const db = firebase.firestore() // コレクション作成時にroomsのIDを指定 const collection = db.collection('rooms/0ff04b17-5e8f-11eb-ac15-067462cbca66/posts/') collection .orderBy('createdAt', 'asc') .get() .then((docs) => { docs.forEach((doc) => { const post = doc.data() // { text: 'こんにちは'... } }) })
Rooms
のIDがWhere文からCollectionの取得条件になっているため単一フィールドのインデックスで取得できます。
また Rooms
に適応するセキュリティルールを Posts
に反映できるので、ユーザーデータを Posts
に持たせる必要がなくなりました。
投稿の追加は以下のようにできます。
const db = firebase.firestore() const collection = db.collection('rooms/0ff04b17-5e8f-11eb-ac15-067462cbca66/posts/') const uuid = uuidv4() collection .doc(uuid) .set({ id: uuid, ownerID: currentUser.uid, text: 'はじめまして', type: 'message' })
まとめ
サブコレクションを使うことで1対多のデータ構造のインデックスおよびセキュリティルールを簡略にできました。 最初に設計したIDによる結合方法とサブコレクションはデータ構造に応じて柔軟に選択することができます。
今後、設計時にセキュリティールールやインデックスも考慮して拡張しやすい構造を作っていきたいです。