ขยาย Realtime Database ด้วย Cloud Functions


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

ในวงจรปกติ ฟังก์ชัน Firebase Realtime Database จะทําสิ่งต่อไปนี้

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

คุณสามารถทริกเกอร์ฟังก์ชันเพื่อตอบสนองต่อการเขียน การสร้าง การอัปเดต หรือลบโหนดฐานข้อมูลใน Firebase Realtime Database หากต้องการควบคุมเวลาเรียกใช้ฟังก์ชัน ให้ระบุตัวแฮนเดิลเหตุการณ์รายการใดรายการหนึ่ง และระบุเส้นทาง Realtime Database ที่ฟังเหตุการณ์

การตั้งค่าตําแหน่งฟังก์ชัน

ระยะห่างระหว่างตำแหน่งของอินสแตนซ์ Realtime Database กับตำแหน่งของฟังก์ชันอาจทำให้เกิดเวลาในการตอบสนองของเครือข่ายที่สูง นอกจากนี้ ความไม่ตรงกันระหว่างภูมิภาคอาจส่งผลให้การติดตั้งใช้งานไม่สำเร็จ หากต้องการหลีกเลี่ยงสถานการณ์เหล่านี้ ให้ระบุตำแหน่งฟังก์ชันเพื่อให้ตรงกับตำแหน่งอินสแตนซ์ฐานข้อมูล

การจัดการเหตุการณ์ Realtime Database

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

ตัวแฮนเดิลเหล่านี้สําหรับการตอบสนองต่อเหตุการณ์ Realtime Database พร้อมใช้งาน

Node.jsPython
  • onValueWritten() ทริกเกอร์เมื่อสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • onValueCreated() จะทริกเกอร์เมื่อสร้างข้อมูลใน Realtime Database เท่านั้น
  • onValueUpdated() จะทริกเกอร์เฉพาะเมื่อมีอัปเดตข้อมูลใน Realtime Database
  • onValueDeleted() จะทริกเกอร์เฉพาะเมื่อมีการขลบข้อมูลใน Realtime Database
  • on_value_written() ทริกเกอร์เมื่อสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • on_value_created() จะทริกเกอร์เมื่อสร้างข้อมูลใน Realtime Database เท่านั้น
  • on_value_updated() จะทริกเกอร์เฉพาะเมื่อมีอัปเดตข้อมูลใน Realtime Database
  • on_value_deleted() จะทริกเกอร์เฉพาะเมื่อมีการขลบข้อมูลใน Realtime Database

นําเข้าโมดูลที่จําเป็น

ในแหล่งที่มาของฟังก์ชัน คุณต้องนําเข้าโมดูล SDK ที่ต้องการใช้ สําหรับตัวอย่างนี้ คุณต้องนําเข้าโมดูล HTTP และ Realtime Database พร้อมกับโมดูล Firebase Admin SDK เพื่อนําเข้าข้อมูลไปยัง Realtime Database

Node.jsPython
// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

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

หากต้องการควบคุมเวลาและตําแหน่งที่ฟังก์ชันควรทริกเกอร์ ให้กําหนดค่าฟังก์ชันด้วยเส้นทางและอินสแตนซ์ Realtime Database (ไม่บังคับ) หากคุณไม่ได้ระบุอินสแตนซ์ ฟังก์ชันจะฟังอินสแตนซ์ Realtime Database ทั้งหมดในภูมิภาคของฟังก์ชัน นอกจากนี้ คุณยังระบุรูปแบบอินสแตนซ์ Realtime Database เพื่อนำไปติดตั้งใช้งานกับอินสแตนซ์ชุดย่อยที่เลือกในภูมิภาคเดียวกันได้ด้วย

เช่น

Node.jsPython
// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

พารามิเตอร์เหล่านี้จะกำหนดให้ฟังก์ชันจัดการการเขียนในบางเส้นทางภายในอินสแตนซ์ Realtime Database

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

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

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

ไวลด์การ์ดและการจับ

คุณใช้ {key}, {key=*}, {key=prefix*}, {key=*suffix}ในการจับภาพได้ *, prefix*, *suffix สำหรับไวลด์การ์ดแบบกลุ่มเดียว หมายเหตุ: ** แสดงไวลด์การ์ดหลายกลุ่ม ซึ่ง Realtime Database ไม่รองรับ โปรดดูทําความเข้าใจรูปแบบเส้นทาง

ไวลด์การ์ดเส้นทาง คุณระบุคอมโพเนนต์เส้นทางเป็นไวลด์การ์ดได้ ดังนี้

  • ใช้เครื่องหมายดอกจัน * ตัวอย่างเช่น foo/* จะจับคู่กับโหนดย่อยระดับ 1 ของลําดับชั้นโหนดที่อยู่ด้านล่าง foo/
  • การใช้กลุ่มที่มีเครื่องหมายดอกจัน * เท่านั้น เช่น foo/app*-us จะจับคู่กับกลุ่มย่อยที่อยู่ใต้ foo/ ที่มีคำนำหน้า app และคำต่อท้าย -us

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

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

จับคู่เส้นทาง "/foo/*" 2 ครั้ง ได้แก่ 1 ครั้งกับ "hello": "world" และอีก 1 ครั้งกับ "firebase": "functions"

การบันทึกเส้นทาง คุณสามารถบันทึกเส้นทางที่ตรงกันในตัวแปรที่มีชื่อเพื่อใช้ในโค้ดฟังก์ชัน (เช่น /user/{uid}, /user/{uid=*-us})

ค่าของตัวแปรการบันทึกจะอยู่ในออบเจ็กต์ database.DatabaseEvent.params ของฟังก์ชัน

การใช้ไวลด์การ์ดกับอินสแตนซ์ คุณยังระบุคอมโพเนนต์อินสแตนซ์โดยใช้ไวลด์การ์ดได้ด้วย ไวลด์การ์ดอินสแตนซ์อาจมีคำนำหน้า คำต่อท้าย หรือทั้ง 2 อย่างก็ได้ (เช่น my-app-*-prod)

ไวลด์การ์ดและการอ้างอิงการบันทึก

เมื่อใช้ Cloud Functions (รุ่นที่ 2) และ Realtime Database คุณจะใช้รูปแบบได้เมื่อระบุ ref และ instance อินเทอร์เฟซทริกเกอร์แต่ละรายการจะมีตัวเลือกต่อไปนี้สําหรับการกำหนดขอบเขตฟังก์ชัน

การระบุ ref การระบุ instance พฤติกรรม
เดี่ยว (/foo/bar) ไม่ระบุ กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ทั้งหมดในภูมิภาคฟังก์ชัน
เดี่ยว (/foo/bar) เดี่ยว (‘my-new-db') กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ที่เฉพาะเจาะจงในภูมิภาคของฟังก์ชัน
เดี่ยว (/foo/bar) รูปแบบ (‘inst-prefix*') กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ทั้งหมดที่ตรงกับรูปแบบในภูมิภาคฟังก์ชัน
รูปแบบ (/foo/{bar}) ไม่ระบุ กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ทั้งหมดในภูมิภาคฟังก์ชัน
รูปแบบ (/foo/{bar}) เดี่ยว (‘my-new-db') กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ที่เฉพาะเจาะจงในภูมิภาคของฟังก์ชัน
รูปแบบ (/foo/{bar}) รูปแบบ (‘inst-prefix*') กําหนดขอบเขตตัวแฮนเดิลไปยังอินสแตนซ์ทั้งหมดที่ตรงกับรูปแบบในภูมิภาคฟังก์ชัน

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

เมื่อเหตุการณ์ Realtime Database เรียกให้แสดง ระบบจะส่งออบเจ็กต์ Event ไปยังฟังก์ชันแฮนเดิล ออบเจ็กต์นี้มีพร็อพเพอร์ตี้ data ซึ่งสําหรับเหตุการณ์การสร้างและการลบ จะมีสแนปชอตของข้อมูลที่สร้างขึ้นหรือลบ

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

Node.jsPython
// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

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

สําหรับเหตุการณ์ write หรือ update พร็อพเพอร์ตี้ data จะเป็นออบเจ็กต์ Change ที่มีภาพรวม 2 รายการ ซึ่งแสดงสถานะข้อมูลก่อนและหลังเหตุการณ์เรียกให้แสดง ออบเจ็กต์ Change มีพร็อพเพอร์ตี้ before ที่ช่วยให้คุณตรวจสอบสิ่งที่บันทึกไว้ใน Realtime Database ก่อนเหตุการณ์ และพร็อพเพอร์ตี้ after ที่แสดงสถานะของข้อมูลหลังจากเกิดเหตุการณ์

เช่น คุณสามารถใช้พร็อพเพอร์ตี้ before เพื่อให้แน่ใจว่าฟังก์ชันจะเปลี่ยนเฉพาะข้อความเป็นอักษรตัวพิมพ์ใหญ่เมื่อสร้างครั้งแรกเท่านั้น ดังนี้

Node.jsPython
  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.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 event.data.after.ref.parent.child('uppercase').set(uppercase);
      });
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)