توسيع نطاق Cloud Firestore باستخدام Cloud Functions (الجيل الثاني)

باستخدام Cloud Functions، يمكنك نشر التعليمات البرمجية لمعالجة الأحداث التي نتجت عن تغييرات في قاعدة بيانات Cloud Firestore. ويتيح لك ذلك إضافة وظائف جهة الخادم بسهولة إلى تطبيقك بدون تشغيل خوادمك الخاصة.

وظائف السحابة الإلكترونية (الجيل الثاني)

توفِّر لك الدوال السحابية (الجيل الثاني) من Cloud Run وEventarc، بنية أساسية أكثر فعالية، وعناصر تحكّم متقدّمة في الأداء وقابلية التوسّع، والمزيد من التحكّم في الوظائف أثناء التشغيل. لمزيد من المعلومات حول الجيل الثاني، يمكنك الاطّلاع على دوال Cloud لبرنامج Firebase (الجيل الثاني). لمعرفة المزيد حول الجيل الأول بدلاً من ذلك، يمكنك مراجعة توسيع Cloud Firestore باستخدام Cloud Functions.

مشغلات وظيفة Cloud Firestore

تعمل حزمة تطوير البرامج (SDK) لوظائف Cloud لمنصة 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 في Firebase (الجيل الثاني) لضبط دوال السحابة الإلكترونية في مشروع Firebase وإعدادها.

كتابة الدوال التي تُشغِّلها Cloud Firestore

تحديد عامل تشغيل دالة

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

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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

import { onDocumentUpdated } from "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 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

import { onDocumentWrittenWithAuthContext } from "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

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

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