こんばんは✋
@peka3 です。 ヘムロックがお気に入りです。
ブログは久しぶりに書きます。
最近は攻略ツールを開発をしております。
先日、あつ森でのアイテムを交換するためのツール「あつ森交換掲示板」を作りました。
フロントエンドは GameWithDesignSystem を使っています。 こちらについては以前の記事に詳しく載っているのでぜひ読んでみてください。
実装側としては Vue + TypeScript で Webコンポーネントを実装していくことになります。
そしてバックエンドなのですが、今回はすべて firestore で実装しました。
firestore については、RDB脳だとハマりどころが多かったので、今回はこちらを詳しく紹介できればと思います!!
まず最初に、あつ森交換掲示板の機能をざっくり紹介いたします。
よくあるスレッド型掲示板とそれほど違いはないので、読み飛ばしていただいても大丈夫です。
あつ森交換掲示板の紹介
あつ森交換掲示板 | あつまれどうぶつの森 - GameWith
一覧画面
現在の取引募集を一覧で見ることができます。 アイテムを選択して、検索をすることができます。
投稿画面
欲しいアイテム、譲れるアイテムを選択して投稿します
詳細画面
投稿主に対して、コメントすることができます。 この画面で交換のやり取りが行われます。
アイテム選択モーダル
アイテム一覧から様々な条件で絞り込み、アイテムを選択する画面です。
firestoreでのデータの持ち方
今回作ったコレクションは3つになります。
- アイテム情報
- アイテム名、サムネイル等
- 取引情報
- 欲しいアイテム、譲るアイテム、投稿ユーザーの情報等
- サブコレクションで 返信情報 も保持しています
- 削除ログ
- 規約に反した投稿を削除した際のログ
firestore での DB 設計の勘所
firestoreで大事なところとして 「なるべく1つのコレクションに画面を出力するのに必要なすべてのデータを格納する」 というのがあると思いました。
firestore には join がない
まずfirestoreはjoinがありません。
RDBですと、取引情報には、欲しいアイテムのアイテムID、譲るアイテムのアイテムIDだけ格納し、 実際に画面でアイテム名が必要になったらjoinによってアイテム名を取得してくる、という流れになると思いますが、
firestoreでそれをやろうとすると、joinがないためアイテムID一つに対して1回読み取りクエリを投げることになります。 パフォーマンス的にも料金的にも、とても非効率なります。
そのため、今回は取引情報のなかにアイテム名やサムネイルURL等、画面の描画に必要なデータをすべて盛り込みました。
firestoreの便利だったところ
https://firebase.google.com/docs/firestore/query-data/queries?hl=ja#array_membership
firestore には array-contains 演算子というものがあります。
これが今回の「欲しいアイテム」「譲るアイテム」から特定アイテムを検索する、という要件にぴったりと合致しました。
「欲しいアイテム」と「譲るアイテム」は複数設定できます。 それぞれのアイテム名を配列で持つフィールドを用意し、そこに対して array-contains で where することで、一発で検索できるようになりました。
コード例:
wishItemTradesRef = itemTradesRef.where( "items", "array-contains", selectedItemNames );
firestoreでの論理削除は非効率
firestoreでも論理削除をすることもできますが、削除フラグを持ってしまうと、削除フラグを含めた複合indexをたくさん作る必要がでてきます。 firestoreはindexも課金対象であるため、できれば避けたいです。
今回の要件として、削除された対象のログをあとから追跡できれば良かったので、削除したログを残すコレクションを別途用意し、取引情報のほうは物理削除することにしました。
firestore で、複数条件での検索、ソートが必要になったら要注意
whereによる範囲比較、orderByによるソートは、同一のフィールドを指定しないと動きません。
たとえば年齢10〜20歳の人で、かつ男性を上位に表示する、というようなクエリは発行できません。 年齢10〜20歳の人で、年齢の昇順に表示する、なら可能になります。
// これはエラーになる usersRef.where("age", ">", 10).orderBy("gender"); // これはOK usersRef.where("age", ">", 10).orderBy("age");
これは firestore 特有の制限ですね。
今回はこれを知らずに仕様を決めていたため、あとで調整が必要になりました。
要注意ポイントです。
(前述の array-contains は範囲比較に該当しないため問題なし)
以下、公式ドキュメントへのリンクです
- https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja
- https://firebase.google.com/docs/firestore/query-data/queries?hl=ja#compound_queries
firestore ではセキュリティルールを書く必要がある
firestoreのconsoleからセキュリティルールを書くことができます。
これはバリデーションのようなものであり、これを書かないと、どんな内容でもクエリを受け付けてしまいます。
セキュリティルールを書くことによって、フィールドに対して型を縛ったり、READ/WRITE制限をかけたり、このフィールドのみUPDATE可能にする、というようなこともできます。
セキュリティルールは詳しく書くとこれだけで結構なボリュームになるので割愛します。
まとめ
ちょっと要件が複雑な掲示板でしたが、 firestore のみでバックエンド問題なく実装ができました。
firestore 単体で実装がすむと、API開発をしなくてよい、APIサーバの運用を考えなくてもよいという圧倒的メリットがありますね!
ただ、利用には一癖二癖あるので、何度かfirestoreの実装を経験しないと、工数見積もりなどを正確に出すのは難しそうだと感じました。
しかしAPI開発を一切せずにこういったツールが作れるとなるとは…どんどん便利になってエンジニアとしては嬉しいかぎりですね。
firestore には未来を感じるので、これからも経験を積んでいきたいなぁと思いました。
それでは失礼します👋
GameWith Advent Calendar 2020 の他の記事もよろしくおねがいします!