Memperluas fungsi Cloud Firestore dengan Cloud Functions (generasi ke-2)

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

Cloud Functions (generasi ke-2)

Dengan dukungan Cloud Run dan Eventarc, Cloud Functions for Firebase (generasi ke-2) memberi Anda infrastruktur yang lebih andal kontrol lanjutan atas performa dan skalabilitas, serta kontrol runtime fungsi yang lebih besar. Untuk mengetahui informasi selengkapnya tentang generasi ke-2, lihat Cloud Functions for Firebase (generasi ke-2). Untuk melihat informasi selengkapnya tentang generasi ke-1, lihat Memperluas Cloud Firestore dengan Cloud Functions.

Pemicu fungsi Cloud Firestore

Cloud Functions for Firebase SDK mengekspor pemicu peristiwa Cloud Firestore berikut untuk memungkinkan Anda membuat pengendali yang terkait dengan peristiwa Cloud Firestore tertentu:

Node.js

Jenis Peristiwa Pemicu
onDocumentCreated Dipicu saat dokumen ditulisi untuk pertama kalinya.
onDocumentUpdated Dipicu saat dokumen sudah ada dan nilainya berubah.
onDocumentDeleted Dipicu saat dokumen dihapus.
onDocumentWritten Dipicu saat onDocumentCreated, onDocumentUpdated, atau onDocumentDeleted dipicu.

Python (pratinjau)

Jenis Peristiwa Pemicu
on_document_created Dipicu saat dokumen ditulisi untuk pertama kalinya.
on_document_updated Dipicu saat dokumen sudah ada dan nilainya berubah.
on_document_deleted Dipicu saat dokumen dihapus.
on_document_written Dipicu saat on_document_created, on_document_updated, atau on_document_deleted dipicu.

Peristiwa Cloud Firestore hanya dipicu jika ada perubahan dokumen. Pembaruan terhadap dokumen Cloud Firestore yang tidak mengubah data (penulisan tanpa pengoperasian), tidak menghasilkan peristiwa penulisan atau pembaruan. Peristiwa tidak dapat ditambahkan ke kolom tertentu.

Jika Anda belum mengaktifkan project untuk Cloud Functions for Firebase, baca Memulai Cloud Functions for Firebase (generasi ke-2) untuk mengonfigurasi dan menyiapkan Cloud Functions Anda untuk project Firebase.

Menulis fungsi yang dipicu oleh Cloud Firestore

Mendefinisikan pemicu fungsi

Untuk mendefinisikan pemicu Cloud Firestore, tentukan jalur dokumen dan jenis peristiwa:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (pratinjau)

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:

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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (pratinjau)

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:

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

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 (pratinjau)

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

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 event.params. Anda dapat mendefinisikan sebanyak mungkin karakter pengganti yang diinginkan untuk menggantikan ID dokumen atau koleksi eksplisit, misalnya:

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 (pratinjau)

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

Pemicu Anda harus selalu menunjuk ke sebuah dokumen, meskipun Anda menggunakan karakter pengganti. Misalnya, users/{userId}/{messageCollectionId} tidak valid karena {messageCollectionId} adalah sebuah koleksi. Namun, users/{userId}/{messageCollectionId}/{messageId} valid karena {messageId} akan selalu mengarah ke dokumen.

Pemicu Peristiwa

Memicu fungsi saat dokumen baru dibuat

Anda dapat memicu fungsi agar aktif setiap kali ada dokumen baru yang dibuat dalam koleksi. Fungsi contoh ini akan terpicu setiap kali profil pengguna baru ditambahkan:

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

Python (pratinjau)

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 ...

Memicu fungsi saat dokumen diperbarui

Anda juga dapat memicu fungsi agar aktif saat dokumen diperbarui. Fungsi contoh ini akan aktif jika pengguna mengubah profilnya:

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

Python (pratinjau)

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 ...

Memicu fungsi saat dokumen dihapus

Anda juga dapat memicu fungsi saat dokumen dihapus. Fungsi contoh ini akan aktif ketika pengguna menghapus profilnya:

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

Python (pratinjau)

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 ...

Memicu fungsi untuk semua perubahan pada dokumen

Jika tidak mementingkan jenis peristiwa yang diaktifkan, Anda dapat memproses semua perubahan dalam dokumen Cloud Firestore menggunakan pemicu peristiwa "dokumen yang ditulis". Fungsi contoh ini akan diaktifkan jika pengguna dibuat, diperbarui, atau dihapus:

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

Python (pratinjau)

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 ...

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 event.data.before, yang berisi snapshot dokumen sebelum pembaruan. Demikian pula, event.data.after berisi status snapshot dokumen setelah pembaruan.

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 (pratinjau)

@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()

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 = 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 (pratinjau)

# 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"]

Menulis Data

Setiap pemanggilan fungsi dikaitkan dengan dokumen tertentu dalam database Cloud Firestore. Anda dapat mengakses dokumen tersebut di ringkasan yang ditampilkan ke fungsi Anda.

Referensi dokumen menyertakan metode seperti update(), set(), dan remove() sehingga Anda dapat mengubah dokumen yang memicu fungsi.

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 (pratinjau)

@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})
 ```

Data di luar peristiwa pemicu

Cloud Functions dijalankan dalam lingkungan tepercaya. Alat tersebut diotorisasi sebagai akun layanan di project Anda, dan Anda dapat melakukan pembacaan dan penulisan menggunakan Firebase Admin SDK:

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 (pratinjau)

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

Keterbatasan

Perhatikan batasan berikut untuk pemicu Cloud Firestore untuk Cloud Functions:

  • 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.
  • Cloud Firestore dalam mode Datastore memerlukan Cloud Functions (generasi ke-2). Cloud Functions (generasi ke-1) tidak mendukung mode Datastore.
  • Cloud Functions (generasi ke-1) hanya berfungsi dengan database "(default)" dan tidak mendukung database bernama Cloud Firestore. Gunakan Cloud Functions (generasi ke-2) untuk mengonfigurasi peristiwa untuk database bernama.
  • 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 Cloud Functions (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 Cloud Functions (generasi ke-2)
      • Memperkecil dokumen
      • Menghapus Cloud Functions yang dimaksud
    • Anda dapat menonaktifkan logging itu sendiri menggunakan pengecualian, tetapi perhatikan bahwa peristiwa yang melanggar tidak akan dikirim.