Ampliar o Cloud Firestore com o Cloud Functions

Com o Cloud Functions, é possível implantar código do Node.js para processar eventos acionados por alterações no seu banco de dados do Cloud Firestore. Dessa forma, é possível adicionar funcionalidade do lado do servidor no seu aplicativo facilmente, sem execução nos 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.

Acionadores de função do Cloud Firestore

O SDK do Cloud Functions para Firebase exporta um objeto functions.firestore que permite criar manipuladores vinculados a eventos específicos do Cloud Firestore.

Tipo de evento Acionador
onCreate Acionado quando um documento é gravado pela primeira vez.
onUpdate Acionado quando um documento atual 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: gravar e implantar suas primeiras funções para configurar um projeto.

Como gravar funções acionadas pelo Cloud Firestore

Definir um acionador de função

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

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('...')
  .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 ativar um evento para qualquer alteração em um documento específico, use a função a seguir.

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 usando padrões de caracteres curinga

Para anexar um acionador 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 padrão de caracteres curinga chamado userId.

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

As correspondências de padrões de caracteres curinga são extraídas do caminho do documento e armazenadas em context.params. É possível definir quantos padrões de caracteres curinga você quiser para substituir uma 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"}
    });

Acionadores de eventos

Acionar uma função quando um novo documento é criado

É possível acionar uma função para disparar sempre que um novo documento for criado em uma coleção usando um manipulador onCreate() com um padrão de caracteres curinga. 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

É possível acionar uma função para disparar quando um documento for atualizado usando a função onUpdate() com um padrão de caracteres curinga. 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

Também é possível acionar uma função quando um documento é excluído, basta usar a função onDelete() com um padrão de caracteres curinga. Esta função de exemplo chama a função 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 Cloud Firestore. Para isso, use a função onWrite() com um padrão de caracteres 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 do evento

Como ler dados

Quando uma função for acionada, veja os dados de um documento que foi atualizado ou colete-os antes da atualização. É possível 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.updateUser = 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 está associada a um documento específico no seu banco de dados do Cloud Firestore. É possível acessar esse documento como DocumentReference na propriedade ref do snapshot retornado para sua função.

Este DocumentReference vem do SDK do Cloud Firestore Node.js e inclui métodos como update(), set() e remove() para que você 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 do acionador

O Cloud Functions é executado em um ambiente confiável, isto é, ele é autorizado como uma conta de serviço no 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 e garantias

Os acionadores do Cloud Firestore para o Cloud Functions são um recurso na versão Beta com algumas limitações conhecidas:

  • Pode levar até 10 segundos para uma função responder às alterações no Cloud Firestore.
  • 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 "apenas uma vez" e escreva funções idempotentes.
  • Os acionadores do Cloud Firestore para o Cloud Functions estão disponíveis apenas no Cloud Firestore em modo nativo. Não estão disponíveis para o Cloud Firestore no modo Datastore.