Como estender o Firestore com o Cloud Functions
Com o Cloud Functions, é possível implantar o código do Node.js para processar eventos acionados por alterações em seu banco de dados do Firestore. Isso permite que você adicione funcionalidade do lado do servidor no seu app facilmente, sem executar seus próprios servidores.
Para ver exemplos de casos de uso, consulte O que posso fazer com o Cloud Functions? ou o repositório de Amostras de funções no GitHub.
Gatilhos de função do Firestore
O SDK do Cloud Functions para Firebase exporta um objeto functions.firestore
que permite criar gerenciadores vinculados a eventos específicos do Cloud Firestore.
Tipo de evento | Gatilho |
---|---|
onCreate |
Acionado quando um documento é gravado pela primeira vez. |
onUpdate |
Acionado quando um documento já existe e tem algum valor alterado. |
onDelete |
Acionado quando um documento com dados é excluído. |
onWrite |
Acionado quando onCreate , onUpdate ou onDelete é acionado. |
Se você ainda não ativou um projeto para o Cloud Functions para Firebase, leia Primeiros passos: escrever e implantar suas primeiras funções para configurar um projeto.
Como gravar funções acionadas pelo Firestore
Definir um gatilho de função
Para definir um acionador do Firestore, especifique um caminho do documento e um tipo de evento:
Node.js
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('my-collection/{docId}')
.onWrite((change, context) => { /* ... */ });
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
// Listen for any change on document `marie` in collection `users` exports.myFunctionName = functions.firestore .document('users/marie').onWrite((change, context) => { // ... Your code here });
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
// Listen for changes in all documents in the 'users' collection exports.useWildcard = functions.firestore .document('users/{userId}') .onWrite((change, context) => { // If we set `/users/marie` to {name: "Marie"} then // context.params.userId == "marie" // ... and ... // change.after.data() == {name: "Marie"} });
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 context.params
.
É possível definir quantos caracteres curinga você quiser para substituir a coleção explícita
ou os IDs do documento:
Node.js
// Listen for changes in all documents in the 'users' collection and all subcollections exports.useMultipleWildcards = functions.firestore .document('users/{userId}/{messageCollectionId}/{messageId}') .onWrite((change, context) => { // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then // context.params.userId == "marie"; // context.params.messageCollectionId == "incoming_messages"; // context.params.messageId == "134"; // ... and ... // change.after.data() == {body: "Hello"} });
Gatilhos de eventos
Acionar uma função quando um novo documento é criado
Você pode usar um manipulador onCreate()
com um caractere curinga para que uma função seja acionada sempre que um novo documento é criado em uma coleção.
Neste exemplo de função, createUser
é chamado sempre que um novo perfil de usuário é adicionado:
Node.js
exports.createUser = functions.firestore .document('users/{userId}') .onCreate((snap, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = snap.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Acionar uma função quando um documento é atualizado
Você também pode usar a função
onUpdate()
com um caractere curinga para que uma função seja acionada quando um documento é atualizado. Esta função de exemplo chamará updateUser
se um perfil
de usuário for alterado:
Node.js
exports.updateUser = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
Acionar uma função quando um documento é excluído
E para acionar uma função quando um documento é excluído, basta usar a função
onDelete()
com um caractere curinga. Esta função
de exemplo chama deleteUser
quando um perfil de usuário é excluído:
Node.js
exports.deleteUser = functions.firestore .document('users/{userID}') .onDelete((snap, context) => { // Get an object representing the document prior to deletion // e.g. {'name': 'Marie', 'age': 66} const deletedValue = snap.data(); // perform desired operations ... });
Acionar uma função para todas as alterações em um documento
Se o tipo de evento disparado não for relevante, será possível detectar todas as
alterações em um documento do Firestore. Para isso, use a função onWrite()
com um caractere curinga. Essa função de exemplo chamará modifyUser
se um usuário for criado, atualizado ou excluído:
Node.js
exports.modifyUser = functions.firestore .document('users/{userID}') .onWrite((change, context) => { // Get an object with the current document value. // If the document does not exist, it has been deleted. const document = change.after.exists ? change.after.data() : null; // Get an object with the previous document value (for update or delete) const oldDocument = change.before.data(); // perform desired 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 de eventos
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
change.before.data()
, que contém o snapshot do documento antes da atualização.
Da mesma forma, change.after.data()
contém o estado do snapshot do documento após a atualização.
Node.js
exports.updateUser2 = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the current document const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); });
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 = snap.data().age; const name = snap.data()['name']; // Fetch data using built in accessor const experience = snap.get('experience');
Como gravar dados
Cada invocação de função é associada a um documento específico no
banco de dados do Firestore. É possível acessar esse documento como
DocumentReference
na propriedade ref
do snapshot retornado para sua função.
Esta DocumentReference
vem do
SDK do Firestore para Node.js
e inclui métodos como update()
, set()
e remove()
para que você consiga
modificar facilmente o documento que acionou a função.
Node.js
// Listen for updates to any `user` document. exports.countNameChanges = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Retrieve the current and previous value const data = change.after.data(); const previousData = change.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 change.after.ref.set({ name_change_count: count + 1 }, {merge: true}); });
Dados fora do evento acionador
O Cloud Functions é executado em um ambiente confiável, o que significa que ele é autorizado como uma conta de serviço em seu projeto. É possível executar leituras e gravações com o SDK Admin do Firebase:
Node.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.writeToFirestore = functions.firestore
.document('some/doc')
.onWrite((change, context) => {
db.doc('some/otherdoc').set({ ... });
});
Limitações
As seguintes limitações para gatilhos do Firestore do Cloud Run functions:
- O pré-requisito do Cloud Run 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 Firestore ou com o modo Datastore. Use o Cloud Run 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.
- O Firestore no modo Datastore requer o Cloud Run functions (2ª geração). O Cloud Run functions (1ª geração) não é compatível com o 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 gatilho 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 Run 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:
- Migre e faça upgrade para o Cloud Run functions (2ª geração)
- Reduza o tamanho do documento
- Exclua o Cloud Run 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.