Trigger di Google Cloud Firestore (1ª generazione.)
Le funzioni Cloud Run possono gestire gli eventi in Firestore nello stesso Google Cloud progetto della funzione. Puoi leggere o aggiornare Firestore in risposta a questi eventi utilizzando le API e le librerie client di Firestore.
In un ciclo di vita tipico, una funzione Firestore esegue le seguenti operazioni:
Attende le modifiche a un determinato documento.
Si attiva quando si verifica un evento ed esegue le relative attività.
Riceve un oggetto dati con uno snapshot del documento interessato. Per gli eventi
write
oupdate
, l'oggetto dati contiene istantanee che rappresentano lo stato del documento prima e dopo l'evento di attivazione.
Tipi di evento
Firestore supporta gli eventi create
, update
, delete
e write
. L'evento
write
include tutte le modifiche a un documento.
Tipo di evento | Trigger |
---|---|
providers/cloud.firestore/eventTypes/document.create (valore predefinito) |
Si attiva quando viene scritto per la prima volta in un documento. |
providers/cloud.firestore/eventTypes/document.update |
Viene attivato quando esiste già un documento e un valore è stato modificato. |
providers/cloud.firestore/eventTypes/document.delete |
Viene attivato quando viene eliminato un documento con dati. |
providers/cloud.firestore/eventTypes/document.write |
Viene attivato quando un documento viene creato, aggiornato o eliminato. |
I caratteri jolly vengono scritti negli attivatori utilizzando le parentesi graffe, come segue:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"
Specifica il percorso del documento
Per attivare la funzione, specifica un percorso del documento da monitorare. Le funzioni rispondono solo alle modifiche del documento e non possono monitorare campi o collezioni specifici. Di seguito sono riportati alcuni esempi di percorsi dei documenti validi:
users/marie
: trigger valido. Monitora un singolo documento,/users/marie
.users/{username}
: trigger valido. Monitora tutti i documenti dell'utente. I caratteri jolly vengono utilizzati per monitorare tutti i documenti della raccolta.users/{username}/addresses
: attivatore non valido. Si riferisce alla sottoraccoltaaddresses
, non a un documento.users/{username}/addresses/home
: trigger valido. Monitora il documento dell'indirizzo di casa per tutti gli utenti.users/{username}/addresses/{addressId}
: trigger valido. Monitora tutti i documenti relativi all'indirizzo.
Utilizzo di caratteri jolly e parametri
Se non conosci il documento specifico che vuoi monitorare, utilizza un {wildcard}
instead of the document ID:
users/{username}
rileva le modifiche a tutti i documenti dell'utente.
In questo esempio, quando un campo di qualsiasi documento in users
viene modificato, corrisponde a un carattere jolly chiamato {username}
.
Se un documento in users
ha
sottocollezioni e un
campo in uno dei documenti di queste sottocollezioni viene modificato, il carattere jolly {username}
non viene attivato.
Le corrispondenze con caratteri jolly vengono estratte dai percorsi dei documenti. Puoi definire tutti i caratteri jolly che vuoi per sostituire ID raccolta o documento espliciti.
Struttura dell'evento
Questo attivatore invoca la funzione con un evento simile a quello mostrato di seguito:
{ "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 } }
Ogni oggetto Document
contiene uno o più oggetti Value
. Per i riferimenti ai tipi, consulta la documentazione di Value
. Questo è particolarmente utile se utilizzi un linguaggio a tipi
(come Go) per scrivere le funzioni.
Esempio di codice
La Cloud Function di esempio riportata di seguito stampa i campi di un evento Cloud Firestore di attivazione:
Node.js
Python
Vai
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); } }
L'esempio seguente recupera il valore aggiunto dall'utente, converte la stringa in quella posizione in maiuscolo e sostituisce il valore con la stringa in maiuscolo:
Node.js
Python
Vai
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); } }
Eseguire il deployment della funzione
Il seguente comando gcloud
esegue il deployment di una funzione attivata
da eventi di scrittura sul documento /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}"
Argomento | Descrizione |
---|---|
FUNCTION_NAME |
Il nome registrato della Cloud Function di cui stai eseguendo il deployment.
Può essere il nome di una funzione nel
codice sorgente o una stringa arbitraria. Se FUNCTION_NAME è una
stringa arbitraria, devi includere il
flag --entry-point .
|
--entry-point ENTRY_POINT |
Il nome di una funzione o di una classe nel codice sorgente. Facoltativo, a meno che
non abbia utilizzato FUNCTION_NAME
per specificare la
funzione nel codice sorgente da eseguire durante il deployment. In questo
caso, devi utilizzare --entry-point per fornire il nome della
funzione eseguibile.
|
--runtime RUNTIME |
Il nome del runtime in uso. Per un elenco completo, consulta il
riferimento gcloud .
|
--set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID |
L'identificatore univoco del progetto come variabile di ambiente di runtime. |
--trigger-event NAME |
Il tipo di evento che la funzione monitorerà (uno tra
write , create , update o
delete ).
|
--trigger-resource NAME |
Il percorso completo del database a cui la funzione sarà in ascolto.
Deve essere conforme al seguente formato:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/PATH"
Il testo {pushId} è un parametro jolly descritto sopra
in Specificare il percorso del documento.
|
Limitazioni
Tieni presenti le seguenti limitazioni per gli trigger Firestore per le funzioni Cloud Run:
- Le funzioni Cloud Run (1ª generazione.) richiedono un database "(default)" esistente in modalità nativa di Firestore. Non supporta i database Firestore con nome o la modalità Datastore. Utilizza Cloud Run Functions (2ª generazione.) per configurare gli eventi in questi casi.
- L'ordine non è garantito. Le variazioni rapide possono attivare le chiamate di funzione in un ordine imprevisto.
- Gli eventi vengono inviati almeno una volta, ma un singolo evento può comportare più chiamate di funzione. Evita di fare affidamento su meccanismi di esecuzione esattamente una volta e scrivi funzioni idempotenti.
- Firestore in modalità Datastore richiede Cloud Run Functions (2ª generazione.). Le funzioni Cloud Run (1ª generazione.) non supportano la modalità Datastore.
- Un trigger è associato a un singolo database. Non puoi creare un attivatore che corrisponda a più database.
- L'eliminazione di un database non comporta l'eliminazione automatica degli attivatori per quel database. L'attivatore interrompe l'invio di eventi, ma continua a esistere finché non lo elimini.
- Se un evento con corrispondenza supera la dimensione massima della richiesta, l'evento potrebbe non essere inviato alle funzioni Cloud Run (1ª generazione.).
- Gli eventi non inviati a causa delle dimensioni della richiesta vengono registrati nei log della piattaforma e conteggiati ai fini dell'utilizzo dei log per il progetto.
- Puoi trovare questi log in Esplora log con il messaggio "Impossibile inviare l'evento alla funzione Cloud perché le dimensioni superano il limite per la 1ª gen." di gravità
error
. Puoi trovare il nome della funzione nel campofunctionName
. Se il camporeceiveTimestamp
è ancora entro un'ora da adesso, puoi dedurre i contenuti effettivi dell'evento leggendo il documento in questione con uno snapshot prima e dopo il timestamp. - Per evitare questa cadenza, puoi:
- Esegui la migrazione e l'upgrade a Cloud Run Functions (2ª generazione.)
- Riduci le dimensioni del documento
- Elimina le funzioni Cloud Run in questione
- Puoi disattivare il logging stesso utilizzando le esclusioni, ma tieni presente che gli eventi in violazione non verranno comunque inviati.