Trigger di Google Cloud Firestore (1ª generazione.)
Le funzioni Cloud Run possono gestire gli eventi in Firestore nello stesso progetto Google Cloud 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 attività.
Riceve un oggetto dati con uno snapshot del documento interessato. Per gli eventi
write
oupdate
, l'oggetto dati contiene snapshot 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
comprende tutte le modifiche apportate a un documento.
Tipo di evento | Trigger |
---|---|
providers/cloud.firestore/eventTypes/document.create (valore predefinito) |
Si attiva quando un documento viene scritto per la prima volta. |
providers/cloud.firestore/eventTypes/document.update |
Attivato quando un documento esiste già e un valore è stato modificato. |
providers/cloud.firestore/eventTypes/document.delete |
Attivato quando viene eliminato un documento con dati. |
providers/cloud.firestore/eventTypes/document.write |
Attivato quando un documento viene creato, aggiornato o eliminato. |
I caratteri jolly vengono scritti nei trigger utilizzando le parentesi graffe, come segue:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"
Specificare il percorso del documento
Per attivare la funzione, specifica un percorso del documento da monitorare. Le funzioni rispondono solo alle modifiche apportate ai documenti e non possono monitorare campi o raccolte specifici. Di seguito sono riportati alcuni esempi di percorsi di documenti validi:
users/marie
: trigger valido. Monitora un singolo documento,/users/marie
.users/{username}
: trigger valido. Monitora tutti i documenti degli utenti. I caratteri jolly vengono utilizzati per monitorare tutti i documenti della raccolta.users/{username}/addresses
: trigger non valido. Si riferisce alla raccolta secondariaaddresses
, 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 di indirizzo.
Utilizzo di caratteri jolly e parametri
Se non conosci il documento specifico che vuoi monitorare, utilizza un {wildcard}
anziché l'ID documento:
users/{username}
rileva le modifiche apportate a tutti i documenti utente.
In questo esempio, quando viene modificato un campo in un documento di users
, corrisponde a un carattere jolly chiamato {username}
.
Se un documento in users
ha
raccolte secondarie e un
campo in uno dei documenti di queste raccolte secondarie 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 gli ID raccolta o documento espliciti.
Struttura dell'evento
Questo trigger richiama 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
. Ciò è particolarmente utile se utilizzi un linguaggio tipizzato
(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
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); } }
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
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); } }
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 tuo
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 hai 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 che stai utilizzando. 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 che la funzione ascolterà.
Deve essere conforme al seguente formato:
"projects/YOUR_PROJECT_ID/databases/(default)/documents/PATH"
Il testo {pushId} è un parametro jolly descritto sopra
in Specifica del percorso del documento.
|
Limitazioni
Tieni presente le seguenti limitazioni per i trigger Firestore per le funzioni Cloud Run:
- Le funzioni Cloud Run (1ª generazione.) richiedono un database "(default)" esistente in modalità nativa Firestore. Non supporta i database denominati Firestore o la modalità Datastore. In questi casi, utilizza Cloud Run Functions (2ª generazione) per configurare gli eventi.
- L'ordinamento non è garantito. Le modifiche 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 sulla meccanica di esecuzione esattamente una volta e scrivi funzioni idempotenti.
- Firestore in modalità Datastore richiede le funzioni Cloud Run (2ª generazione). Cloud Run (1ª generazione.) non supporta la modalità Datastore.
- Un trigger è associato a un singolo database. Non puoi creare un trigger che corrisponda a più database.
- L'eliminazione di un database non comporta l'eliminazione automatica di eventuali trigger per quel database. Il trigger smette di inviare eventi, ma continua a esistere finché non lo elimini.
- Se un evento corrispondente supera le dimensioni massime della richiesta, l'evento potrebbe non essere recapitato alle funzioni Cloud Run (1ª generazione.).
- Gli eventi non recapitati a causa delle dimensioni della richiesta vengono registrati nei log della piattaforma e vengono conteggiati nell'utilizzo dei log per il progetto.
- Puoi trovare questi log in Esplora log con il messaggio "Event cannot deliver to
Cloud function due to size exceeding the limit for1ª generazionen..." (Impossibile recapitare l'evento alla
funzione Cloud a causa delle dimensioni che superano il limite per la prima generazione...) di gravità
error
. Puoi trovare il nome della funzione nel campofunctionName
. Se il camporeceiveTimestamp
è ancora entro un'ora da adesso, puoi dedurre il contenuto effettivo dell'evento leggendo il documento in questione con un'istantanea prima e dopo il timestamp. - Per evitare questa cadenza, puoi:
- Eseguire la migrazione e l'upgrade alle funzioni Cloud Run (2ª generazione.)
- Ridurre le dimensioni del documento
- Elimina le funzioni Cloud Run in questione
- Puoi disattivare il logging utilizzando le esclusioni, ma tieni presente che gli eventi problematici non verranno comunque recapitati.