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


باستخدام 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 للتأكّد من كتابة الدالة بأحرف كبيرة فقط عند إنشائها لأول مرة:

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