مشغِّلات Cloud Firestore

باستخدام Cloud Functions، يمكنك معالجة الأحداث في Cloud Firestore بدون الحاجة إلى تعديل رمز العميل. يمكنك إجراء تغييرات على Cloud Firestore من خلال واجهة لقطة المستند أو من خلال مدير SDK.

في مراحل النشاط العادية، تنفّذ دالة Cloud Firestore ما يلي:

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

يمكن أن تؤدي المسافة بين موقع مثيل Firestore وموقع الدالة إلى وقت استجابة كبير في الشبكة. لتحسين الأداء، ننصحك بتحديد موقع الدالة حيثما ينطبق ذلك.

مشغّلات وظائف Cloud Firestore

تصدِّر حزمة تطوير البرامج (SDK) في Cloud Functions for Firebase عوامل تشغيل أحداث Cloud Firestore التالية للسماح لك بإنشاء معالِجات مرتبطة بأحداث Cloud Firestore معيّنة:

Node.js

نوع الحدث عامل التفعيل
onDocumentCreated يتم تشغيلها عند كتابة مستند لأول مرة.
onDocumentUpdated يتم تفعيل هذا المشغّل عندما يكون المستند موجودًا مسبقًا وتم تغيير أي قيمة فيه.
onDocumentDeleted يتم تفعيله عند حذف مستند.
onDocumentWritten يتم تفعيل هذا الإجراء عند تفعيل onDocumentCreated أو onDocumentUpdated أو onDocumentDeleted.
onDocumentCreatedWithAuthContext ‫onDocumentCreated مع معلومات مصادقة إضافية
onDocumentWrittenWithAuthContext ‫onDocumentWritten مع معلومات مصادقة إضافية
onDocumentDeletedWithAuthContext ‫onDocumentDeleted مع معلومات مصادقة إضافية
onDocumentUpdatedWithAuthContext ‫onDocumentUpdated مع معلومات مصادقة إضافية

Python

نوع الحدث عامل التفعيل
on_document_created يتم تشغيلها عند كتابة مستند لأول مرة.
on_document_updated يتم تفعيل هذا المشغّل عندما يكون المستند موجودًا مسبقًا وتم تغيير أي قيمة فيه.
on_document_deleted يتم تفعيله عند حذف مستند.
on_document_written يتم تفعيل هذا الإجراء عند تفعيل on_document_created أو on_document_updated أو on_document_deleted.
on_document_created_with_auth_context ‫on_document_created مع معلومات مصادقة إضافية
on_document_updated_with_auth_context ‫on_document_updated مع معلومات مصادقة إضافية
on_document_deleted_with_auth_context ‫on_document_deleted مع معلومات مصادقة إضافية
on_document_written_with_auth_context ‫on_document_written مع معلومات مصادقة إضافية

يتم تشغيل أحداث Cloud Firestore فقط عند تغيير المستندات. لا يؤدي تعديل مستند Cloud Firestore حيث لا تتغير البيانات (عملية كتابة بدون إجراء) إلى إنشاء حدث تعديل أو كتابة. لا يمكن إضافة أحداث إلى حقول معيّنة.

إذا لم يكن لديك مشروع مفعَّل لـ Cloud Functions for Firebase بعد، يُرجى قراءة بدء استخدام Cloud Functions for Firebase (الجيل الثاني) لضبط مشروع Cloud Functions for Firebase وإعداده.

كتابة دوال يتم تشغيلها من خلال Cloud Firestore

تحديد عامل تفعيل دالة

لتحديد مشغّل Cloud Firestore، حدِّد مسار مستند ونوع حدث:

Node.js

const {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

يمكن أن تشير مسارات المستندات إلى مستند معيّن أو نمط حرف بدل.

تحديد مستند واحد

إذا أردت تشغيل حدث لأي تغيير في مستند معيّن، يمكنك استخدام الدالة التالية.

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

تحديد مجموعة من المستندات باستخدام أحرف البدل

إذا أردت ربط مشغّل بمجموعة من المستندات، مثل أي مستند في مجموعة معيّنة، استخدِم {wildcard} بدلاً من معرّف المستند:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

في هذا المثال، عندما يتم تغيير أي حقل في أي مستند في users، يتطابق مع حرف بدل يُسمى userId.

إذا كان المستند في users يحتوي على مجموعات فرعية وتم تغيير حقل في أحد مستندات هذه المجموعات الفرعية، لا يتم تفعيل حرف البدل userId.

يتم استخراج التطابقات التي تتضمّن أحرف بدل من مسار المستند وتخزينها في event.params. يمكنك تحديد أي عدد تريده من أحرف البدل لاستبدال معرّفات المجموعات أو المستندات الصريحة، على سبيل المثال:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

يجب أن يشير المشغّل دائمًا إلى مستند، حتى إذا كنت تستخدم حرف بدل. على سبيل المثال، users/{userId}/{messageCollectionId} غير صالح لأنّ {messageCollectionId} هو مجموعة. ومع ذلك، فإنّ users/{userId}/{messageCollectionId}/{messageId} صالح لأنّ {messageId} سيشير دائمًا إلى مستند.

مشغّلات الأحداث

تفعيل دالة عند إنشاء مستند جديد

يمكنك تفعيل دالة لتشغيلها في أي وقت يتم فيه إنشاء مستند جديد في مجموعة. يتم تشغيل دالة المثال هذه في كل مرة تتم فيها إضافة ملف شخصي جديد للمستخدم:

Node.js

const {
  onDocumentCreated,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // access a particular field as you would any JS property
    const name = data.name;

    // perform more operations ...
});

للحصول على معلومات مصادقة إضافية، استخدِم onDocumentCreatedWithAuthContext.

Python

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

تفعيل دالة عند تعديل مستند

يمكنك أيضًا تشغيل دالة ليتم تنفيذها عند تعديل مستند. يتم تشغيل دالة المثال هذه إذا غيّر المستخدم ملفه الشخصي:

Node.js

const {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

    // access a particular field as you would any JS property
    const name = newValue.name;

    // perform more operations ...
});

للحصول على معلومات مصادقة إضافية، استخدِم onDocumentUpdatedWithAuthContext.

Python

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

تشغيل دالة عند حذف مستند

يمكنك أيضًا تفعيل دالة عند حذف مستند. يتم تشغيل دالة المثال هذه عندما يحذف المستخدم ملف المستخدم الشخصي:

Node.js

const {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

للحصول على معلومات مصادقة إضافية، استخدِم onDocumentDeletedWithAuthContext.

Python

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

تفعيل دالة لجميع التغييرات في مستند

إذا لم تكن مهتمًا بنوع الحدث الذي يتم تشغيله، يمكنك الاستماع إلى جميع التغييرات في مستند Cloud Firestore باستخدام مشغّل حدث "تمت كتابة المستند". يتم تشغيل دالة المثال هذه في حال إنشاء مستخدم أو تعديله أو حذفه:

Node.js

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('firebase-functions/v2/firestore');

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

للحصول على معلومات مصادقة إضافية، استخدِم onDocumentWrittenWithAuthContext.

Python

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

قراءة البيانات وكتابتها

عند تشغيل دالة، فإنها توفر لقطة من البيانات المتعلقة بالحدث. يمكنك استخدام هذه اللقطة للقراءة من المستند الذي أدّى إلى بدء الحدث أو الكتابة فيه، أو استخدام مدير SDK في Firebase للوصول إلى أجزاء أخرى من قاعدة البيانات.

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

بيانات القراءة

عند تشغيل دالة، قد تحتاج إلى الحصول على بيانات من مستند تم تعديله أو الحصول على البيانات قبل التعديل. يمكنك الحصول على البيانات السابقة باستخدام event.data.before، الذي يحتوي على لقطة المستند قبل التعديل. وبالمثل، يحتوي event.data.after على حالة لقطة المستند بعد التحديث.

Node.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

يمكنك الوصول إلى الخصائص كما تفعل في أي كائن آخر. بدلاً من ذلك، يمكنك استخدام الدالة get للوصول إلى حقول معيّنة:

Node.js

// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];

// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');

Python

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

كتابة البيانات

يرتبط كل استدعاء دالة بمستند معيّن في قاعدة بيانات Cloud Firestore. يمكنك الوصول إلى هذا المستند في اللقطة التي تم إرجاعها إلى دالتك.

يتضمّن مرجع المستند طرقًا مثل update() وset() وremove() لتتمكّن من تعديل المستند الذي أدّى إلى تشغيل الدالة.

Node.js

const {onDocumentUpdated} = require('firebase-functions/v2/firestore');

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.before.data();

  // We'll only update if the name has changed.
  // This is crucial to prevent infinite loops.
  if (data.name == previousData.name) {
    return null;
  }

  // Retrieve the current count of name changes
  let count = data.name_change_count;
  if (!count) {
    count = 0;
  }

  // Then return a promise of a set operation to update the count
  return event.data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # We'll only update if the name has changed.
  # This is crucial to prevent infinite loops.
  if new_value.get("name") == prev_value.get("name"):
      return

  # Retrieve the current count of name changes
  count = new_value.to_dict().get("name_change_count", 0)

  # Update the count
  new_value.reference.update({"name_change_count": count + 1})

الوصول إلى معلومات مصادقة المستخدم

إذا كنت تستخدم أحد أنواع الأحداث التالية، يمكنك الوصول إلى معلومات مصادقة المستخدم حول الجهة الرئيسية التي أدّت إلى بدء الحدث. تُضاف هذه المعلومات إلى المعلومات التي يتم عرضها في الحدث الأساسي.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

للحصول على معلومات عن البيانات المتاحة في سياق المصادقة، يُرجى الاطّلاع على سياق المصادقة. يوضّح المثال التالي كيفية استرداد معلومات المصادقة:

Node.js

const {onDocumentWrittenWithAuthContext} = require('firebase-functions/v2/firestore');

exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
    const snapshot = event.data.after;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // retrieve auth context from event
    const { authType, authId } = event;

    let verified = false;
    if (authType === "system") {
      // system-generated users are automatically verified
      verified = true;
    } else if (authType === "unknown" || authType === "unauthenticated") {
      // admin users from a specific domain are verified
      if (authId.endsWith("@example.com")) {
        verified = true;
      }
    }

    return data.after.ref.set({
        created_by: authId,
        verified,
    }, {merge: true}); 
}); 

Python

@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # Get the auth context from the event
  user_auth_type = event.auth_type
  user_auth_id = event.auth_id

البيانات خارج حدث التفعيل

يتم تنفيذ Cloud Functions في بيئة موثوقة. ويتم تفويضها كحساب خدمة في مشروعك، ويمكنك إجراء عمليات القراءة والكتابة باستخدام مدير SDK في Firebase:

Node.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

القيود

يُرجى ملاحظة القيود التالية على مشغّلات Cloud Firestore في Cloud Functions:

  • يتطلّب Cloud Functions (الجيل الأول) قاعدة بيانات "(تلقائية)" حالية في وضع Firestore الأصلي. ولا يتيح استخدام قواعد البيانات المُسمّاة في Cloud Firestore أو وضع Datastore. يُرجى استخدام Cloud Functions (الجيل الثاني) لضبط الأحداث في هذه الحالات.
  • لا يمكن إعداد المشغّلَين "Cloud Functions" و"Cloud Firestore" في مشاريع مختلفة. لإعداد المشغّل "Cloud Firestore"، يجب أن يكون "Cloud Functions" في المشروع نفسه.
  • لا نضمن ترتيب ظهورها. يمكن أن تؤدي التغييرات السريعة إلى استدعاء الدوال بترتيب غير متوقع.
  • يتم إرسال الأحداث مرة واحدة على الأقل، ولكن قد يؤدي حدث واحد إلى استدعاءات متعددة للدالة. تجنَّب الاعتماد على آليات التنفيذ لمرة واحدة فقط، واكتب دوال متكررة.
  • يتطلّب Cloud Firestore في وضع Datastore استخدام Cloud Functions (الجيل الثاني). لا يتوافق الإصدار Cloud Functions (الجيل الأول) مع وضع Datastore.
  • يرتبط المشغّل بقاعدة بيانات واحدة. لا يمكنك إنشاء عامل تشغيل يطابق قواعد بيانات متعددة.
  • لا يؤدي حذف قاعدة بيانات إلى حذف أي مشغّلات لتلك القاعدة تلقائيًا. يتوقف المشغّل عن إرسال الأحداث، ولكنه يظل موجودًا حتى تحذفه.
  • إذا تجاوز حدث مطابق الحد الأقصى لحجم الطلب، قد لا يتم تسليم الحدث إلى Cloud Functions (الجيل الأول).
    • يتم تسجيل الأحداث التي لم يتم تسليمها بسبب حجم الطلب في سجلات المنصة وتُحتسب ضمن استخدام السجلّ للمشروع.
    • يمكنك العثور على هذه السجلات في "مستكشف السجلات" مع الرسالة "لا يمكن تسليم الحدث إلى دالة Cloud بسبب تجاوز الحجم الحد الأقصى للجيل الأول..." من مستوى الخطورة error. يمكنك العثور على اسم الدالة ضمن حقل functionName. إذا كان حقل receiveTimestamp لا يزال في غضون ساعة من الآن، يمكنك استنتاج محتوى الحدث الفعلي من خلال قراءة المستند المعنيّ باستخدام لقطة قبل الطابع الزمني وبعده.
    • لتجنُّب هذا التكرار، يمكنك إجراء ما يلي:
      • نقل البيانات والترقية إلى Cloud Functions (الجيل الثاني)
      • تصغير حجم المستند
      • حذف Cloud Functions المعنيّ
    • يمكنك إيقاف التسجيل نفسه باستخدام الاستبعادات، ولكن يُرجى العِلم أنّ الأحداث المخالفة لن يتم تسليمها.