Memperluas Realtime Database dengan Cloud Functions

Dengan Cloud Functions, Anda dapat menangani peristiwa di Firebase Realtime Database tanpa harus memperbarui kode klien. Cloud Functions dapat Anda gunakan untuk menjalankan operasi database dengan hak istimewa administratif penuh, dan memastikan setiap perubahan di database diproses secara terpisah. Anda dapat membuat perubahan pada Firebase Realtime Database melalui DataSnapshot atau melalui Admin SDK.

Dalam siklus proses umum, fungsi Firebase Realtime Database melakukan hal-hal berikut:

  1. Menunggu perubahan pada lokasi database tertentu.
  2. Cloud Functions akan terpicu ketika suatu peristiwa terjadi dan menjalankan tugasnya (untuk mengetahui contoh kasus penggunaan, baca Apa yang dapat saya lakukan dengan Cloud Functions?).
  3. Menerima objek data yang berisi snapshot data yang disimpan dalam dokumen yang ditentukan.

Memicu fungsi database

Membuat fungsi baru untuk peristiwa Realtime Database dengan functions.database. Untuk mengontrol kapan fungsi terpicu, tentukan salah satu pengendali peristiwa dan tentukan jalur database tempat peristiwa akan dideteksi.

Mengatur pengendali peristiwa

Dengan Functions, Anda dapat mengendalikan peristiwa database di dua tingkat kekhususan; Anda dapat memantau peristiwa pembuatan, pembaruan, atau penghapusan secara khusus, atau Anda dapat memantau perubahan apa pun ke jalur. Cloud Functions mendukung pengendali peristiwa ini untuk Realtime Database:

  • onWrite(), yang terpicu saat data dibuat, diupdate, atau dihapus di Realtime Database.
  • onCreate(), yang terpicu saat data baru dibuat di Realtime Database.
  • onUpdate(), yang terpicu saat data baru dibuat di Realtime Database.
  • onDelete(), yang terpicu saat data baru dibuat di Realtime Database.

Menentukan instance dan jalur database

Untuk mengontrol kapan dan di mana fungsi Anda terpicu, panggil ref(path) untuk menentukan jalur, dan menentukan instance database dengan instance('INSTANCE_NAME') secara opsional. Jika Anda tidak menentukan instance, fungsi tersebut akan diterapkan ke instance database default untuk project Firebase. Misalnya:

  • Instance database default: functions.database.ref('/foo/bar')
  • Instance dengan nama "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Metode ini akan mengarahkan fungsi Anda untuk menangani penulisan di jalur tertentu dalam instance database. Spesifikasi jalur mencocokkan semua penulisan yang ada di jalur tertentu serta jalur mana pun di bawahnya. Jika Anda menetapkan /foo/bar sebagai jalur fungsi, maka peristiwa di kedua jalur ini akan dicocokkan:

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

Bagaimanapun juga, Firebase menafsirkan bahwa peristiwa terjadi di /foo/bar, dan data peristiwa menyertakan data lama dan baru di . Jika data peristiwa kemungkinan berukuran besar, pertimbangkan untuk menggunakan beberapa fungsi di jalur yang lebih dalam, bukan fungsi tunggal di dekat root database Anda. Untuk mendapatkan performa terbaik, hanya minta data di level sedalam mungkin.

Anda dapat menentukan komponen jalur sebagai karakter pengganti dengan mengurungnya menggunakan tanda kurung kurawal; ref('foo/{bar}') mencocokkan turunan apa pun dari /foo. Nilai komponen jalur karakter pengganti ini tersedia dalam objek EventContext.params di fungsi Anda. Dalam contoh ini, nilainya tersedia sebagai event.params.bar.

Jalur dengan karakter pengganti dapat mencocokkan beberapa peristiwa dari satu penulisan. Sisipan

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

mencocokkan jalur "/foo/{bar}" dua kali: sekali dengan "hello": "world" dan sekali lagi dengan "firebase": "functions".

Menangani data peristiwa

Saat menangani peristiwa Realtime Database, objek data yang ditampilkan adalah DataSnapshot. Untuk peristiwa onWrite atau onUpdate, parameter pertama adalah objek Change yang berisi dua snapshot yang mewakili status data sebelum dan setelah peristiwa pemicu. Untuk peristiwa onCreate dan onDelete, objek data yang ditampilkan adalah snapshot dari data yang dibuat atau dihapus.

Dalam contoh ini, fungsi mengambil snapshot untuk jalur yang ditentukan sebagai snap, mengubah string di lokasi tersebut menjadi huruf kapital, dan menulis string yang telah diubah tersebut ke database:

// 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();
      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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Mengakses informasi autentikasi pengguna

Dari EventContext.auth dan EventContext.authType, Anda dapat mengakses informasi pengguna, termasuk izin, untuk pengguna yang memicu suatu fungsi. Tindakan ini dapat digunakan untuk menegakkan aturan keamanan sehingga fungsi Anda dapat menyelesaikan berbagai operasi sesuai dengan tingkat izin pengguna:

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

Anda juga dapat memanfaatkan informasi autentikasi pengguna untuk "meniru identitas" pengguna dan melakukan operasi tulis atas nama pengguna tersebut. Pastikan untuk menghapus instance aplikasi seperti yang ditunjukkan di bawah ini untuk mencegah masalah serentak:

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

Membaca nilai sebelumnya

Objek Change memiliki properti before yang dapat digunakan untuk memeriksa apa saja yang telah disimpan ke database sebelum peristiwa. Properti before menampilkan DataSnapshot yang semua metodenya (misalnya, val() dan exists()) mengacu ke nilai sebelumnya. Anda dapat membaca kembali nilai baru tersebut menggunakan DataSnapshot asli atau membaca properti after. Properti ini di setiap Change adalah DataSnapshot lain yang mewakili status data setelah peristiwa tersebut terjadi.

Misalnya, properti before dapat digunakan untuk memastikan bahwa fungsi hanya berupa teks huruf kapital ketika pertama kali dibuat:

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