Memperluas Firestore dengan Cloud Functions

Dengan Cloud Functions, Anda dapat men-deploy kode Node.js untuk menangani peristiwa yang dipicu oleh perubahan pada database Firestore. Dengan begitu, Anda dapat menambahkan fungsionalitas sisi server ke aplikasi dengan mudah tanpa harus menjalankan server Anda sendiri.

Untuk contoh kasus penggunaan, lihat Apa yang Dapat Dilakukan dengan Cloud Functions? atau repositori GitHub Contoh Fungsi.

Pemicu fungsi Firestore

Cloud Functions for Firebase SDK mengekspor objek functions.firestore, sehingga Anda dapat membuat pengendali yang terikat dengan peristiwa Firestore tertentu.

Jenis Peristiwa Pemicu
onCreate Dipicu saat dokumen ditulisi untuk pertama kalinya.
onUpdate Dipicu saat dokumen sudah ada dan nilainya berubah.
onDelete Dipicu saat dokumen yang memuat data dihapus.
onWrite Dipicu saat onCreate, onUpdate, atau onDelete dipicu.

Jika belum ada project yang diaktifkan untuk Cloud Functions for Firebase, baca Memulai: Menulis dan Men-deploy Fungsi Pertama Anda untuk mengonfigurasi dan menyiapkan project Cloud Functions for Firebase.

Menulis fungsi yang dipicu oleh Firestore

Mendefinisikan pemicu fungsi

Untuk menentukan pemicu Firestore, tentukan jalur dokumen dan jenis peristiwa:

Node.js

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

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

Jalur dokumen dapat merujuk pada dokumen tertentu atau pola karakter pengganti.

Menentukan satu dokumen

Jika ingin memicu suatu peristiwa untuk perubahan apa pun pada dokumen tertentu, Anda dapat menggunakan fungsi berikut.

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
    });

Menentukan grup dokumen menggunakan karakter pengganti

Jika ingin menambahkan pemicu ke grup dokumen, seperti dokumen dalam koleksi tertentu, gunakan {wildcard} sebagai pengganti ID dokumen:

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"}
    });

Dalam contoh ini, saat kolom dalam dokumen pada users diubah, sistem akan mencocokkannya dengan karakter pengganti yang disebut userId.

Jika dokumen dalam users memiliki subkoleksi, dan kolom di salah satu dokumen subkoleksi tersebut diubah, karakter pengganti userId tidak akan terpicu.

Kecocokan karakter pengganti diekstrak dari jalur dokumen dan disimpan ke dalam context.params. Anda dapat mendefinisikan sebanyak mungkin karakter pengganti yang diinginkan untuk menggantikan ID dokumen atau koleksi eksplisit, misalnya:

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"}
    });

Pemicu Peristiwa

Memicu fungsi saat dokumen baru dibuat

Anda dapat memicu fungsi agar aktif setiap kali ada dokumen baru yang dibuat dalam koleksi menggunakan pengendali onCreate() dengan karakter pengganti. Fungsi contoh ini akan memanggil createUser setiap kali profil pengguna baru ditambahkan:

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 ...
    });

Memicu fungsi saat dokumen diperbarui

Anda juga dapat memicu fungsi agar aktif saat dokumen diperbarui menggunakan fungsi onUpdate() dengan karakter pengganti. Fungsi contoh ini akan memanggil updateUser jika pengguna mengubah profilnya:

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 ...
    });

Memicu fungsi saat dokumen dihapus

Anda juga dapat memicu fungsi saat dokumen dihapus menggunakan fungsi onDelete() dengan karakter pengganti. Fungsi contoh ini akan memanggil deleteUser ketika pengguna menghapus profilnya:

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 ...
    });

Memicu fungsi untuk semua perubahan pada dokumen

Jika tidak peduli dengan jenis peristiwa yang diaktifkan, Anda dapat memproses semua perubahan dalam dokumen Firestore menggunakan fungsi onWrite() dengan karakter pengganti. Fungsi contoh ini akan memanggil modifyUser jika pengguna dibuat, diperbarui, atau dihapus:

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 ...
    });

Membaca dan Menulis Data

Jika dipicu, suatu fungsi akan menghasilkan snapshot data yang terkait dengan peristiwa tersebut. Anda dapat menggunakan snapshot ini untuk membaca atau menulis dokumen yang memicu peristiwa tersebut, atau menggunakan Firebase Admin SDK untuk mengakses bagian lain database Anda.

Data Peristiwa

Membaca Data

Saat sebuah fungsi dipicu, Anda mungkin ingin mendapatkan data dari dokumen sesudah atau sebelum pembaruan. Anda bisa mendapatkan data sebelumnya menggunakan change.before.data(), yang berisi snapshot dokumen sebelum pembaruan. Demikian pula, change.after.data() berisi status snapshot dokumen setelah pembaruan.

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();
    });

Anda dapat mengakses properti sebagaimana Anda mengakses properti pada objek lainnya. Atau, Anda dapat menggunakan fungsi get untuk mengakses kolom tertentu:

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');

Menulis Data

Setiap pemanggilan fungsi dikaitkan dengan dokumen tertentu dalam database Firestore. Anda dapat mengakses dokumen tersebut sebagai DocumentReference di properti ref pada snapshot yang ditampilkan ke fungsi Anda.

DocumentReference ini berasal dari Firestore Node.js SDK dan mencakup berbagai metode seperti update(), set(), dan remove() sehingga Anda dapat dengan mudah mengubah dokumen yang memicu fungsi tersebut.

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});
    });

Data di luar peristiwa pemicu

Cloud Functions dijalankan dalam lingkungan tepercaya, yang berarti diberi otorisasi sebagai akun layanan di project Anda. Anda dapat melakukan pembacaan dan penulisan menggunakan Firebase Admin SDK:

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({ ... });
  });

Batasan

Perhatikan batasan berikut untuk pemicu Firestore untuk fungsi Cloud Run:

  • Fungsi Cloud Run (generasi ke-1) menjadi prasyarat database "(default)" yang ada dalam mode native Firestore. Lingkungan ini tidak mendukung mode Datastore atau database bernama Firestore. Gunakan fungsi Cloud Run (generasi ke-2) untuk mengonfigurasi peristiwa dalam kasus tersebut.
  • Pengurutan tidak dijamin. Perubahan cepat dapat memicu pemanggilan fungsi dalam urutan yang tidak terduga.
  • Peristiwa dikirim setidaknya satu kali, tetapi satu peristiwa dapat menghasilkan beberapa pemanggilan fungsi. Hindari mengandalkan mekanisme tepat satu kali, dan tulis fungsi idempoten.
  • Firestore dalam mode Datastore memerlukan fungsi Cloud Run (generasi ke-2). Fungsi Cloud Run (generasi ke-1) tidak mendukung mode Datastore.
  • Pemicu dikaitkan dengan satu database. Anda tidak dapat membuat pemicu yang cocok dengan beberapa database.
  • Menghapus database tidak secara otomatis menghapus pemicu untuk database tersebut. Pemicu berhenti mengirim peristiwa, tetapi akan tetap ada sampai Anda menghapus pemicu.
  • Jika peristiwa yang cocok melebihi ukuran permintaan maksimum, peristiwa tersebut mungkin tidak akan dikirim ke fungsi Cloud Run (generasi ke-1).
    • Peristiwa yang tidak terkirim karena ukuran permintaan akan dicatat dalam log platform dan diperhitungkan terhadap penggunaan log untuk project.
    • Anda dapat menemukan log ini di Logs Explorer dengan pesan "Event dapat mengirim ke Cloud function karena ukuran melebihi batas untuk generasi ke-1..." dengan tingkat keparahan error. Anda dapat menemukan nama fungsi di bawah kolom functionName. Jika kolom receiveTimestamp masih berada dalam waktu satu jam dari sekarang, Anda dapat menyimpulkan konten peristiwa yang sebenarnya dengan membaca dokumen yang dimaksud menggunakan snapshot sebelum dan setelah stempel waktu.
    • Untuk menghindari peristiwa seperti ini, Anda dapat:
      • Melakukan migrasi dan upgrade ke fungsi Cloud Run (generasi ke-2)
      • Memperkecil dokumen
      • Menghapus fungsi Cloud Run yang dimaksud
    • Anda dapat menonaktifkan logging itu sendiri menggunakan pengecualian, tetapi perhatikan bahwa peristiwa yang melanggar tidak akan dikirim.