مشغلات قاعدة البيانات في الوقت الفعلي


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

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

  1. تنتظر التغييرات في موقع Realtime Database معيّن.
  2. يتم تشغيلها عند حدوث حدث وتنفيذ مهامها (راجِع ما الذي يمكنني فعله باستخدام Cloud Functions؟ للحصول على أمثلة على حالات الاستخدام).
  3. تتلقّى عنصر بيانات يحتوي على لقطة للبيانات المخزّنة في المستند المحدّد.

تشغيل دالة Realtime Database

إنشاء دوال جديدة لأحداث Realtime Database باستخدام functions.database. للتحكّم في وقت تفعيل الدالة، يجب تحديد أحد معالِجات الأحداث وتحديد مسار Realtime Database حيث يتم الاستماع إلى الأحداث.

ضبط معالِج الحدث

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

  • onWrite()، الذي يتم تشغيله عند إنشاء بيانات أو تعديلها أو حذفها في Realtime Database.
  • onCreate()، الذي يتم تشغيله عند إنشاء بيانات جديدة في Realtime Database
  • onUpdate()، الذي يتم تشغيله عند تعديل البيانات في Realtime Database
  • onDelete()، الذي يتم تشغيله عند حذف البيانات من Realtime Database

تحديد العنصر والمسار

للتحكّم في وقت ومكان تشغيل الدالة، استدعِ ref(path) لتحديد مسار، ويمكنك اختياريًا تحديد مثيل Realtime Database باستخدام instance('INSTANCE_NAME'). في حال عدم تحديد مثيل، يتم نشر الدالة على مثيل Realtime Database التلقائي لمشروع Firebase. على سبيل المثال:

  • مثيل Realtime Database التلقائي: functions.database.ref('/foo/bar')
  • النسخة التي تحمل الاسم "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

توجّه هذه الطرق دالتك للتعامل مع عمليات الكتابة في مسار معيّن ضمن مثيل Realtime Database. تتطابق مواصفات المسار مع جميع عمليات الكتابة التي تؤثر في مسار، بما في ذلك عمليات الكتابة التي تحدث في أي مكان تحته. إذا ضبطت المسار لوظيفتك على /foo/bar، تتم مطابقة الأحداث في كلا الموقعَين التاليَين:

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

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

ويمكنك تحديد مكوّن مسار كحرف بدل من خلال إحاطته بأقواس معقوفة. تتطابق السمة ref('foo/{bar}') مع أي عنصر فرعي من /foo. تتوفّر قيم مكونات مسار البدل هذه في عنصر EventContext.params لدالة. في هذا المثال، تتوفّر القيمة على النحو التالي: context.params.bar.

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

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

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

التعامل مع بيانات الأحداث

عند التعامل مع حدث Realtime Database، يكون عنصر البيانات المعروض هو DataSnapshot. بالنسبة إلى أحداث onWrite أو onUpdate، تكون المَعلمة الأولى هي عنصر Change يحتوي على لقطات مصوّرة تُمثّل حالة البيانات قبل الحدث المشغِّل وبعده. بالنسبة إلى حدثَي 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/v1');
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 حيث تشير كل ال methods (على سبيل المثال، val() و exists()) إلى القيمة السابقة. يمكنك الاطّلاع على القيمة الجديدة من جديد إما باستخدام DataSnapshot الأصلية أو قراءة السمة after. هذه السمة في أيّ Change هي DataSnapshot أخرى تمثّل حالة البيانات بعد وقوع الحدث.

على سبيل المثال، يمكن استخدام السمة before للتأكّد من أنّ الدالة تحول النص إلى uppercase فقط عند إنشائه لأول مرة:

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