Google Cloud Firestore 觸發條件 (第 1 代)
Cloud Run 函式可以處理與函式位於同一個 Google Cloud 專案中的 Firestore 事件。您可以使用 Firestore API 和用戶端程式庫,讀取或更新 Firestore,以回應這些事件。
在一般生命週期中,Firestore 函式會執行下列動作:
等待對特定文件的變更。
在事件發生時觸發並執行它的工作。
接收含有受影響文件快照的資料物件。如果是
write
或update
事件,資料物件會包含代表觸發事件前後文件狀態的快照。
事件類型
Firestore 支援 create
、update
、delete
和 write
事件。write
事件涵蓋對文件進行的所有修改。
事件類型 | 觸發條件 |
---|---|
providers/cloud.firestore/eventTypes/document.create (預設) |
在第一次寫入文件時觸發。 |
providers/cloud.firestore/eventTypes/document.update |
在文件已經存在且已變更任何值時觸發。 |
providers/cloud.firestore/eventTypes/document.delete |
在刪除具有資料的文件時觸發。 |
providers/cloud.firestore/eventTypes/document.write |
在建立、更新或刪除文件時觸發。 |
萬用字元會以大括號寫入觸發條件,如下所示:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"
指定文件路徑
如要觸發函式,請指定要監聽的文件路徑。函式只會回應文件變更,無法監控特定欄位或集合。以下是幾個有效的文件路徑範例:
users/marie
:有效觸發條件。監控單一文件/users/marie
。users/{username}
:有效觸發條件。監控所有使用者文件。萬用字元用於監控集合中的所有文件。users/{username}/addresses
:無效的觸發條件。是指子集合addresses
,而非文件。users/{username}/addresses/home
:有效觸發條件。監控所有使用者的住家地址文件。users/{username}/addresses/{addressId}
:有效觸發條件。監控所有地址文件。
使用萬用字元與參數
如果您不知道要監控的特定文件,請使用 {wildcard}
,而非文件 ID:
users/{username}
會監聽所有使用者文件的變更。
在這個範例中,當 users
中任何文件的任何欄位發生變更時,系統會比對名為 {username}
的萬用字元。
如果 users
中的文件有子集合,且其中一個子集合文件的欄位有所變更,系統不會觸發 {username}
萬用字元。
系統會從文件路徑擷取萬用字元相符項目。 您可以定義任意數量的萬用字元,取代明確的集合或文件 ID。
事件結構
這個觸發條件會使用類似下方的事件叫用函式:
{ "oldValue": { // Update and Delete operations only A Document object containing a pre-operation document snapshot }, "updateMask": { // Update operations only A DocumentMask object that lists changed fields. }, "value": { // A Document object containing a post-operation document snapshot } }
每個 Document
物件都包含一或多個 Value
物件。如需類型參照,請參閱 Value
說明文件。如果您使用型別語言 (例如 Go) 編寫函式,這項功能就特別實用。
程式碼範例
下列範例 Cloud 函式會列印觸發 Cloud Firestore 事件的欄位:
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.Firestore.V1; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace FirebaseFirestore; public class Function : ICloudEventFunction<DocumentEventData> { private readonly ILogger _logger; public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken) { _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject); _logger.LogInformation("Event type: {type}", cloudEvent.Type); MaybeLogDocument("Old value", data.OldValue); MaybeLogDocument("New value", data.Value); // In this example, we don't need to perform any asynchronous operations, so the // method doesn't need to be declared async. return Task.CompletedTask; } /// <summary> /// Logs the names and values of the fields in a document in a very simplistic way. /// </summary> private void MaybeLogDocument(string message, Document document) { if (document is null) { return; } // ConvertFields converts the Firestore representation into a .NET-friendly // representation. IReadOnlyDictionary<string, object> fields = document.ConvertFields(); var fieldNamesAndTypes = fields .OrderBy(pair => pair.Key) .Select(pair => $"{pair.Key}: {pair.Value}"); _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes)); } }
Ruby
PHP
use Google\CloudFunctions\CloudEvent; function firebaseFirestore(CloudEvent $cloudevent) { $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL); fwrite($log, 'Event Type: ' . $cloudevent->getType() . PHP_EOL); $data = $cloudevent->getData(); $resource = $data['resource']; fwrite($log, 'Function triggered by event on: ' . $resource . PHP_EOL); if (isset($data['oldValue'])) { fwrite($log, 'Old value: ' . json_encode($data['oldValue']) . PHP_EOL); } if (isset($data['value'])) { fwrite($log, 'New value: ' . json_encode($data['value']) . PHP_EOL); } }
以下範例會擷取使用者新增的值、將該位置的字串轉換為大寫,並以大寫字串取代該值:
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Firestore; using Google.Cloud.Functions.Framework; using Google.Cloud.Functions.Hosting; using Google.Events.Protobuf.Cloud.Firestore.V1; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace FirestoreReactive; public class Startup : FunctionsStartup { public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) => services.AddSingleton(FirestoreDb.Create()); } // Register the startup class to provide the Firestore dependency. [FunctionsStartup(typeof(Startup))] public class Function : ICloudEventFunction<DocumentEventData> { private readonly ILogger _logger; private readonly FirestoreDb _firestoreDb; public Function(ILogger<Function> logger, FirestoreDb firestoreDb) => (_logger, _firestoreDb) = (logger, firestoreDb); public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken) { // Get the recently-written value. This expression will result in a null value // if any of the following is true: // - The event doesn't contain a "new" document // - The value doesn't contain a field called "original" // - The "original" field isn't a string string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string; if (currentValue is null) { _logger.LogWarning($"Event did not contain a suitable document"); return; } string newValue = currentValue.ToUpperInvariant(); if (newValue == currentValue) { _logger.LogInformation("Value is already upper-cased; no replacement necessary"); return; } // The CloudEvent subject is "documents/x/y/...". // The Firestore SDK FirestoreDb.Document method expects a reference relative to // "documents" (so just the "x/y/..." part). This may be simplified over time. if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/")) { _logger.LogWarning("CloudEvent subject is not a document reference."); return; } string documentPath = cloudEvent.Subject.Substring("documents/".Length); _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath); await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken); } }
Ruby
PHP
use Google\Cloud\Firestore\FirestoreClient; use Google\CloudFunctions\CloudEvent; function firebaseReactive(CloudEvent $cloudevent) { $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); $data = $cloudevent->getData(); $resource = $data['value']['name']; $db = new FirestoreClient(); $docPath = explode('/documents/', $resource)[1]; $affectedDoc = $db->document($docPath); $curValue = $data['value']['fields']['original']['stringValue']; $newValue = strtoupper($curValue); if ($curValue !== $newValue) { fwrite($log, 'Replacing value: ' . $curValue . ' --> ' . $newValue . PHP_EOL); $affectedDoc->set(['original' => $newValue]); } else { // Value is already upper-case // Don't perform another write (it might cause an infinite loop) fwrite($log, 'Value is already upper-case.' . PHP_EOL); } }
部署函式
下列 gcloud
指令會部署函式,在文件 /messages/{pushId}
發生寫入事件時觸發函式:
gcloud functions deploy FUNCTION_NAME \ --no-gen2 \ --entry-point ENTRY_POINT \ --runtime RUNTIME \ --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID \ --trigger-event "providers/cloud.firestore/eventTypes/document.write" \ --trigger-resource "projects/YOUR_PROJECT_ID/databases/(default)/documents/messages/{pushId}"
引數 | 說明 |
---|---|
FUNCTION_NAME |
您要部署的 Cloud 函式註冊名稱。
這可以是原始碼中的函式名稱,也可以是任意字串。如果 FUNCTION_NAME 是任意字串,則必須加入 --entry-point 旗標。
|
--entry-point ENTRY_POINT |
原始碼中的函式或類別名稱。除非您未使用 FUNCTION_NAME 在原始碼中指定要在部署期間執行的函式,否則這個欄位為選填。在這種情況下,您必須使用 --entry-point 提供可執行函式的名稱。
|
--runtime RUNTIME |
您使用的執行階段名稱。如需完整清單,請參閱 gcloud 參考資料。 |
--set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID |
專案的唯一 ID,以執行階段環境變數的形式提供。 |
--trigger-event NAME |
函式要監控的事件類型 (write 、create 、update 或 delete )。 |
--trigger-resource NAME |
函式將監聽的完整資料庫路徑。
格式應如下所示:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/PATH"
{pushId} 文字是上文「指定文件路徑」所述的萬用字元參數。
|
限制
請注意,Cloud Run 函式的 Firestore 觸發條件有下列限制:
- 如要使用 Cloud Run 函式 (第 1 代),必須先在 Firestore 原生模式中建立「(default)」資料庫。不支援 Firestore 已命名的資料庫或 Datastore 模式。在這種情況下,請使用 Cloud Run functions (第 2 代) 設定事件。
- 我們不保證排序。快速變更可能會以非預期的順序觸發函式呼叫。
- 系統至少會傳送一次事件,但單一事件可能會導致多次函式叫用。請避免依附於「只執行一次」機制,並編寫等冪函式。
- Firestore (Datastore 模式) 需要 Cloud Run 函式 (第 2 代)。Cloud Run 函式 (第 1 代) 不支援 Datastore 模式。
- 觸發條件會與單一資料庫建立關聯。您無法建立與多個資料庫相符的觸發程序。
- 刪除資料庫時,系統不會自動刪除該資料庫的任何觸發程序。觸發條件會停止傳送事件,但會繼續存在,直到您刪除觸發條件為止。
- 如果相符事件超過要求大小上限,事件可能無法傳送至 Cloud Run 函式 (第 1 代)。
- 如果事件因要求大小而未傳送,系統會記錄在平台記錄中, 並計入專案的記錄檔用量。
- 您可以在記錄檔探索器中找到這些記錄,訊息為「Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen...」(事件無法傳送至 Cloud 函式,因為大小超出第 1 代的限制...),嚴重程度為
error
。您可以在「functionName
」欄位下方找到函式名稱。如果receiveTimestamp
欄位仍在一小時內,您可以讀取時間戳記前後的快照,推斷實際活動內容。 - 如要避免這種情況,可以採取下列做法:
- 遷移及升級至 Cloud Run functions (第 2 代)
- 縮小文件
- 刪除有問題的 Cloud Run 函式
- 您可以使用排除條件關閉記錄功能,但請注意,違規事件仍不會傳送。