Cloud Firestore 觸發條件


有了 Cloud Functions,您就能在 Cloud Firestore 中處理事件,而無須更新用戶端程式碼。您可以透過文件快照介面或Admin SDK 變更 Cloud Firestore。

在一般生命週期中,Cloud Firestore 函式會執行下列操作:

  1. 等待對特定文件的變更。
  2. 在事件發生時觸發並執行它的工作。
  3. 接收包含儲存在指定文件中資料快照的資料物件。對於寫入或更新事件,資料物件包含兩個快照,分別代表觸發事件前後的資料狀態。

Firestore 執行個體和函式位置之間的距離可能會造成嚴重的網路延遲。為提升效能,建議您在適用情況下指定函式位置

Cloud Firestore 函式觸發條件

Cloud Functions for Firebase SDK 會匯出 functions.firestore 物件,讓您建立與特定 Cloud Firestore 事件相關聯的處理程序。

事件類型 觸發條件
onCreate 在第一次寫入文件時觸發。
onUpdate 在文件已經存在且已變更任何值時觸發。
onDelete 在刪除具有資料的文件時觸發。
onWrite 在觸發 onCreateonUpdateonDelete 時觸發。

如果您尚未針對 Cloud Functions for Firebase 啟用專案,請參閱開始使用:編寫及部署第一個函式,瞭解如何設定及設定 Cloud Functions for Firebase 專案。

編寫 Cloud Firestore 觸發函式

定義函式觸發條件

如要定義 Cloud Firestore 觸發條件,請指定文件路徑和事件類型:

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

文件路徑可以參照特定文件萬用字元模式

指定單一文件

如果想在特定文件發生「任何」變更時觸發事件,可以使用下列函式。

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

使用萬用字元指定一組文件

如果您想將觸發條件附加至一組文件 (例如特定集合中的任何文件),請使用 {wildcard} 取代文件 ID:

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

在這個範例中,當 users 中任何文件的任何欄位發生變更時,就會與名為 userId 的萬用字元相符。

如果 users 中的文件含有子集合,且其中一個子集合文件的欄位有所變更,則系統不會觸發 userId 萬用字元。

系統會從文件路徑中擷取萬用字元比對結果,並儲存在 context.params 中。您可以視需要定義任意數量的萬用字元,用於替換明確的集合或文件 ID,例如:

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

事件觸發條件

在建立新文件時觸發函式

您可以使用 onCreate() 處理常式搭配萬用字元,在每次在集合中建立新文件時觸發函式。這個範例函式會在每次新增使用者設定檔時呼叫 createUser

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

在文件更新時觸發函式

您也可以使用 onUpdate() 函式搭配萬用字元,在文件更新時觸發函式。以下範例函式會在使用者變更設定檔時呼叫 updateUser

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

在刪除文件時觸發函式

您也可以在刪除文件時,使用 onDelete() 函式搭配萬用字元觸發函式。當使用者刪除個人資料時,這個範例函式會呼叫 deleteUser

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

針對文件的所有變更觸發函式

如果您不在意觸發的事件類型,可以使用 onWrite() 函式搭配萬用字元來監聽 Cloud Firestore 文件中的所有變更。這個範例函式會在建立、更新或刪除使用者時呼叫 modifyUser

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

讀取及寫入資料

觸發函式時,系統會提供事件相關資料的快照。您可以使用這個快照,讀取或寫入觸發事件的文件,也可以使用 Firebase Admin SDK 存取資料庫的其他部分。

事件資料

讀取資料

函式觸發時,您可能會想從已更新的文件中取得資料,或取得更新前的資料。您可以使用 change.before.data() 取得先前的資料,其中包含更新前的文件快照。同樣地,change.after.data() 會包含更新後的文件快照狀態。

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

您可以像存取任何其他物件一樣存取屬性。或者,您也可以使用 get 函式存取特定欄位:

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

寫入資料

每個函式叫用都會與 Cloud Firestore 資料庫中的特定文件相關聯。您可以在傳回函式的快照的 ref 屬性中,以 DocumentReference 的形式存取該文件。

這個 DocumentReference 來自 Cloud Firestore Node.js SDK,其中包含 update()set()remove() 等方法,方便您輕鬆修改觸發函式的文件。

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.before.data();

      // We'll only update if the name has changed.
      // This is crucial to prevent infinite loops.
      if (data.name == previousData.name) {
        return null;
      }

      // Retrieve the current count of name changes
      let count = data.name_change_count;
      if (!count) {
        count = 0;
      }

      // Then return a promise of a set operation to update the count
      return change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

觸發事件以外的資料

Cloud Functions 會在可信任的環境中執行,也就是說,這些工作會在專案中以服務帳戶的身分授權。您可以使用 Firebase Admin SDK 執行讀取和寫入作業:

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

限制

請注意,Cloud FunctionsCloud Firestore 觸發條件有下列限制:

  • Cloud Functions (第 1 代) 必須在 Firestore 原生模式中使用現有的「(預設)」資料庫。不支援 Cloud Firestore 命名資料庫或 Datastore 模式。在這種情況下,請使用 Cloud Functions (第 2 代) 設定事件。
  • 我們不保證排序。快速變更可能會以非預期的順序觸發函式叫用。
  • 事件至少會傳送一次,但單一事件可能會導致多次函式叫用。避免依賴「一次一律」機制,並編寫冪等函式
  • Cloud Firestore 在 Datastore 模式下需要 Cloud Functions (第 2 代)。Cloud Functions (第 1 代) 不支援 Datastore 模式。
  • 觸發條件會與單一資料庫相關聯。您無法建立可比對多個資料庫的觸發條件。
  • 刪除資料庫不會自動刪除該資料庫的任何觸發條件。觸發條件會停止傳送事件,但在您刪除觸發條件之前,仍會繼續存在。
  • 如果相符的事件超過最大要求大小,事件可能不會傳送至 Cloud Functions (第 1 代)。
    • 由於要求大小而未送達的事件會記錄在平台記錄中,並計入專案的記錄用量。
    • 您可以在「Logs Explorer」中找到這些記錄,其中的訊息為「Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen...」(由於大小超出第 1 代限制,事件無法傳送至 Cloud 函式),嚴重性為 error。您可以在 functionName 欄位下方找到函式名稱。如果 receiveTimestamp 欄位仍在現在起算一小時內,您可以透過時間戳記前後的快照,讀取相關文件,進而推斷實際事件內容。
    • 如要避免這種節奏,您可以採取下列做法:
      • 遷移並升級至 Cloud Functions (第 2 代)
      • 縮小文件大小
      • 刪除問題中的 Cloud Functions
    • 您可以使用排除項目關閉記錄功能,但請注意,系統仍不會傳送違規事件。