使用 Cloud Functions 擴充 Firestore
透過 Cloud Functions,您可以部署 Node.js 程式碼,處理因 Firestore 資料庫變更而觸發的事件。這樣一來,您就能輕鬆將伺服器端功能加入應用程式,不必自行執行伺服器。
如需使用案例範例,請參閱「我可以使用 Cloud Functions 執行哪些作業?」或 Functions Samples GitHub 存放區。
版本和模式需求
本文適用於原生模式的 Firestore Standard 版。
Firestore Enterprise 版不支援 Cloud Functions (第 1 代)。請改用 Cloud Run functions (第 2 代)。
Firestore 函式觸發條件
Cloud Functions for Firebase SDK 會匯出 functions.firestore 物件,方便您建立與特定 Firestore 事件相關聯的處理常式。
| 事件類型 | 觸發條件 |
|---|---|
onCreate |
在第一次寫入文件時觸發。 |
onUpdate |
在文件已經存在且已變更任何值時觸發。 |
onDelete |
在刪除具有資料的文件時觸發。 |
onWrite |
在觸發 onCreate、onUpdate 或 onDelete 時觸發。 |
如果尚未啟用 Cloud Functions for Firebase 專案,請參閱「開始使用:編寫及部署第一個函式」,瞭解如何設定 Cloud Functions for Firebase 專案。
編寫 Firestore 觸發的函式
定義函式觸發條件
如要定義 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() 函式搭配萬用字元,監聽 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');
寫入資料
每次函式呼叫都會與 Firestore 資料庫中的特定文件建立關聯。您可以透過傳回函式的快照 ref 屬性,以 DocumentReference 形式存取該文件。
這個 DocumentReference 來自 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 Run functions 的 Firestore 觸發條件有下列限制:
- Cloud Run functions (第 1 代) 僅支援 Firestore 原生模式下既有的「(default)」資料庫,不支援 Firestore 的具名資料庫或 Datastore 模式,在後者的情況下,請改用 Cloud Run functions (第 2 代) 設定事件。
- 目前不支援跨專案設定 Cloud Run functions 和 Firestore 觸發條件。如要設定 Firestore 觸發條件,Cloud Run functions 必須位於同一個專案中。
- 函式的叫用並無一定順序。在快速變更的情形下,可能會以非預期的順序觸發函式叫用。
- 事件至少會傳送一次,但單一事件可能會導致多次函式叫用。請避免依賴「恰好一次」的機制,並務必編寫冪等函式。
- Datastore 模式下的 Firestore 需要 Cloud Run functions (第 2 代)。Cloud Run functions (第 1 代) 不支援 Datastore 模式。
- 每項觸發條件僅能關聯至單一資料庫,無法建立同時比對多個資料庫的觸發條件。
- 刪除資料庫時,相關觸發條件並不自動隨之刪除,僅會停止傳送事件,並持續保留到手動刪除為止。
- 如果比對符合事件超過要求大小上限,事件可能無法傳送至 Cloud Run functions (第 1 代)。
- 如果事件因要求大小而未能傳送,這些事件會記錄於平台記錄檔,並計入專案的記錄用量。
- 這些記錄位於 Logs Explorer 中,訊息為「Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen...」(因事件大小超出第一代限制,無法傳遞至 Cloud 函式),嚴重程度為
error。函式名稱位於functionName欄位下方。如果receiveTimestamp欄位的時間戳記仍在一小時內,則可讀取該時間點前後的相關文件快照,推斷實際的事件內容。 - 如要避免此情況,建議方法如下:
- 遷移並升級至 Cloud Run functions (第 2 代)
- 縮減文件大小
- 刪除受影響的 Cloud Run functions
- 您可以使用排除條件關閉記錄功能,但請注意,超出限制的事件仍不會傳送。