Com Cloud Functions, é possível implantar o código para processar eventos acionados por alterações no banco de dados Cloud Firestore. Isso permite que você adicione funcionalidade do lado do servidor no seu app facilmente, sem executar seus próprios servidores.
Cloud Functions (2ª geração)
Tecnologia do Cloud Run e Eventarc, O Cloud Functions for Firebase (2a geração) oferece recursos mais avançados infraestrutura, controle avançado sobre o desempenho e a escalonabilidade, e muito mais do ambiente de execução das funções. Para mais informações sobre a segunda geração, consulte Cloud Functions para Firebase (2ª geração). Para saber mais sobre a 1a geração, consulte Estender Cloud Firestore com o Cloud Functions.
Acionadores de função Cloud Firestore
O SDK do Cloud Functions for Firebase exporta o seguinte Cloud Firestore acionadores de eventos para permitir a criação de manipuladores vinculados a Cloud Firestore eventos:
Node.js
Tipo de evento | Gatilho |
---|---|
onDocumentCreated |
Acionado quando um documento é gravado pela primeira vez. |
onDocumentUpdated |
Acionado 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. |
onDocumentCreatedWithAuthContext |
onDocumentCreated com mais informações de autenticação |
onDocumentWrittenWithAuthContext |
onDocumentWritten com mais informações de autenticação |
onDocumentDeletedWithAuthContext |
onDocumentDeleted com mais informações de autenticação |
onDocumentUpdatedWithAuthContext |
onDocumentUpdated com mais informações de autenticação |
Python (pré-lançamento)
Tipo de evento | Gatilho |
---|---|
on_document_created |
Acionado quando um documento é gravado pela primeira vez. |
on_document_updated |
Acionado 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. |
on_document_created_with_auth_context |
on_document_created com mais informações de autenticação |
on_document_updated_with_auth_context |
on_document_updated com mais informações de autenticação |
on_document_deleted_with_auth_context |
on_document_deleted com mais informações de autenticação |
on_document_written_with_auth_context |
on_document_written com mais informações de autenticação |
Somente acionadores de eventos Cloud Firestore sobre alterações nos documentos. Uma atualização em um documento do Cloud Firestore, em que os dados permanecem inalterados (uma gravação em ambiente autônomo), não vai gerar um evento de atualização ou gravação. Não é possível adicionar eventos a campos específicos.
Se você ainda não ativou um projeto para Cloud Functions for Firebase, leia Começar a usar o Cloud Functions for Firebase (2a geração) para configurar seu projeto do Cloud Functions for Firebase.
Como escrever funções acionadas por Cloud Firestore
Definir um gatilho de função
Para definir um acionador do Cloud Firestore, especifique um caminho do 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 (pré-lançamento)
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 do documento podem se referir a um documento específico ou a um padrão de caracteres curinga.
Especificar um único documento
Se você quiser acionar um evento para qualquer alteração em um documento específico, use 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 (pré-lançamento)
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:
Especificar um grupo de documentos com caracteres curinga
Para adicionar um gatilho a um grupo de documentos, como qualquer documento em
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 (pré-lançamento)
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 caractere curinga chamado userId
.
Se um documento em users
tiver subcoleções e um campo em um dos documentos dessas subcoleções for alterado, o caractere curinga userId
não será acionado.
As correspondências de caracteres curinga são extraídas do caminho do documento e armazenadas em event.params
.
É possível definir quantos caracteres curinga você quiser para substituir a coleção explícita
ou os IDs do documento:
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 (pré-lançamento)
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"}
É preciso que seu gatilho aponte sempre para um documento, mesmo que você esteja usando um caractere 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 vai apontar para um documento.
Gatilhos de eventos
Acionar uma função quando um novo documento é criado
Você pode acionar uma função 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 ...
});
Para mais informações de autenticação, use onDocumentCreatedWithAuthContext
.
Python (pré-lançamento)
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 ...
Acionar uma função quando um documento é atualizado
Você também pode acionar uma função quando um documento for atualizado. Esta função de exemplo é acionada quando um perfil de usuário for alterado:
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 ...
});
Para mais informações de autenticação, use onDocumentUpdatedWithAuthContext
.
Python (pré-lançamento)
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 ...
Acionar uma função quando um documento é excluído
Também é possível acionar uma função quando um documento for excluído. Esta função de exemplo é acionada quando um perfil de usuário for excluído:
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 ...
});
Para mais informações de autenticação, use onDocumentDeletedWithAuthContext
.
Python (pré-lançamento)
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 ...
Acionar uma função para todas as alterações em um documento
Se o tipo de evento acionado não for relevante, será possível detectar todas as alterações em um documento do Cloud Firestore usando o acionador de evento "documento gravado". Esta função de exemplo é acionada 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 ...
});
Para mais informações de autenticação, use onDocumentWrittenWithAuthContext
.
Python (pré-lançamento)
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 ...
Como ler e gravar dados
Quando uma função é acionada, ela fornece um snapshot dos dados relacionados ao evento. É possível usar esse snapshot para ler ou gravar no documento usado para acionar o evento ou usar o SDK Admin do Firebase para acessar outras partes do seu banco de dados.
Dados do evento
Como ler dados
Quando uma função é acionada, é possível que você queira ver os dados de um documento antes ou depois de que ele seja atualizado. Para conseguir os dados anteriores, basta usar
event.data.before
, que contém o snapshot do documento antes da atualização.
Da mesma forma, event.data.after
contém o estado do snapshot 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 (pré-lançamento)
@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()
Acesse as propriedades como faria em qualquer outro objeto. Como alternativa,
use 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 (pré-lançamento)
# 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"]
Como gravar dados
Cada invocação de função é associada a um documento específico no banco de dados do Cloud Firestore. É possível acessar esse documento no snapshot 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 (pré-lançamento)
@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})
Acessar informações de autenticação do usuário
Se você usar um dos tipos de evento a seguir, poderá acessar informações de autenticação do usuário sobre o principal que acionou o evento. Essas informações são adicionadas às informações retornadas no evento base.
Node.js
onDocumentCreatedWithAuthContext
onDocumentWrittenWithAuthContext
onDocumentDeletedWithAuthContext
onDocumentUpdatedWithAuthContext
Python (pré-lançamento)
on_document_created_with_auth_context
on_document_updated_with_auth_context
on_document_deleted_with_auth_context
on_document_written_with_auth_context
Para mais informações sobre os dados disponíveis no contexto de autenticação, consulte Contexto da autenticação. O exemplo a seguir demonstra como recuperar informações de autenticação:
Node.js
import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"
exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
const snapshot = event.data.after;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// retrieve auth context from event
const { authType, authId } = event;
let verified = false;
if (authType === "system") {
// system-generated users are automatically verified
verified = true;
} else if (authType === "unknown" || authType === "unauthenticated") {
// admin users from a specific domain are verified
if (authId.endsWith("@example.com")) {
verified = true;
}
}
return data.after.ref.set({
created_by: authId,
verified,
}, {merge: true});
});
Python (pré-lançamento)
@on_document_updated_with_auth_context(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
# Get the auth context from the event
user_auth_type = event.auth_type
user_auth_id = event.auth_id
Dados fora do evento acionador
Cloud Functions são executados em um ambiente confiável. Os dados são autorizados como uma conta de serviço no projeto, e é possível executar 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 (pré-lançamento)
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 acionadores Cloud Firestore para Cloud Functions:
- O pré-requisito do Cloud Functions (1ª geração) é um banco de dados "(padrão)" no modo nativo do Firestore. Ele não é compatível com bancos de dados nomeados do Cloud Firestore ou com o modo Datastore. Use o Cloud Functions (2ª geração) para configurar eventos nesses casos.
- Não garantimos acionamentos em ordem. Alterações rápidas podem acionar invocações de função em uma ordem inesperada.
- Os eventos são entregues pelo menos uma vez, mas um único evento pode resultar em invocações de várias funções. Evite depender de mecanismos do tipo "apenas uma vez" e escreva funções idempotentes.
- Cloud Firestore no modo Datastore requer Cloud Functions (2a geração). O Cloud Functions (1a geração) não dão suporte ao modo Datastore.
- Um gatilho está associado a um único banco de dados. Não é possível criar um gatilho que corresponda a vários bancos de dados.
- A exclusão de um banco de dados não remove automaticamente nenhum gatilho dele. O acionador deixa de entregar eventos, mas continua existindo até que você o exclua.
- Se um evento correspondente exceder o tamanho máximo da solicitação, ele pode 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 registros da plataforma e contabilizados no uso de registros do projeto.
- É possível encontrar esses registros na Análise de registros com a mensagem "O evento não pode ser entregue à
função do Cloud devido ao tamanho excedido em relação ao limite para a 1ª geração..." da gravidade
de
error
. Encontre o nome da função no campofunctionName
. Se o camporeceiveTimestamp
ainda estiver dentro de uma hora, será possível inferir o conteúdo real do evento lendo o documento em questão com um snapshot antes e depois do carimbo de data/hora. - Para evitar isso, faça o seguinte:
- Migrar e fazer upgrade para o Cloud Functions (2ª geração)
- Reduza o tamanho do documento
- Exclua o Cloud Functions em questão
- É possível desativar a geração de registros usando exclusões, mas os eventos ofensivos ainda não serão entregues.