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

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

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

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

مشغّلات دالة 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

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:

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