Điều kiện kích hoạt Cơ sở dữ liệu theo thời gian thực


Với Cloud Functions, bạn có thể xử lý các sự kiện trong Cơ sở dữ liệu theo thời gian thực của Firebase mà không cần cập nhật mã ứng dụng. Cloud Functions cho phép bạn chạy các hoạt động của Cơ sở dữ liệu theo thời gian thực với toàn bộ quyền quản trị đặc quyền và đảm bảo rằng từng thay đổi đối với Cơ sở dữ liệu theo thời gian thực được xử lý riêng lẻ. Bạn có thể thực hiện các thay đổi đối với Cơ sở dữ liệu theo thời gian thực của Firebase thông qua DataSnapshot hoặc thông qua SDK dành cho quản trị viên.

Trong một vòng đời thông thường, chức năng của Cơ sở dữ liệu theo thời gian thực của Firebase thực hiện những việc sau:

  1. Chờ những thay đổi đối với một vị trí cụ thể của Cơ sở dữ liệu theo thời gian thực.
  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 đó (xem phần Tôi có thể làm gì) với Cloud Functions? để biết ví dụ về trường hợp sử dụng).
  3. Nhận một đối tượng dữ liệu chứa ảnh chụp nhanh về dữ liệu được lưu trữ trong tài liệu được chỉ định.

Kích hoạt hàm Cơ sở dữ liệu theo thời gian thực

Tạo hàm mới cho sự kiện Cơ sở dữ liệu theo thời gian thực cùng với functions.database. Người nhận kiểm soát thời điểm chức năng này kích hoạt, chỉ định một trong các trình xử lý sự kiện và chỉ định đường dẫn Cơ sở dữ liệu theo thời gian thực để theo dõi các sự kiện.

Thiết lập trình xử lý sự kiện

Các hàm cho phép bạn xử lý các sự kiện trong Cơ sở dữ liệu theo thời gian thực ở hai cấp độ cụ thể; bạn có thể nghe riêng để tạo, cập nhật, hoặc xoá sự kiện hoặc bạn có thể theo dõi mọi thay đổi dưới bất kỳ hình thức nào đối với đường dẫn. Cloud Functions hỗ trợ các trình xử lý sự kiện sau đây cho Cơ sở dữ liệu theo thời gian thực:

  • onWrite(): sẽ kích hoạt khi dữ liệu được tạo, cập nhật hoặc xoá trong Cơ sở dữ liệu theo thời gian thực.
  • onCreate(): kích hoạt khi dữ liệu mới được tạo trong Cơ sở dữ liệu theo thời gian thực.
  • onUpdate(), kích hoạt khi dữ liệu được cập nhật trong Cơ sở dữ liệu theo thời gian thực .
  • onDelete(): sẽ kích hoạt khi dữ liệu bị xoá khỏi Cơ sở dữ liệu theo thời gian thực .

Chỉ định thực thể và đường dẫn

Để kiểm soát thời điểm và vị trí hàm của bạn sẽ kích hoạt, hãy gọi ref(path) để chỉ định một đường dẫn và tuỳ ý chỉ định một phiên bản Cơ sở dữ liệu thời gian thực cùng với instance('INSTANCE_NAME'). Nếu bạn không chỉ định phiên bản, hàm sẽ triển khai cho phiên bản Cơ sở dữ liệu thời gian thực mặc định cho dự án Firebase. Ví dụ:

  • Phiên bản Cơ sở dữ liệu theo thời gian thực mặc định: functions.database.ref('/foo/bar')
  • Thực thể có tên là "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Các phương thức này hướng dẫn hàm của bạn xử lý các lượt ghi tại một đường dẫn nhất định trong bản sao Cơ sở dữ liệu theo thời gian thực. Thông số kỹ thuật của đường dẫn khớp với tất cả các bản ghi chạm vào một đường dẫn, bao gồm hoạt động ghi xảy ra ở bất kỳ vị trí nào bên dưới nó. Nếu bạn thiết lập đường dẫn cho hàm của bạn dưới dạng /foo/bar, nó khớp với các sự kiện ở cả hai vị trí sau:

 /foo/bar
 /foo/bar/baz/really/deep/path

Trong cả hai trường hợp, Firebase diễn giải rằng sự kiện xảy ra tại /foo/bar, và dữ liệu sự kiện bao gồm dữ liệu cũ và mới tại /foo/bar. Nếu dữ liệu sự kiện có thể lớn, hãy cân nhắc sử dụng nhiều hàm ở những đường dẫn sâu hơn thay vì một hàm gần gốc của cơ sở dữ liệu. Để có hiệu suất tốt nhất, chỉ yêu cầu dữ liệu ở cấp sâu nhất có thể.

Bạn có thể chỉ định một thành phần đường dẫn làm ký tự đại diện bằng cách đặt thành phần đó bằng dấu ngoặc nhọn dấu ngoặc; ref('foo/{bar}') khớp với mọi phần tử con của /foo. Giá trị của những thuộc tính này có sẵn các thành phần đường dẫn ký tự đại diện trong EventContext.params đối tượng của hàm. Trong ví dụ này, giá trị được cung cấp là context.params.bar.

Đường dẫn chứa ký tự đại diện có thể khớp với nhiều sự kiện từ một lần ghi. Phụ trang của

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

khớp với đường dẫn "/foo/{bar}" hai lần: một lần với "hello": "world" và một lần nữa với "firebase": "functions".

Xử lý dữ liệu sự kiện

Khi xử lý sự kiện Cơ sở dữ liệu theo thời gian thực, đối tượng dữ liệu được trả về là DataSnapshot. Đối với các sự kiện onWrite hoặc onUpdate, phương thức tham số đầu tiên là đối tượng Change chứa hai ảnh chụp nhanh biểu thị trạng thái dữ liệu trước khi và sau sự kiện kích hoạt. Đối với các sự kiện onCreateonDelete: đối tượng dữ liệu được trả về là thông tin tổng quan nhanh về dữ liệu được tạo hoặc bị xoá.

Trong ví dụ này, hàm truy xuất ảnh chụp nhanh cho đường dẫn đã chỉ định, chuyển đổi chuỗi ở vị trí đó thành chữ viết hoa, và ghi chuỗi đã sửa đổi đó vào cơ sở dữ liệu:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

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

Từ EventContext.authEventContext.authType, bạn có thể truy cập thông tin người dùng, bao gồm cả các quyền, đối với người dùng đã kích hoạt một hàm. Điều này có thể hữu ích cho việc thực thi các quy tắc bảo mật, cho phép chức năng của bạn hoàn tất các hoạt động khác nhau dựa trên cấp quyền:

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

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

Ngoài ra, bạn có thể tận dụng thông tin xác thực người dùng để "mạo danh" một người dùng và thực hiện các thao tác ghi thay mặt người dùng. Hãy nhớ xoá phiên bản ứng dụng như trình bày dưới đây nhằm ngăn chặn các vấn đề đồng thời:

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

Đọc giá trị trước

Đối tượng Changebefore cho phép bạn kiểm tra nội dung đã lưu vào Cơ sở dữ liệu theo thời gian thực trước khi sự kiện. Thuộc tính before trả về DataSnapshot trong đó tất cả (ví dụ: val()exists()) tham chiếu đến giá trị trước đó. Bạn có thể đọc lại giá trị mới bằng cách sử dụng DataSnapshot gốc hoặc đọc after thuộc tính này. Thuộc tính này trên bất kỳ Change nào cũng là một DataSnapshot khác đại diện cho trạng thái của dữ liệu sau khi sự kiện xảy ra.

Ví dụ: bạn có thể dùng thuộc tính before để đảm bảo hàm này chỉ viết hoa văn bản khi tạo lần đầu:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });