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 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 digitato
(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
la documentazione di 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 in 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 Specifica del percorso del documento.
|
Limitazioni
Tieni presenti le seguenti limitazioni per i trigger Firestore per Cloud Run Functions:
- 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.
- La configurazione tra progetti con le funzioni Cloud Run e il trigger Firestore è una limitazione. Per configurare le funzioni Cloud Run del trigger Firestore devono trovarsi nello stesso progetto.
- L'ordinamento non è garantito. Le modifiche rapide possono attivare le chiamate di funzioni 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..." (L'evento non può essere inviato alla
funzione Cloud perché le dimensioni 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, puoi dedurre i contenuti effettivi 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 a Cloud Run Functions (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.