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

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

Cloud Functions (الجيل الثاني)

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

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

تصدِّر حزمة Cloud Functions for Firebase SDK مشغِّلات أحداث 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 في Firebase، يمكنك الاطّلاع على مقالة بدء استخدام Cloud Functions for Firebase (الجيل الثاني) لضبط إعداد وظائف Cloud Functions في 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

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

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