LoginSignup
3
2
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

Cloud FunctionでFirestoreのコレクションとサブコレクションをまとめて削除する方法

Posted at

背景

あるドキュメントが削除された際に、そのドキュメント配下にあるサブコレクションは残ってしまうという問題に直面した。

原因

Firestoreでは、ドキュメントを削除しても、そのドキュメント配下のサブコレクションは自動的には削除されない。
これは、特にドキュメントがサブコレクションを多く持っている場合、手動で一つずつ削除するのは非効率的で面倒な作業。

やること

ドキュメントが削除された際に、そのドキュメント配下にあるサブコレクションもまとめて削除できるようにする。

解決案

Firebase Admin SDKを使用して、ドキュメントとそのサブコレクションを一括で削除する方法を採用。
Admin SDKにはrecursiveDeleteメソッドがあり、これを使用することで指定したドキュメントとそのサブコレクションを再帰的に削除することができる。

実装 Admin SDKで再帰削除する

最初、firebase-toolsを使った方法で試みたが、tokenの問題などに引っかかったり、その方法では有効期限切れも気にしないといけない。(ドキュメント

それが嫌だったので、代わりにAdmin SDKを使う方法を試みた。(ドキュメント

完成したコードが以下。

typescript
import * as functions from "firebase-functions/v1";
import * as FirebaseAdmin from "firebase-admin";
import * as logger from "firebase-functions/logger";

FirebaseAdmin.initializeApp();
const db = FirebaseAdmin.firestore();

export const onDeleteDocuments = functions
  .region("asia-northeast1") // regionの指定が必要ないなら、この行はいらない
  .firestore.document("collections/{collectionId}/subcollections/{subcollectionId}") // トリガーとなるドキュメント
  .onDelete(async (snapshot, context) => {
    const path = snapshot.ref.path; // 削除されたcollectionのpathを取得
    try {
      const docRef = db.doc(path);
      await db.recursiveDelete(docRef); // Admin SDKで再帰削除できる
      logger.log(`Successfully deleted collection and subcollection`);
    } catch (err) {
      logger.error(`Error deleting collection and subcollection: `, err);
    }
  });

ポイントとしては、以下があれば再帰削除ができるっぽい。

typescript
FirebaseAdmin.initializeApp();
const db = FirebaseAdmin.firestore();
// 中略
  const docRef = db.doc(path);
  await db.recursiveDelete(docRef);

コード解説

Firebase Admin SDKの初期化

typescript
FirebaseAdmin.initializeApp();

はFirebase Admin SDKを初期化し、FirestoreなどのFirebaseのサービスにアクセスする準備みたいな感じ。

Firestoreインスタンスの作成

typescript
const db = FirebaseAdmin.firestore();

これでFirestoreのインスタンスを作成。これにより、Firestoreのデータにアクセスできる。

Cloud Functionsでのトリガー設定

特定のFirestoreドキュメントが削除されたときにトリガーされるCloud Functionを設定。

typescript
.firestore.document("collections/{collectionId}/subcollections/{subcollectionId}")
  .onDelete(async (snapshot, context) => {
    // ...
  });

この関数は、指定されたドキュメントパス(ここでは "collections/{collectionId}/subcollections/{subcollectionId}")にあるドキュメントが削除されると自動的に呼び出される。

削除されたドキュメントのパスの取得と再帰削除

削除されたドキュメントのパスを取得し、そのパスに基づいてドキュメントとそのサブコレクションを再帰的に削除。

typescript
const path = snapshot.ref.path; // 削除されたドキュメントのパスを取得
try {
  const docRef = db.doc(path);
  await db.recursiveDelete(docRef); // 再帰的に削除
  logger.log(`Successfully deleted collection and subcollection`);
} catch (err) {
  logger.error(`Error deleting collection and subcollection: `, err);
}

recursiveDelete メソッドは、指定されたドキュメントとその下にあるすべてのサブコレクションを削除する。

お疲れ様でした。

この方法により、Firestoreのデータ構造における親子関係を持つデータの整合性を維持しつつ、不要になったデータを効率的に削除できると思う。
また、この処理はサーバーサイドで実行されるため、クライアント側の負荷を軽減できる利点もある。

わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。

3
2
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
3
2