توسيع قاعدة بيانات الوقت الفعلي باستخدام دوال السحابة


باستخدام Cloud Functions، يمكنك معالجة الأحداث في Firebase Realtime Database بدون الحاجة إلى تعديل رمز العميل. يتيح لك Cloud Functions تنفيذ عمليات Realtime Database باستخدام امتيازات إدارية كاملة، ويضمن معالجة كل تغيير في Realtime Database بشكلٍ فردي. يمكنك إجراء Firebase Realtime Database تغييرات من خلال لقطة البيانات أو من خلال "حزمة تطوير البرامج (SDK) للمشرف".

في دورة حياة نموذجية، تُجري دالة Firebase Realtime Database ما يلي:

  1. ينتظر حدوث تغييرات في مسار Realtime Database معيّن.
  2. يتم تشغيلها عند حدوث حدث وتنفيذ مهامها.
  3. تتلقّى عنصر بيانات يحتوي على لقطة للبيانات المخزّنة في هذا المسار.

يمكنك تنشيط دالة استجابةً لكتابة عقد قاعدة البيانات أو إنشائها أو تعديلها أو حذفها في Firebase Realtime Database. للتحكّم في وقت بدء الدالة، حدِّد أحد معالِجات الأحداث، وحدِّد مسار Realtime Database الذي ستستمع فيه إلى الأحداث.

ضبط موقع الدالة

يمكن أن تؤدي المسافة بين موقع مثيل Realtime Database وموقع دالة إلى زيادة وقت استجابة الشبكة بشكل كبير. بالإضافة إلى ذلك، يمكن أن يؤدي عدم التطابق بين المناطق إلى تعذُّر النشر. لتجنُّب هذه الحالات، حدِّد موقع الدالة بحيث يتطابق مع موقع مثيل قاعدة البيانات.

التعامل مع أحداث Realtime Database

تتيح لك الدوالّ التعامل مع أحداث Realtime Database على مستويَين من التحديد: يمكنك الاستماع تحديدًا إلى أحداث الكتابة أو الإنشاء أو التعديل أو الحذف فقط، أو يمكنك الاستماع إلى أي تغيير من أي نوع على مرجع.

تتوفّر معالِجات الردّ على أحداث 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، تتم مطابقة الأحداث في كلا الموقعَين التاليَين:

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

في كلتا الحالتَين، تفسّر Firebase أنّ الحدث يحدث في /foo/bar، وتتضمّن بيانات الحدث البيانات القديمة والجديدة في /foo/bar. إذا كانت بيانات الحدث قد تكون كبيرة، ننصحك باستخدام دوال متعددة في مسارات أعمق بدلاً من دالة واحدة بالقرب من جذر قاعدة بياناتك. للحصول على أفضل أداء، اطلب البيانات على أدنى مستوى ممكن فقط.

استخدام أحرف البدل والاحتفاظ بها

يمكنك استخدام {key} و{key=*} و{key=prefix*} و{key=*suffix} لالتقاط المحتوى. ‫* وprefix* و*suffix للاستخدام مع العنصر النائب المكوّن من عنصر واحد ملاحظة: يمثّل ** أحرف البدل المتعدّدة الشرائح، وهي غير متاحة في Realtime Database. راجِع فهم أنماط المسارات.

استخدام أحرف البدل في المسارات يمكنك تحديد مكوّن مسار كحرف بدل:

  • باستخدام علامة النجمة *. على سبيل المثال، يتطابق foo/* مع أي عناصر فرعية في مستوى واحد من التسلسل الهرمي للعقدة أسفل foo/.
  • استخدام شريحة تحتوي على علامة النجمة * بالضبط على سبيل المثال، يتطابق foo/app*-us مع أيّ شرائح فرعية أسفل foo/ باستخدام البادئة app واللاحقة -us.

يمكن أن تتطابق المسارات التي تحتوي على أحرف متسلسلة بديلة مع أحداث متعددة من عملية تسجيل واحدة مثلاً. إدراج

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

يطابق المسار "/foo/*" مرتين: مرة مع "hello": "world" ومرة أخرى مع "firebase": "functions".

التقاط المسار: يمكنك تسجيل مطابقات المسارات في متغيّرات مُسمّاة لاستخدامها في رمز الدالة (مثل /user/{uid} و/user/{uid=*-us}).

تتوفّر قيم متغيّرات الالتقاط ضمن كائن database.DatabaseEvent.params لدالة.

استخدام أحرف البدل في النماذج يمكنك أيضًا تحديد عنصر مثيل باستخدام أحرف البدل. يمكن أن يحتوي حرف البدل للعنصر على بادئة أو لاحقة أو كليهما (مثل my-app-*-prod).

مرجع أحرف البدل وعمليات الالتقاط

باستخدام Cloud Functions (الجيل الثاني) و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 يحتوي على لقطات مصوّرة تمثّل حالة البيانات قبل الحدث المشغِّل وبعده. يحتوي العنصر 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)