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


باستخدام Cloud Functions، يمكنك معالجة الأحداث في Firebase Realtime Database بدون الحاجة إلى تعديل رمز العميل. يتيح لك Cloud Functions تنفيذ عمليات Realtime Database باستخدام امتيازات إدارية كاملة، ويضمن معالجة كل تغيير في Realtime Database بشكلٍ فردي. يمكنك إجراء تغييرات Firebase Realtime Database من خلال DataSnapshot أو من خلال 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);
    });