Trình kích hoạt trên Cloud Firestore

Với Cloud Functions, bạn có thể xử lý các sự kiện trong Cloud Firestore mà không cần cập nhật mã ứng dụng. Bạn có thể thực hiện các thay đổi đối với Cloud Firestore thông qua giao diện ảnh chụp nhanh tài liệu hoặc thông qua SDK dành cho quản trị viên.

Trong một vòng đời thông thường, hàm Cloud Firestore sẽ thực hiện những việc sau:

  1. Chờ các thay đổi đối với một tài liệu cụ thể.
  2. Kích hoạt khi một sự kiện xảy ra và thực hiện các nhiệm vụ của sự kiện đó.
  3. Nhận một đối tượng dữ liệu chứa ảnh chụp nhanh của dữ liệu được lưu trữ trong tài liệu đã chỉ định. Đối với các sự kiện ghi hoặc cập nhật, đối tượng dữ liệu chứa hai ảnh chụp nhanh đại diện cho trạng thái dữ liệu trước và sau sự kiện kích hoạt.

Khoảng cách giữa vị trí của phiên bản Firestore và vị trí của hàm có thể tạo ra độ trễ mạng đáng kể. Để tối ưu hoá hiệu suất, hãy cân nhắc chỉ định vị trí hàm nếu có.

Trình kích hoạt hàm Cloud Firestore

SDK Cloud Functions for Firebase xuất các trình kích hoạt sự kiện Cloud Firestore sau đây để cho phép bạn tạo trình xử lý liên kết với các sự kiện Cloud Firestore cụ thể:

Node.js

Loại sự kiện Điều kiện kích hoạt
onDocumentCreated Được kích hoạt khi một tài liệu được ghi lần đầu tiên.
onDocumentUpdated Được kích hoạt khi một tài liệu đã tồn tại và có bất kỳ giá trị nào thay đổi.
onDocumentDeleted Được kích hoạt khi một tài liệu bị xoá.
onDocumentWritten Được kích hoạt khi onDocumentCreated, onDocumentUpdated hoặc onDocumentDeleted được kích hoạt.
onDocumentCreatedWithAuthContext onDocumentCreated kèm theo thông tin xác thực bổ sung
onDocumentWrittenWithAuthContext onDocumentWritten kèm theo thông tin xác thực bổ sung
onDocumentDeletedWithAuthContext onDocumentDeleted kèm theo thông tin xác thực bổ sung
onDocumentUpdatedWithAuthContext onDocumentUpdated kèm theo thông tin xác thực bổ sung

Python

Loại sự kiện Điều kiện kích hoạt
on_document_created Được kích hoạt khi một tài liệu được ghi lần đầu tiên.
on_document_updated Được kích hoạt khi một tài liệu đã tồn tại và có bất kỳ giá trị nào thay đổi.
on_document_deleted Được kích hoạt khi một tài liệu bị xoá.
on_document_written Được kích hoạt khi on_document_created, on_document_updated hoặc on_document_deleted được kích hoạt.
on_document_created_with_auth_context on_document_created kèm theo thông tin xác thực bổ sung
on_document_updated_with_auth_context on_document_updated kèm theo thông tin xác thực bổ sung
on_document_deleted_with_auth_context on_document_deleted kèm theo thông tin xác thực bổ sung
on_document_written_with_auth_context on_document_written kèm theo thông tin xác thực bổ sung

Sự kiện Cloud Firestore chỉ kích hoạt khi có thay đổi về tài liệu. Bản cập nhật cho tài liệu Cloud Firestore mà dữ liệu không thay đổi (ghi không hoạt động) sẽ không tạo ra sự kiện cập nhật hoặc ghi. Bạn không thể thêm sự kiện vào các trường cụ thể.

Nếu bạn chưa bật dự án cho Cloud Functions for Firebase, hãy đọc bài viết Bắt đầu sử dụng Cloud Functions for Firebase (thế hệ thứ 2) để định cấu hình và thiết lập dự án Cloud Functions for Firebase.

Viết các hàm do Cloud Firestore kích hoạt

Xác định điều kiện kích hoạt hàm

Để xác định điều kiện kích hoạt Cloud Firestore, hãy chỉ định đường dẫn tài liệu và loại sự kiện:

Node.js

const {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

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

Python

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:

Đường dẫn tài liệu có thể tham chiếu đến một tài liệu cụ thể hoặc một mẫu ký tự đại diện.

Chỉ định một tài liệu duy nhất

Nếu muốn kích hoạt một sự kiện cho bất kỳ thay đổi nào đối với một tài liệu cụ thể, thì bạn có thể sử dụng hàm sau.

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

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

Python

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:

Chỉ định một nhóm tài liệu bằng ký tự đại diện

Nếu bạn muốn đính kèm một điều kiện kích hoạt vào một nhóm tài liệu, chẳng hạn như bất kỳ tài liệu nào trong một tập hợp nhất định, hãy sử dụng {wildcard} thay cho mã tài liệu:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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

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

Trong ví dụ này, khi bất kỳ trường nào trên bất kỳ tài liệu nào trong users thay đổi, trường đó sẽ khớp với một ký tự đại diện có tên là userId.

Nếu một tài liệu trong users có các tập hợp con và một trường trong một trong các tài liệu của tập hợp con đó bị thay đổi, thì ký tự đại diện userId sẽ không được kích hoạt.

Các kết quả khớp ký tự đại diện được trích xuất từ đường dẫn tài liệu và được lưu trữ vào event.params. Bạn có thể xác định bao nhiêu ký tự đại diện tuỳ thích để thay thế mã nhận dạng tài liệu hoặc tập hợp rõ ràng, ví dụ:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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

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

Trình kích hoạt của bạn phải luôn trỏ đến một tài liệu, ngay cả khi bạn đang sử dụng ký tự đại diện. Ví dụ: users/{userId}/{messageCollectionId} không hợp lệ vì {messageCollectionId} là một tập hợp. Tuy nhiên, users/{userId}/{messageCollectionId}/{messageId} là hợp lệ vì {messageId} sẽ luôn trỏ đến một tài liệu.

Trình kích hoạt sự kiện

Kích hoạt một hàm khi tài liệu mới được tạo

Bạn có thể kích hoạt một hàm để kích hoạt bất cứ khi nào một tài liệu mới được tạo trong một tập hợp. Hàm mẫu này kích hoạt mỗi khi có hồ sơ người dùng mới được thêm vào:

Node.js

const {
  onDocumentCreated,
  Change,
  FirestoreEvent
} = require('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 ...
});

Để biết thêm thông tin xác thực, hãy sử dụng onDocumentCreatedWithAuthContext.

Python

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

Kích hoạt một hàm khi tài liệu được cập nhật

Bạn cũng có thể kích hoạt một hàm để kích hoạt khi một tài liệu được cập nhật. Hàm mẫu này sẽ kích hoạt nếu người dùng thay đổi hồ sơ của họ:

Node.js

const {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} = require('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 ...
});

Để biết thêm thông tin xác thực, hãy sử dụng onDocumentUpdatedWithAuthContext.

Python

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

Kích hoạt một hàm khi một tài liệu bị xoá

Bạn cũng có thể kích hoạt một hàm khi một tài liệu bị xoá. Hàm mẫu này sẽ kích hoạt khi người dùng xoá hồ sơ người dùng của họ:

Node.js

const {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('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 ...
});

Để biết thêm thông tin xác thực, hãy sử dụng onDocumentDeletedWithAuthContext.

Python

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

Kích hoạt một hàm cho tất cả thay đổi đối với một tài liệu

Nếu không quan tâm đến loại sự kiện được kích hoạt, bạn có thể theo dõi tất cả thay đổi trong tài liệu Cloud Firestore bằng cách sử dụng trình kích hoạt sự kiện "tài liệu đã được ghi". Hàm mẫu này sẽ kích hoạt nếu người dùng được tạo, cập nhật hoặc xoá:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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 ...
});

Để biết thêm thông tin xác thực, hãy sử dụng onDocumentWrittenWithAuthContext.

Python

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

Đọc và ghi dữ liệu

Khi được kích hoạt, hàm sẽ cung cấp thông tin tổng quan nhanh về dữ liệu liên quan đến sự kiện. Bạn có thể sử dụng ảnh chụp nhanh này để đọc hoặc ghi vào tài liệu đã kích hoạt sự kiện, hoặc sử dụng SDK của Firebase dành cho quản trị viên để truy cập vào các phần khác của cơ sở dữ liệu.

Dữ liệu sự kiện

Dữ liệu đọc

Khi một hàm được kích hoạt, bạn có thể muốn lấy dữ liệu từ một tài liệu đã được cập nhật hoặc lấy dữ liệu trước khi cập nhật. Bạn có thể lấy dữ liệu trước đó bằng cách sử dụng event.data.before, chứa ảnh chụp nhanh tài liệu trước khi cập nhật. Tương tự, event.data.after chứa trạng thái tổng quan nhanh của tài liệu sau khi cập nhật.

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

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

Bạn có thể truy cập vào các thuộc tính như trong bất kỳ đối tượng nào khác. Ngoài ra, bạn có thể sử dụng hàm get để truy cập vào các trường cụ thể:

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

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

Ghi dữ liệu

Mỗi lệnh gọi hàm được liên kết với một tài liệu cụ thể trong cơ sở dữ liệu Cloud Firestore của bạn. Bạn có thể truy cập vào tài liệu đó trong ảnh chụp nhanh được trả về hàm của bạn.

Tham chiếu tài liệu bao gồm các phương thức như update(), set() và remove() để bạn có thể sửa đổi tài liệu đã kích hoạt hàm.

Node.js

const {onDocumentUpdated} = require('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 event.data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python

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

Truy cập vào thông tin xác thực người dùng

Nếu sử dụng một trong các loại sự kiện sau, bạn có thể truy cập vào thông tin xác thực người dùng về đối tượng chính đã kích hoạt sự kiện. Thông tin này bổ sung cho thông tin được trả về trong sự kiện cơ sở.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Để biết thông tin về dữ liệu có trong bối cảnh xác thực, hãy xem phần Bối cảnh xác thực. Ví dụ sau đây minh hoạ cách truy xuất thông tin xác thực:

Node.js

const {onDocumentWrittenWithAuthContext} = require('firebase-functions/v2/firestore');

exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
    const snapshot = event.data.after;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // retrieve auth context from event
    const { authType, authId } = event;

    let verified = false;
    if (authType === "system") {
      // system-generated users are automatically verified
      verified = true;
    } else if (authType === "unknown" || authType === "unauthenticated") {
      // admin users from a specific domain are verified
      if (authId.endsWith("@example.com")) {
        verified = true;
      }
    }

    return data.after.ref.set({
        created_by: authId,
        verified,
    }, {merge: true}); 
}); 

Python

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

  # Get the auth context from the event
  user_auth_type = event.auth_type
  user_auth_id = event.auth_id

Dữ liệu nằm ngoài sự kiện kích hoạt

Cloud Functions thực thi trong một môi trường đáng tin cậy. Các hàm này được uỷ quyền dưới dạng tài khoản dịch vụ trong dự án của bạn và bạn có thể thực hiện các thao tác đọc và ghi bằng SDK của Firebase dành cho quản trị viên:

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

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

Hạn chế

Hãy lưu ý các giới hạn sau đây đối với trình kích hoạt Cloud Firestore cho Cloud Functions:

  • Cloud Functions (thế hệ thứ 1) yêu cầu phải có sẵn một cơ sở dữ liệu "(default)" ở chế độ gốc của Firestore. Công cụ này không hỗ trợ cơ sở dữ liệu có tên Cloud Firestore hoặc chế độ Datastore. Vui lòng sử dụng Cloud Functions (thế hệ thứ 2) để định cấu hình sự kiện trong những trường hợp như vậy.
  • Việc thiết lập trên nhiều dự án bằng trình kích hoạt Cloud Functions và Cloud Firestore là một hạn chế. Để thiết lập trình kích hoạt Cloud Firestore, Cloud Functions phải nằm trong cùng một dự án.
  • Thứ tự không được đảm bảo. Những thay đổi nhanh chóng có thể kích hoạt lệnh gọi hàm theo thứ tự không mong muốn.
  • Sự kiện được phân phối ít nhất một lần, nhưng một sự kiện có thể dẫn đến nhiều lệnh gọi hàm. Tránh phụ thuộc vào cơ chế chỉ một lần và viết hàm luỹ đẳng.
  • Cloud Firestore ở chế độ Datastore yêu cầu Cloud Functions (thế hệ thứ 2). Cloud Functions (thế hệ thứ 1) không hỗ trợ chế độ Kho dữ liệu.
  • Một điều kiện kích hoạt được liên kết với một cơ sở dữ liệu duy nhất. Bạn không thể tạo điều kiện kích hoạt khớp với nhiều cơ sở dữ liệu.
  • Việc xoá cơ sở dữ liệu sẽ không tự động xoá mọi điều kiện kích hoạt cho cơ sở dữ liệu đó. Điều kiện kích hoạt sẽ ngừng phân phối sự kiện nhưng vẫn tiếp tục tồn tại cho đến khi bạn xoá điều kiện kích hoạt.
  • Nếu một sự kiện trùng khớp vượt quá kích thước yêu cầu tối đa, thì sự kiện đó có thể không được phân phối đến Cloud Functions (thế hệ thứ 1).
    • Những sự kiện không được phân phối do kích thước yêu cầu sẽ được ghi lại trong nhật ký nền tảng và được tính vào mức sử dụng nhật ký cho dự án.
    • Bạn có thể tìm thấy các nhật ký này trong Trình khám phá nhật ký bằng thông báo "Không thể gửi sự kiện đến Cloud Function do kích thước vượt quá giới hạn cho thế hệ thứ 1..." có mức độ nghiêm trọng error. Bạn có thể tìm thấy tên hàm trong trường functionName. Nếu trường receiveTimestamp vẫn còn trong vòng một giờ kể từ bây giờ, thì bạn có thể suy luận nội dung sự kiện thực tế bằng cách đọc tài liệu liên quan có ảnh chụp nhanh trước và sau dấu thời gian.
    • Để tránh nhịp độ như vậy, bạn có thể:
      • Di chuyển và nâng cấp lên Cloud Functions (thế hệ thứ 2)
      • Giảm kích thước tài liệu
      • Xoá Cloud Functions có liên quan
    • Bạn có thể tắt tính năng ghi nhật ký bằng cách sử dụng quy tắc loại trừ, nhưng xin lưu ý rằng các sự kiện vi phạm vẫn sẽ không được phân phối.