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


باستخدام 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 حيث تشير جميع الطرق (مثل 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);
    });