Estender o Firestore com o Cloud Functions
Com o Cloud Functions, pode implementar código Node.js para processar eventos acionados por alterações na sua base de dados do Firestore. Isto permite-lhe adicionar facilmente funcionalidades do lado do servidor à sua app sem executar os seus próprios servidores.
Para ver exemplos de exemplos de utilização, consulte o artigo O que posso fazer com as Cloud Functions? ou o repositório do GitHub Functions Samples.
Acionadores de funções do Firestore
O SDK do Cloud Functions para Firebase exporta um objeto functions.firestore
que lhe permite criar controladores associados a eventos específicos do Firestore.
Tipo de evento | Acionador |
---|---|
onCreate |
Acionado quando um documento é escrito pela primeira vez. |
onUpdate |
Acionado quando já existe um documento e qualquer valor é alterado. |
onDelete |
Acionado quando um documento com dados é eliminado. |
onWrite |
Acionado quando onCreate , onUpdate ou onDelete é acionado. |
Se ainda não tiver um projeto ativado para o Cloud Functions para Firebase, leia o artigo Introdução: escreva e implemente as suas primeiras funções para configurar o seu projeto do Cloud Functions para Firebase.
Escrever funções acionadas pelo Firestore
Defina um acionador 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 dos documentos podem referenciar um documento específico ou um padrão de carateres universais.
Especifique um único documento
Se quiser acionar um evento para qualquer alteração a um documento específico, pode usar 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 });
Especifique um grupo de documentos através de carateres universais
Se quiser anexar um acionador a um grupo de documentos, como qualquer documento numa determinada coleção, use um {wildcard}
em vez 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, corresponde a um caráter universal denominado userId
.
Se um documento no users
tiver subcoleções e um campo num dos documentos dessas subcoleções for alterado, o caráter universal userId
não é acionado.
As correspondências com carateres universais são extraídas do caminho do documento e armazenadas em context.params
.
Pode definir quantos carateres universais quiser para substituir IDs de documentos ou de recolha explícitos, por exemplo:
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"} });
Acionadores de eventos
Acione uma função quando é criado um novo documento
Pode acionar uma função para ser executada sempre que um novo documento for criado numa coleção usando um controlador onCreate()
com um caráter universal.
Esta função de exemplo chama createUser
sempre que é adicionado um novo perfil de utilizador:
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 ... });
Acione uma função quando um documento é atualizado
Também pode acionar uma função para ser executada quando um documento é atualizado através da função onUpdate()
com um caráter universal. Esta função de exemplo chama updateUser
se um utilizador alterar o respetivo perfil:
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 ... });
Acione uma função quando um documento é eliminado
Também pode acionar uma função quando um documento é eliminado através da função onDelete()
com um caráter universal. Este exemplo
chama a função deleteUser
quando um utilizador elimina o respetivo perfil de utilizador:
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 a um documento
Se não se importar com o tipo de evento que está a ser acionado, pode ouvir todas as alterações num documento do Firestore através da função onWrite()
com um caráter universal. Esta função de exemplo chama modifyUser
se um utilizador for criado, atualizado ou eliminado:
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 ... });
Leitura e escrita de dados
Quando uma função é acionada, fornece um instantâneo dos dados relacionados com o evento. Pode usar esta imagem instantânea para ler ou escrever no documento que acionou o evento, ou usar o SDK de administração do Firebase para aceder a outras partes da sua base de dados.
Dados de eventos
Dados de leitura
Quando uma função é acionada, pode querer obter dados de um documento que foi atualizado ou obter os dados antes da atualização. Pode obter os dados anteriores através de
change.before.data()
, que contém a captura de ecrã do documento antes da atualização.
Da mesma forma, change.after.data()
contém o estado do instantâneo 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(); });
Pode aceder às propriedades como faria em qualquer outro objeto. Em alternativa, pode usar a função get
para aceder a 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');
Escrever dados
Cada invocação de função está associada a um documento específico na sua base de dados do Firestore. Pode aceder a esse documento como um
DocumentReference
na propriedade ref
do instantâneo devolvido à sua função.
Este DocumentReference
provém do
SDK Node.js do Firestore
e inclui métodos como update()
, set()
e remove()
para que possa 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 de acionamento
As funções do Cloud são executadas num ambiente fidedigno, o que significa que são autorizadas como uma conta de serviço no seu projeto. Pode fazer leituras e escritas usando o SDK Firebase Admin:
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
Tenha em atenção as seguintes limitações para acionadores do Firestore para funções do Cloud Run:
- As funções do Cloud Run (1.ª geração) requerem uma base de dados "(default)" existente no modo nativo do Firestore. Não suporta bases de dados com nome do Firestore nem o modo Datastore. Use as funções do Cloud Run (2.ª geração) para configurar eventos nesses casos.
- A configuração entre projetos com funções do Cloud Run e o acionador do Firestore é uma limitação. Para configurar funções do Cloud Run com acionadores do Firestore, estas têm de estar no mesmo projeto.
- A ordenação não é garantida. As alterações rápidas podem acionar invocações de funções numa ordem inesperada.
- Os eventos são entregues, pelo menos, uma vez, mas um único evento pode resultar em várias invocações de funções. Evite depender de mecanismos de execução única e escreva funções idempotentes.
- O Firestore no modo Datastore requer funções do Cloud Run (2.ª geração). As funções do Cloud Run (1.ª geração) não suportam o modo Datastore.
- Um acionador está associado a uma única base de dados. Não pode criar um acionador que corresponda a várias bases de dados.
- A eliminação de uma base de dados não elimina automaticamente os acionadores dessa base de dados. O acionador deixa de enviar eventos, mas continua a existir até eliminar o acionador.
- Se um evento correspondente exceder o tamanho máximo do pedido, o evento pode não ser entregue às funções do Cloud Run (1.ª geração).
- Os eventos não entregues devido ao tamanho do pedido são registados nos registos da plataforma e contam para a utilização de registos do projeto.
- Pode encontrar estes registos no Explorador de registos com a mensagem "Event cannot deliver to
Cloud function due to size exceeding the limit for 1st gen..." de
error
gravidade. Pode encontrar o nome da função no campofunctionName
. Se o camporeceiveTimestamp
ainda estiver dentro de uma hora a partir de agora, pode inferir o conteúdo real do evento lendo o documento em questão com uma captura de ecrã antes e depois da data/hora. - Para evitar esta cadência, pode:
- Migre e atualize para as funções do Cloud Run (2.ª geração)
- Reduza o tamanho do documento
- Elimine as funções do Cloud Run em questão
- Pode desativar o próprio registo através de exclusões, mas tenha em atenção que os eventos ofensivos continuam a não ser enviados.