10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MYJLabAdvent Calendar 2020

Day 9

過去の自分に伝えたいFirestoreの失敗と勘違い

Last updated at Posted at 2020-12-09

M2のmarutakuです。前回の記事は、@sh05_sh05によるFastAPIでの静的ファイル, index.htmlの設定(WebSocket Chatの例を直す)でした。忙しい中、カレンダーが埋まってくれなかったからと急遽書いてくれました。ありがとう!
今回は「過去の僕に伝えたいFirestoreの設計と失敗」という題でお送りします。

一年前くらいから個人的にFirebaseを使う機会が増えてきました。特に、Firestoreはデータベースを立てなくてよくなるので、とても便利です(小並感)。しかし、NoSQLということもあり慣れるまでに様々な失敗をしてきました。ということで、この記事では過去に自分がした失敗とどうすれば回避できそうかを書いていきます。まだまだ経験が浅いので、間違えがあれば教えていただきたいです。
メソッドの名前などは、JavaScriptのSDKに準拠しています。

そもそもFirestoreとは

Firestoreとは、Googleが提供しているモバイル、ウェブ、サーバー開発に対応したドキュメント指向データベースです。高機能なクエリ、リアルタイム性、無料枠の広さなどから色々な用途に用いられています。
FirebaseにはFirestore以外にも認証やストレージなどの機能も備わっているので、ぜひ使ってみてください。

やらかした失敗、勘違い

1:Nのデータを配列で持つ

文字通り、1:Nのデータをドキュメントのフィールドで配列として持っていました。
例として、本のレビューサイトを考えてみてください。本のレビューサイトでは、本に対してレビューがたくさん紐づいています。

# /books/:id
{
    "name": "...",
    "image": "..."
    "isbn": "...",
    "comments": [
        {
            "text": "...",
            "createdAt": "..."
        },
        ...
    ]
}

これをやると、単に本の一覧を持ってきたいだけなのに、全てのコメントを一緒に持ってきてしまいます。また、あとで出てくるセキュリティルールを細かく設定することもできなくなってしまいます。

解決策としては、サブコレクションを使うようにすることです。サブコレクションを用いることで、ドキュメントの下にさらにコレクションを作成することができます。先ほどの例であれば、サブコレクションを用いてこのようにすることがでます。


# /books/:id
{
    "name": "...",
    "image": "..."
    "isbn": "...",
}
# /books/:id/comments/:comment_id
{
    "text": "...",
    "createdAt": "..."
}

コレクションをまたいでクエリがしたいときは、CollectionGroupを使用して実現できます。

Firestore内でjoinしたい

RDBでは、inner joinやleft joinを用いてデータを一回のSQL文で全て持ってくることがあります。過去の自分は、Firestoreにも同じようにデータをjoinするような機能があると思っていました。

結論から言うと、そのような機能は知る限りではありません。理由としては、Firestoreではデータの結合はクライアント側で行います(クライアントサイドジョイン)。RDBに慣れていた自分はN+1問題などの理由から効率が悪そうに思いましたが、そもそもRDBではないのでそんなものかと言う感じです。

何でもかんでもリアルタイムアップデートを入手

Firestoreの特徴の一つにリアルタイム性があります。これは、Firestoreでデータの追加・更新・削除があった場合に、クライアント側に変更があったことを通知する仕組みです。自分は、Firestoreからデータを持ってくるときはリアルタイムアップデート受け取る必要があると思って、全てのデータを取得する箇所でリアルタイムアップデートを受け取っていました。

当然、Firestoreにはその時点であるデータを取得してくる機能が用意されています。CollectionReferenceDocumentReferenceの両方に.get()メソッドが存在していて、これを用いることで一度だけデータを取得することができます。

トランザクションが無いと思ってた

FirestoreにはRDBのようなトランザクションはないと思っていました。もちろんFirestoreにはトランザクションを実現するための機能が用意されています。
以下のコードでトランザクションを実現できます

const store = firebase.firestore()
const documentRef = store.collection("hoge").get("fuga")
await store.runTransaction(async (transaction) => {
    const doc = await transaction.get(documentRef)
    const vote = doc.data().vote + 1
    transaction.update(documentRef, { vote });
}).catch((e) => console.log("Error: " + e))

セキュリティ弱い

Firestoreはクライアント側でリクエストを送ったりしているので、なんやかんやでハックすればデータベースの中身を自由に覗かれてしまうと思い込んでいました。しかし、Firestoreにはセキュリティルールというセキュリティを設定する機能が存在しています。セキュリティルールを用いるとユーザごとにアクセスできるドキュメントを制限できたり、Firebase Authenticationを使ってログインしているかどうかでアクセスできる範囲を変えたりできます。

テストモードでは、指定された日にちまでは誰でも無制限にアクセスができてしまうので注意が必要です。

終わりに

過去に自分が行なった失敗や勘違いをまとめました。この記事を見て同じミスをしなくなる人がいれば幸いです。
明日もFirebase関連の記事を書く予定です。

参考にしたサイトなど

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?