ترقية وظائف Node.js من الجيل الأول إلى الجيل الثاني

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

تفترض الأمثلة الواردة في هذا المستند أنّك تستخدم JavaScript مع وحدات CommonJS (عمليات استيراد بنمط require)، ولكن تنطبق المبادئ نفسها على JavaScript مع وحدات ESM (عمليات استيراد بنمط import … from) وTypeScript.

عملية نقل البيانات

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

التحقّق من إصدارات Firebase وfirebase-function

تأكَّد من استخدام الإصدار 12.00 من واجهة سطر الأوامر Firebase على الأقل والإصدار 4.3.0 من firebase-functions. سيتوافق أي إصدار أحدث مع الجيل الثاني والجيل الأول.

تعديل عمليات الاستيراد

يتم استيراد دوال الجيل الثاني من الحزمة الفرعية v2 في حزمة تطوير البرامج (SDK) firebase-functions، وهذا المسار المختلف للاستيراد هو كل ما يحتاج إليه Firebase CLI لتحديد ما إذا كان سيتم نشر رمز الدالة كدالة من الجيل الأول أو الثاني.

الحزمة الفرعية v2 هي حزمة نمطية، وننصحك باستيراد الوحدة النمطية المحدّدة التي تحتاج إليها فقط.

قبل: الجيل الأول

const functions = require("firebase-functions/v1");

بعد: الجيل الثاني

// explicitly import each trigger
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

تعديل تعريفات المشغّلات

بما أنّ الجيل الثاني من حزمة تطوير البرامج (SDK) يفضّل عمليات الاستيراد النموذجية، عليك تعديل تعريفات المشغّلات لتعكس عمليات الاستيراد المعدَّلة من الخطوة السابقة.

تم تغيير الوسيطات التي يتم تمريرها إلى دوال رد الاتصال لبعض المشغّلات. في هذا المثال، لاحظ أنّه تم دمج وسيطات معاودة الاتصال onDocumentCreated في عنصر event واحد. بالإضافة إلى ذلك، تتضمّن بعض المشغّلات ميزات جديدة ومريحة للإعداد، مثل الخيار cors في المشغّل onRequest.

قبل: الجيل الأول

const functions = require("firebase-functions/v1");

exports.date = functions.https.onRequest((req, res) => {
  // ...
});

exports.uppercase = functions.firestore
  .document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

بعد: الجيل الثاني

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

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

تقليل جهود إعادة الكتابة باستخدام JavaScript destructuring

إذا كانت الدوال تتضمّن نصوصًا برمجية معقّدة تعتمد بشكل كبير على سياق الجيل الأول أو المَعلمات الخاصة بموفّر الخدمة (مثل message أو snapshot)، يمكنك استخدام أدوات المساعدة المتوافقة مع الجيل الأول والمضمّنة في حزمة تطوير البرامج (SDK) من الجيل الثاني.

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

مرجع ربط موفّر الخدمة

مقدّم الخدمة وسيطات الجيل الأول الجيل الثاني من تصحيح أخطاء تفكيك بنية الحدث
Pub/Sub (message, context) ({ message, context }) => { ... }
Firestore (snapshot, context) ({ snapshot, context }) => { ... }
التخزين (object, context) ({ object, context }) => { ... }
قاعدة بيانات الوقت الفعلي (snapshot, context) ({ snapshot, context }) => { ... }
الإعداد عن بُعد (version, context) ({ version, context }) => { ... }
نظام جدولة المهام (context) ({ context }) => { ... }
قائمة انتظار المهام (data, context) ({ data, context }) => { ... }

قبل (الجيل الأول):

export const myPubSubV1 = functions.pubsub.topic("my-topic").onPublish((message, context) => {
  const data = message.json;
  const eventId = context.eventId;
  // ... rest of the logic
});

New Alternative (2nd gen with Destructuring):

import { onMessagePublished } from "firebase-functions/v2/pubsub";

export const myPubSubV2 = onMessagePublished("my-topic", ({ message, context }) => {
  // No need to change the function body!
  const data = message.json;      // Uses v1 Message wrapper
  const eventId = context.eventId; // Uses v1 EventContext map
  // ... rest of the logic
});

استخدام الإعدادات التي تتضمّن مَعلمات

تتوقف دوال الجيل الثاني عن توفير الدعم لـ functions.config، ويتم استبدالها بواجهة أكثر أمانًا تتيح لك تحديد مَعلمات الإعداد بشكل تصريحي داخل قاعدة التعليمات البرمجية. باستخدام وحدة params الجديدة، تمنع واجهة سطر الأوامر عملية النشر ما لم تتضمّن جميع المَعلمات قيمة صالحة، ما يضمن عدم نشر دالة مع إعدادات غير مكتملة.

قبل: الجيل الأول

const functions = require("firebase-functions/v1");

exports.getQuote = functions.https.onRequest(async (req, res) => {
  const quote = await fetchMotivationalQuote(functions.config().apiKey);
  // ...
});

بعد: الجيل الثاني

const {onRequest} = require("firebase-functions/v2/https");
const {defineSecret} = require("firebase-functions/params");

// Define the secret parameter
const apiKey = defineSecret("API_KEY");

exports.getQuote = onRequest(
  // make the secret available to this function
  { secrets: [apiKey] },
  async (req, res) => {
    // retrieve the value of the secret
    const quote = await fetchMotivationalQuote(apiKey.value());
    // ...
  }
);

إذا كان لديك إعدادات بيئة حالية باستخدام functions.config، عليك نقل هذه الإعدادات كجزء من عملية الترقية إلى الجيل الثاني.

سيتم إيقاف واجهة برمجة التطبيقات functions.config نهائيًا في مارس 2027. بعد هذا التاريخ، ستتعذّر عمليات النشر التي تستخدم functions.config.

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

  1. إعدادات التصدير باستخدام واجهة سطر الأوامر Firebase

    استخدِم الأمر config export لتصدير إعدادات البيئة الحالية إلى سر جديد في Cloud Secret Manager:

    $ firebase functions:config:export
    i  This command retrieves your Runtime Config values (accessed via functions.config())
       and exports them as a Secret Manager secret.
    
    i  Fetching your existing functions.config() from your project...     Fetched your existing functions.config().
    
    i  Configuration to be exported:
    ⚠  This may contain sensitive data. Do not share this output.
    
    {
       ...
    } What would you like to name the new secret for your configuration? RUNTIME_CONFIG
    
    ✔  Created new secret version projects/project/secrets/RUNTIME_CONFIG/versions/1```
    
  2. تعديل رمز الدالة لربط الأسرار

    لاستخدام الإعدادات المخزَّنة في المفتاح السري الجديد في Cloud Secret Manager، استخدِم واجهة برمجة التطبيقات defineJsonSecret في مصدر الدالة. تأكَّد أيضًا من ربط الأسرار بجميع الدوال التي تحتاج إليها.

    قبل

    const functions = require("firebase-functions/v1");
    
    exports.myFunction = functions.https.onRequest((req, res) => {
      const apiKey = functions.config().someapi.key;
      // ...
    });
    

    بعد

    const { onRequest } = require("firebase-functions/v2/https");
    const { defineJsonSecret } = require("firebase-functions/params");
    
    const config = defineJsonSecret("RUNTIME_CONFIG");
    
    exports.myFunction = onRequest(
      // Bind secret to your function
      { secrets: [config] },
      (req, res) => {
        // Access secret values via .value()
        const apiKey = config.value().someapi.key;
        // ...
    });
    
  3. تفعيل الوظائف

    انشر وظائفك المعدَّلة لتطبيق التغييرات وربط أذونات البيانات السرية.

    firebase deploy --only functions:<your-function-name>
    

ضبط خيارات وقت التشغيل

تغيّرت طريقة ضبط خيارات وقت التشغيل بين الجيل الأول والجيل الثاني، كما يضيف الجيل الثاني إمكانية جديدة لضبط الخيارات لجميع الدوال.

قبل: الجيل الأول

const functions = require("firebase-functions/v1");

exports.date = functions
  .runWith({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  })
  // locate function closest to users
  .region("asia-northeast1")
  .https.onRequest((req, res) => {
    // ...
  });

exports.uppercase = functions
  // locate function closest to users and database
  .region("asia-northeast1")
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

بعد: الجيل الثاني

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions/v2");

// locate all functions closest to users
setGlobalOptions({ region: "asia-northeast1" });

exports.date = onRequest({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  }, (req, res) => {
  // ...
});

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

تعديل حساب الخدمة التلقائي (اختياري)

بينما تستخدم دوال الجيل الأول حساب الخدمة التلقائي في Google App Engine لمنح الإذن بالوصول إلى واجهات برمجة تطبيقات Firebase، تستخدم دوال الجيل الثاني حساب الخدمة التلقائي في Compute Engine. ويمكن أن يؤدي هذا الاختلاف إلى حدوث مشاكل في الأذونات للدوال التي تم نقلها إلى الجيل الثاني في الحالات التي منحت فيها أذونات خاصة لحساب الخدمة في الجيل الأول. إذا لم تغيّر أي أذونات لحساب الخدمة، يمكنك تخطّي هذه الخطوة.

الحلّ المقترَح هو تعيين حساب الخدمة التلقائي الحالي من الجيل الأول من App Engine بشكل صريح للوظائف التي تريد نقلها إلى الجيل الثاني، ما يؤدي إلى إلغاء الإعداد التلقائي للجيل الثاني. يمكنك إجراء ذلك من خلال التأكّد من أنّ كل دالة تم نقلها تضبط القيمة الصحيحة لـ serviceAccountEmail:

const {onRequest} = require("firebase-functions/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions");

// Use the App Engine default service account for all functions
setGlobalOptions({serviceAccountEmail: '<my-project-number>@<wbr>appspot.gserviceaccount.com'});

// Now I use the App Engine default service account.
exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

// I do too!
exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  // ...
});

يمكنك بدلاً من ذلك التأكّد من تعديل تفاصيل حساب الخدمة لتتطابق مع جميع الأذونات الضرورية في كلّ من حساب الخدمة التلقائي في App Engine (للجيل الأول) وحساب الخدمة التلقائي في Compute Engine (للجيل الثاني).

استخدام التزامن

من المزايا المهمة للجيل الثاني من الدوال إمكانية أن يعالج مثيل واحد من الدالة أكثر من طلب واحد في المرة الواحدة. ويمكن أن يؤدي ذلك إلى تقليل عدد عمليات التشغيل على البارد التي يواجهها المستخدمون النهائيون بشكل كبير. يتم ضبط التزامن تلقائيًا على 80، ولكن يمكنك ضبطه على أي قيمة من 1 إلى 1000:

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // set concurrency value
    concurrency: 500
  },
  (req, res) => {
    // ...
});

يمكن أن يؤدي ضبط التزامن إلى تحسين الأداء وتقليل تكلفة الدوال. مزيد من المعلومات حول التزامن في السماح بالطلبات المتزامنة

تدقيق استخدام المتغيرات العامة

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

أثناء الترقية، يمكنك ضبط وحدة المعالجة المركزية (CPU) للدالة على gcf_gen1 وضبط concurrency على 1 لاستعادة سلوك الجيل الأول:

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // TEMPORARY FIX: remove concurrency
    cpu: "gcf_gen1",
    concurrency: 1
  },
  (req, res) => {
    // ...
});

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

نقل عدد الزيارات إلى دوال الجيل الثاني الجديدة

كما هو الحال عند تغيير منطقة دالة أو نوع مشغّلها، عليك منح دالة الجيل الثاني اسمًا جديدًا ونقل عدد الزيارات إليها تدريجيًا.

لا يمكن ترقية دالة من الجيل الأول إلى الجيل الثاني بالاسم نفسه وتشغيل firebase deploy. سيؤدي ذلك إلى ظهور الخطأ التالي:

Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.

تعتمد استراتيجية النقل على نوع المشغّل الذي تستخدمه الدالة.

بالنسبة إلى المشغّلات القابلة للاستدعاء وقوائم انتظار المهام ومشغّلات HTTP

وهذه المشغّلات هي عمليات استدعاء مباشرة. بما أنّ الجيل الثاني من الدوال سيحمل اسمًا جديدًا (وعنوان URL جديدًا لمشغّلات HTTP)، يمكنك نقل الزيارات من خلال تعديل البرامج.

  1. أعِد تسمية الدالة في الرمز البرمجي (على سبيل المثال، أعِد تسمية myCallable إلى myCallableV2).
  2. نفِّذ الدالة. سيتم الآن تشغيل كلّ من دالة الجيل الأول ودالة الجيل الثاني.
  3. عدِّل رمز العميل أو برنامج الاتصال للإشارة إلى اسم أو عنوان URL الجديدين للدالة من الجيل الثاني.
  4. بعد نقل جميع الزيارات إلى الدالة الجديدة، احذف دالة الجيل الأول باستخدام الأمر firebase functions:delete في واجهة سطر الأوامر (CLI) الخاصة بمنصة Firebase.

بالنسبة إلى المشغّلات التي تعمل في الخلفية (Pub/Sub وCloud Firestore وCloud Storage وما إلى ذلك)

تستجيب المشغّلات التي تعمل في الخلفية للأحداث في مشروعك. لتجنُّب فقدان أي أحداث أثناء عملية النقل، عليك تشغيل كلّ من الجيل الأول والجيل الثاني من الدوال جنبًا إلى جنب بشكل مؤقت.

خلال فترة الانتقال، سيتم تشغيل كلتا الدالتين عند وقوع الحدث نفسه، ما يعني أنّ منطق نشاطك التجاري سيتم تنفيذه مرتين لكل حدث. لذا، تأكَّد من أنّ الدالة متكرّرة قبل المتابعة.

الخطوة 1: إضافة دالة الجيل الثاني إلى جانب دالة الجيل الأول

احتفظ بالدالة الحالية من الجيل الأول في الرمز، وأضِف دالة من الجيل الثاني تستمع إلى مصدر الحدث نفسه.

نصيحة احترافية: استخدام وظيفة تمرير للتحقّق لتجنُّب تكرار منطق نشاطك التجاري في قاعدة الرموز البرمجية أثناء عملية الانتقال، أو للتحقّق من أنّ وظيفة الجيل الثاني تتلقّى الأحداث بشكل صحيح قبل الوثوق بها تمامًا، اجعل وظيفة الجيل الثاني وظيفة تمرير تستدعي وظيفة الجيل الأول باستخدام طريقة run.

import * as functions from "firebase-functions/v1";
import { onMessagePublished } from "firebase-functions/v2/pubsub";

// --- Existing 1st gen function ---
export const myPubSub = functions.pubsub.topic("my-topic").onPublish((message, context) => {
  console.log("V1 handler running for event:", context.eventId);
  // ... existing v1 function logic ...
});

// --- New v2 passthrough function ---
export const myPubSubV2 = onMessagePublished("my-topic", async ({ message, context }) => {
   console.log("v2 handler triggering V1 for event:", context.eventId);
   // Call the v1 function's handler
   await myPubSub.run(message, context);
});

الخطوة 2: نشر كلتا الدالتين

نفِّذ firebase deploy. أصبح كلتا الدالتين نشطتين الآن وتستمعان إلى الأحداث نفسها.

الخطوة 3: التأكّد من أنّ الجيل الثاني من الدالة يتلقّى الزيارات

راقِب السجلات الخاصة بكلتا الدالتين، وتأكَّد من استدعاء الدالة من الجيل الثاني لجميع الأحداث ومن نجاح عمليات الاستدعاء.

الخطوة 4: نقل المنطق الكامل إلى دالة الجيل الثاني

بعد التأكّد، انقل منطق النشاط التجاري الفعلي من دالة الجيل الأول إلى نص دالة الجيل الثاني. وإذا كنت قد استخدمت طريقة النقل المباشر، أزِل طلب myPubSub.run().

import * as functions from "firebase-functions/v1";
import { onMessagePublished } from "firebase-functions/v2/pubsub";

// --- Existing v1 function (to be removed next) ---
export const myPubSub = functions.pubsub.topic("my-topic").onPublish((message, context) => {
  console.log("v1 handler running for event:", context.eventId);
  // ... existing v1 function logic ...
});

// --- New v2 function with full logic ---
export const myPubSubV2 = onMessagePublished("my-topic", ({ message, context }) => {
   console.log("v2 handler running for event:", context.eventId);
   // ... existing v1 function logic WAS MOVED HERE ...
});

انشر هذا التغيير.

الخطوة 5: إلغاء نشر الجيل الأول من الدالة

أزِل تعريف الدالة من الجيل الأول من الرمز وأعِد نشرها. سيطلب منك سطر الأوامر حذف الدالة من الجيل الأول من Google Cloud.

import { onMessagePublished } from "firebase-functions/v2/pubsub";

// --- V1 function definition REMOVED ---

// --- New v2 function with full logic ---
export const myPubSubV2 = onMessagePublished("my-topic", ({ message, context }) => {
   console.log("v2 handler running for event:", context.eventId);
   // ... existing v1 function logic ...
});