Estenda o Cloud Firestore com Cloud Functions (2ª geração)

Com o Cloud Functions, você pode implantar código para lidar com eventos acionados por alterações no banco de dados do Cloud Firestore. Isso permite que você adicione facilmente funcionalidades do lado do servidor ao seu aplicativo sem executar seus próprios servidores.

Funções do Cloud (2ª geração)

Desenvolvido por Cloud Run e Eventarc , o Cloud Functions for Firebase (2ª geração) oferece infraestrutura mais poderosa, controle avançado sobre desempenho e escalabilidade e mais controle do tempo de execução das funções. Para obter mais informações sobre a 2ª geração, consulte Cloud Functions para Firebase (2ª geração) . Para ver mais sobre a primeira geração, consulte Estender o Cloud Firestore com Cloud Functions .

Gatilhos de função do Cloud Firestore

O SDK do Cloud Functions para Firebase exporta os seguintes acionadores de eventos do Cloud Firestore para permitir a criação de gerenciadores vinculados a eventos específicos do Cloud Firestore:

Node.js

Tipo de evento Acionar
onDocumentCreated Acionado quando um documento é gravado pela primeira vez.
onDocumentUpdated Disparado quando um documento já existe e tem algum valor alterado.
onDocumentDeleted Acionado quando um documento é excluído.
onDocumentWritten Acionado quando onDocumentCreated , onDocumentUpdated ou onDocumentDeleted é acionado.

Python (visualização)

Tipo de evento Acionar
on_document_created Acionado quando um documento é gravado pela primeira vez.
on_document_updated Disparado quando um documento já existe e tem algum valor alterado.
on_document_deleted Acionado quando um documento é excluído.
on_document_written Acionado quando on_document_created , on_document_updated ou on_document_deleted é acionado.

Os eventos do Cloud Firestore são acionados apenas em alterações de documentos. Uma atualização em um documento do Cloud Firestore em que os dados permanecem inalterados (uma gravação autônoma) não gera um evento de atualização ou gravação. Não é possível adicionar eventos a campos específicos.

Se você ainda não tem um projeto ativado para o Cloud Functions for Firebase, leia Primeiros passos com o Cloud Functions for Firebase (2ª geração) para configurar e configurar seu projeto do Cloud Functions for Firebase.

Escrevendo funções acionadas pelo Cloud Firestore

Defina um gatilho de função

Para definir um gatilho do Cloud Firestore, especifique um caminho de documento e um tipo de evento:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

Os caminhos dos documentos podem fazer referência a um documento específico ou a um padrão curinga .

Especifique um único documento

Se quiser acionar um evento para qualquer alteração em um documento específico, você pode usar a seguinte função.

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

Especifique um grupo de documentos usando curingas

Se você quiser anexar um gatilho a um grupo de documentos, como qualquer documento de uma determinada coleção, use um {wildcard} no lugar do ID do documento:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

Neste exemplo, quando qualquer campo em qualquer documento em users é alterado, ele corresponde a um curinga chamado userId .

Se um documento em users tiver subcoleções e um campo em um dos documentos dessas subcoleções for alterado, o curinga userId não será acionado.

As correspondências curinga são extraídas do caminho do documento e armazenadas em event.params . Você pode definir quantos curingas desejar para substituir IDs explícitos de coleções ou documentos, por exemplo:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

Seu gatilho deve sempre apontar para um documento, mesmo se você estiver usando um curinga. Por exemplo, users/{userId}/{messageCollectionId} não é válido porque {messageCollectionId} é uma coleção. No entanto, users/{userId}/{messageCollectionId}/{messageId} é válido porque {messageId} sempre apontará para um documento.

Gatilhos de eventos

Acione uma função quando um novo documento for criado

Você pode acionar uma função para disparar sempre que um novo documento for criado em uma coleção. Este exemplo de função é acionado sempre que um novo perfil de usuário é adicionado:

Node.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // access a particular field as you would any JS property
    const name = data.name;

    // perform more operations ...
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Acione uma função quando um documento for atualizado

Você também pode acionar uma função para disparar quando um documento é atualizado. Este exemplo de função é acionado se um usuário alterar seu perfil:

Node.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

    // access a particular field as you would any JS property
    const name = newValue.name;

    // perform more operations ...
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Acione uma função quando um documento for excluído

Você também pode acionar uma função quando um documento é excluído. Este exemplo de função é acionado quando um usuário exclui seu perfil de usuário:

Node.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

Acione uma função para todas as alterações em um documento

Se você não se importa com o tipo de evento que está sendo disparado, poderá ouvir todas as alterações em um documento do Cloud Firestore usando o gatilho de evento "documento escrito". Este exemplo de função é acionado se um usuário for criado, atualizado ou excluído:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

Python (visualização)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

Lendo e gravando dados

Quando uma função é acionada, ela fornece um instantâneo dos dados relacionados ao evento. Você pode usar esse snapshot para ler ou gravar no documento que acionou o evento ou usar o SDK Admin do Firebase para acessar outras partes do seu banco de dados.

Dados do Evento

Lendo dados

Quando uma função é acionada, você pode querer obter dados de um documento que foi atualizado ou obter os dados antes da atualização. Você pode obter os dados anteriores usando event.data.before , que contém o instantâneo do documento antes da atualização. Da mesma forma, event.data.after contém o estado do instantâneo do documento após a atualização.

Node.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python (visualização)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

Você pode acessar propriedades como faria em qualquer outro objeto. Alternativamente, você pode usar a função get para acessar campos específicos:

Node.js

// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];

// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');

Python (visualização)

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

Gravando dados

Cada invocação de função está associada a um documento específico no banco de dados do Cloud Firestore. Você pode acessar esse documento no instantâneo retornado para sua função.

A referência do documento inclui métodos como update() , set() e remove() para que você possa modificar o documento que acionou a função.

Node.js

import { onDocumentUpdated } from "firebase-functions/v2/firestore";

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.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 data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python (visualização)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
 # Get the current and previous document values.
 new_value = event.data.after
 prev_value = event.data.before

 # We'll only update if the name has changed.
 # This is crucial to prevent infinite loops.
 if new_value.get("name") == prev_value.get("name"):
     return

 # Retrieve the current count of name changes
 count = new_value.to_dict().get("name_change_count", 0)

 # Update the count
 new_value.reference.update({"name_change_count": count + 1})
 ```

Dados fora do evento acionador

O Cloud Functions é executado em um ambiente confiável. Eles são autorizados como uma conta de serviço no seu projeto, e você pode realizar leituras e gravações usando o SDK Admin do Firebase :

Node.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python (visualização)

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

Limitações

Observe as seguintes limitações para gatilhos do Cloud Firestore para Cloud Functions:

  • O pedido não é garantido. Mudanças rápidas podem acionar invocações de funções em uma ordem inesperada.
  • Os eventos são entregues pelo menos uma vez, mas um único evento pode resultar em múltiplas invocações de funções. Evite depender da mecânica exatamente uma vez e escreva funções idempotentes .
  • O Cloud Firestore no modo Datastore requer Cloud Functions (2ª geração). O Cloud Functions (1ª geração) não é compatível com o modo Datastore.
  • O Cloud Functions (1ª geração) funciona apenas com banco de dados "(padrão)" e não é compatível com bancos de dados nomeados do Cloud Firestore. Use o Cloud Functions (2ª geração) para configurar eventos para bancos de dados nomeados.
  • Um gatilho está associado a um único banco de dados. Você não pode criar um gatilho que corresponda a vários bancos de dados.
  • A exclusão de um banco de dados não exclui automaticamente nenhum acionador desse banco de dados. O gatilho para de entregar eventos, mas continua existindo até que você exclua o gatilho .
  • Se um evento correspondente exceder o tamanho máximo da solicitação , o evento poderá não ser entregue ao Cloud Functions (1ª geração).
    • Os eventos não entregues devido ao tamanho da solicitação são registrados nos logs da plataforma e contam para o uso do log do projeto.
    • Você pode encontrar esses logs no Logs Explorer com a mensagem "O evento não pode ser entregue à função Cloud devido ao tamanho exceder o limite da 1ª geração..." de gravidade error . Você pode encontrar o nome da função no campo functionName . Se o campo receiveTimestamp ainda estiver dentro de uma hora, você poderá inferir o conteúdo real do evento lendo o documento em questão com um instantâneo antes e depois do carimbo de data/hora.
    • Para evitar essa cadência, você pode:
      • Migrar e fazer upgrade para o Cloud Functions (2ª geração)
      • Reduza o tamanho do documento
      • Exclua o Cloud Functions em questão
    • Você pode desativar o registro em log usando exclusões , mas observe que os eventos ofensivos ainda não serão entregues.