Cloud Firestoreのbatchを使って一括書き込み

公開日時
更新日時

Cloud Firestoreを使って、写真に対するいいね機能を実装した際に以下のようにデータ設計を行った。

  • users/${authUser.uid}/like_photos としてサブコレクションを作成し、自分がいいねした写真一覧を保持
  • photos/${targetId}/like_users としてサブコレクションを作成し、写真にいいねしたユーザ一覧を保持

いいね及びいいね削除の際に、上記2つのサブコレクションに書き込みを行う。

この書き込みはアトミックな処理として行いたいので、一括書き込みができるbatchを利用した。

実装サンプル

  • firestoreにデータ書き込みを行うDBインスタンスを作成

nuxtアプリの場合、プロジェクト独自のファイルはどこに置くのがいいんだろう…

今回はsrc以下にprojectディレクトリを作成し、その下に配置することにした。

// src/project/DB.ts
import firebase from '~/plugins/firebase'
import IDB from '~/types/IDB'
import IAuthUser from '~/types/IAuthUser'

const firestore = firebase.firestore()

class DB implements IDB {
  async like(targetId: string, authUser: IAuthUser) {
    const like = {
      targetId: targetId,
      fromUid: authUser.uid,
      createdAt: firebase.firestore.FieldValue.serverTimestamp()
    }
    const batch = firestore.batch()
    const userRef = firestore.doc(`users/${authUser.uid}/like_photos/${targetId}`)
    batch.set(userRef, like)
    const photoRef = firestore.doc(`photos/${targetId}/like_users/${authUser.uid}`)
    batch.set(photoRef, like)
    await batch.commit()
  }
  async unlike(targetId: string, authUser: IAuthUser) {
    const batch = firestore.batch()
    const userRef = firestore.doc(`users/${authUser.uid}/like_photos/${targetId}`)
    batch.delete(userRef)
    const photoRef = firestore.doc(`photos/${targetId}/like_users/${authUser.uid}`)
    batch.delete(photoRef)
    await batch.commit()
  }
}

const db = new DB()
export default db
  • 写真詳細ページ

呼び出し元となる写真詳細ページで、Firestoreへの書き込みとstoreの操作を行うようにした。

// src/pages/photos/_id.vue
import db from '~/project/DB'

export default class PhotosShow extends Vue {
  // ...

  async like() {
    this.loading()
    try {
      await db.like(this.photo.id, this.authUser)  // batch書き込み
      this.$store.dispatch('authUser/like', this.photo.id)  // store操作
      this.$store.dispatch('photo/like')
    } catch (error) {
      console.error(error)
    } finally {
      this.loaded()
    }
  }

  async unlike() {
    this.loading()
    try {
      await db.unlike(this.photo.id, this.authUser)
      this.$store.dispatch('authUser/unlike', this.photo.id)
      this.$store.dispatch('photo/unlike')
    } catch (error) {
      console.error(error)
    } finally {
      this.loaded()
    }
  }
}

参考


Related #firebase

Firestoreの複合インデックスを削除する

CLI経由で削除する必要があった

Firebase Web SDK v9

_this.auth.addAuthTokenListener is not a function

8.6.5にダウングレードした

Firebase Summit 2021