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


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

في دورة الحياة النموذجية، تنفّذ دالة قاعدة بيانات Firebase في الوقت الفعلي ما يلي:

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

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

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

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

تتيح لك الدوال معالجة أحداث قاعدة بيانات الوقت الفعلي على مستويَين من الخصوصية؛ يمكنك الاستماع إلى أحداث الإنشاء أو التحديث أو الحذف فقط، أو يمكنك معرفة أي تغيير من أي نوع في المسار. تدعم دوال Cloud معالِجات الأحداث التالية لقاعدة بيانات الوقت الفعلي:

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

تحديد المثيل والمسار

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

  • مثيل قاعدة بيانات الوقت الفعلي التلقائية: functions.database.ref('/foo/bar')
  • مثيل باسم "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

توجه هذه الطرق الدالة لديك لمعالجة عمليات الكتابة في مسار معين داخل مثيل قاعدة بيانات الوقت الفعلي. تتطابق مواصفات المسار مع جميع عمليات الكتابة التي تلمس أي مسار، بما في ذلك عمليات الكتابة التي تحدث في أي مكان أسفله. إذا ضبطت مسار الدالة على /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".

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

عند التعامل مع حدث في قاعدة بيانات الوقت الفعلي، يكون عنصر البيانات الذي يتم عرضه هو 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');
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 التي تتيح لك فحص ما تم حفظه في قاعدة بيانات الوقت الفعلي قبل الحدث. تعرض السمة 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);
    });