ทริกเกอร์ Realtime Database


Cloud Functions จะช่วยให้คุณจัดการเหตุการณ์ในฐานข้อมูลเรียลไทม์ของ Firebase ได้โดยไม่ต้องอัปเดตโค้ดไคลเอ็นต์ Cloud Functions ช่วยให้คุณเรียกใช้การดำเนินการของ Realtime Database ด้วยสิทธิ์ของผู้ดูแลระบบโดยสมบูรณ์ และดูแลให้การเปลี่ยนแปลงที่เกิดขึ้นกับ Realtime Database แต่ละรายการได้รับการประมวลผลทีละรายการ คุณเปลี่ยนแปลงฐานข้อมูลเรียลไทม์ของ Firebase ได้ผ่าน DataSnapshot หรือผ่าน Admin SDK

ในวงจรทั่วไป ฟังก์ชันฐานข้อมูลเรียลไทม์ของ Firebase จะดำเนินการต่อไปนี้

  1. รอให้มีการเปลี่ยนแปลงตำแหน่งของ Realtime Database ที่เฉพาะเจาะจง
  2. ทริกเกอร์เมื่อเหตุการณ์เกิดขึ้นและทํางานของเหตุการณ์นั้น (ดูตัวอย่าง Use Case ที่หัวข้อฉันจะทําอะไรกับ Cloud Functions ได้บ้าง)
  3. รับออบเจ็กต์ข้อมูลที่มีสแนปชอตของข้อมูลที่จัดเก็บไว้ในเอกสารที่ระบุ

ทริกเกอร์ฟังก์ชัน Realtime Database

สร้างฟังก์ชันใหม่ให้กับเหตุการณ์ Realtime Database ด้วย functions.database หากต้องการควบคุมเวลาที่ฟังก์ชันจะทริกเกอร์ ให้ระบุตัวแฮนเดิลเหตุการณ์ และระบุเส้นทาง Realtime Database ที่ระบบจะคอยตรวจจับเหตุการณ์

ตั้งค่าเครื่องจัดการเหตุการณ์

ฟังก์ชันต่างๆ ช่วยให้คุณจัดการเหตุการณ์ Realtime Database ได้ในระดับความจำ 2 ระดับ โดยคุณสามารถฟังเฉพาะเหตุการณ์การสร้าง อัปเดต หรือการลบ หรือจะฟังการเปลี่ยนแปลงใดๆ ก็ตามในเส้นทางก็ได้ Cloud Functions รองรับตัวแฮนเดิลเหตุการณ์สำหรับฐานข้อมูลเรียลไทม์ต่อไปนี้

  • onWrite() ซึ่งจะทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • onCreate() ซึ่งจะทริกเกอร์เมื่อมีการสร้างข้อมูลใหม่ใน Realtime Database
  • onUpdate() ซึ่งจะทำงานเมื่ออัปเดตข้อมูลใน Realtime Database
  • onDelete() ซึ่งจะปรากฏเมื่อลบข้อมูลออกจาก Realtime Database

ระบุอินสแตนซ์และเส้นทาง

หากต้องการควบคุมเวลาและตำแหน่งที่ฟังก์ชันควรทริกเกอร์ ให้เรียกใช้ ref(path) เพื่อระบุเส้นทาง และอาจระบุอินสแตนซ์ Realtime Database ด้วย instance('INSTANCE_NAME') (ไม่บังคับ) หากไม่ระบุอินสแตนซ์ ฟังก์ชันดังกล่าวจะทำให้ใช้งานได้กับอินสแตนซ์ฐานข้อมูลเรียลไทม์เริ่มต้นของโปรเจ็กต์ Firebase เช่น

  • อินสแตนซ์ฐานข้อมูลเรียลไทม์เริ่มต้น: functions.database.ref('/foo/bar')
  • อินสแตนซ์ชื่อ "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

วิธีการเหล่านี้จะกำหนดให้ฟังก์ชันจัดการการเขียนในบางเส้นทางภายในอินสแตนซ์ Realtime Database ข้อกำหนดเส้นทางตรงกับการเขียนทั้งหมดที่แตะเส้นทาง รวมถึงการเขียนที่เกิดขึ้นที่ใดก็ได้ด้านล่าง หากกำหนดเส้นทางสำหรับฟังก์ชันเป็น /foo/bar เส้นทางดังกล่าวจะตรงกับเหตุการณ์ที่ทั้ง 2 ตำแหน่งนี้

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

ในทั้ง 2 กรณี Firebase จะตีความว่าเหตุการณ์เกิดขึ้นที่ /foo/bar และข้อมูลเหตุการณ์จะรวมข้อมูลเก่าและใหม่ที่ /foo/bar หากข้อมูลเหตุการณ์อาจมีขนาดใหญ่ ให้พิจารณาใช้ฟังก์ชันหลายฟังก์ชันในเส้นทางที่ลึกขึ้น แทนการใช้ฟังก์ชันเดียวที่อยู่ใกล้กับรูทของฐานข้อมูล เพื่อประสิทธิภาพที่ดีที่สุด ให้ขอเฉพาะข้อมูลในระดับที่ลึกที่สุดเท่านั้น

คุณระบุคอมโพเนนต์เส้นทางเป็นไวลด์การ์ดได้ล้อมรอบด้วยวงเล็บปีกกา โดย ref('foo/{bar}') จะจับคู่คอมโพเนนต์ย่อยของ /foo ค่าของคอมโพเนนต์เส้นทางไวลด์การ์ดเหล่านี้จะอยู่ในออบเจ็กต์ EventContext.params ของฟังก์ชัน ในตัวอย่างนี้ ค่าจะแสดงเป็น context.params.bar

เส้นทางที่มีไวลด์การ์ดสามารถจับคู่หลายเหตุการณ์จากการเขียนครั้งเดียว ส่วนแทรกของ

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

จับคู่เส้นทาง "/foo/{bar}" 2 ครั้ง โดยครั้งแรกกับ "hello": "world" และอีกครั้งด้วย "firebase": "functions"

จัดการข้อมูลเหตุการณ์

เมื่อจัดการเหตุการณ์ Realtime Database ออบเจ็กต์ข้อมูลที่แสดงผลคือ DataSnapshot สำหรับเหตุการณ์ onWrite หรือ onUpdate พารามิเตอร์แรกคือออบเจ็กต์ Change ที่มีสแนปชอต 2 รายการที่แสดงสถานะข้อมูลก่อนและหลังเหตุการณ์ทริกเกอร์ สำหรับเหตุการณ์ onCreate และ onDelete ออบเจ็กต์ข้อมูลที่แสดงผลเป็นภาพรวมของข้อมูลที่สร้างหรือลบ

ในตัวอย่างนี้ ฟังก์ชันจะเรียกสแนปชอตสำหรับเส้นทางที่ระบุ แปลงสตริงที่ตำแหน่งนั้นเป็นตัวพิมพ์ใหญ่ และเขียนสตริงที่แก้ไขแล้วไปยังฐานข้อมูล

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

การเข้าถึงข้อมูลการตรวจสอบสิทธิ์ผู้ใช้

จาก EventContext.auth และ EventContext.authType คุณสามารถเข้าถึงข้อมูลผู้ใช้ รวมถึงสิทธิ์สำหรับผู้ใช้ที่เรียกใช้ฟังก์ชัน ซึ่งจะมีประโยชน์ในการบังคับใช้กฎความปลอดภัย ทำให้ฟังก์ชันของคุณดำเนินการต่างๆ ตามระดับสิทธิ์ของผู้ใช้ได้

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

นอกจากนี้คุณยังใช้ประโยชน์จากข้อมูลการตรวจสอบสิทธิ์ผู้ใช้เพื่อ "แอบอ้าง" ผู้ใช้และดำเนินการเขียนในนามของผู้ใช้ได้อีกด้วย อย่าลืมลบอินสแตนซ์ของแอปตามที่แสดงด้านล่างเพื่อป้องกันปัญหาการเกิดขึ้นพร้อมกัน

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

การอ่านค่าก่อนหน้า

ออบเจ็กต์ Change มีพร็อพเพอร์ตี้ before ที่ให้คุณตรวจสอบสิ่งที่บันทึกไว้ใน Realtime Database ก่อนเหตุการณ์ พร็อพเพอร์ตี้ before จะแสดงผล DataSnapshot โดยที่เมธอดทั้งหมด (เช่น val() และ exists()) อ้างอิงถึงค่าก่อนหน้า คุณจะอ่านค่าใหม่อีกครั้งได้โดยใช้ DataSnapshot เดิมหรืออ่านพร็อพเพอร์ตี้ after พร็อพเพอร์ตี้นี้ใน Change ใดก็ได้เป็น DataSnapshot อีกรายการที่แสดงสถานะของข้อมูลหลังเหตุการณ์เกิดขึ้น

เช่น สามารถใช้พร็อพเพอร์ตี้ before เพื่อให้ฟังก์ชันเป็นข้อความตัวพิมพ์ใหญ่เมื่อสร้างครั้งแรกเท่านั้น

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