توابع Node.js نسل اول را به نسل دوم ارتقا دهید

برنامه‌هایی که از توابع نسل اول استفاده می‌کنند باید با استفاده از دستورالعمل‌های این راهنما، مهاجرت به نسل دوم را در نظر بگیرند. توابع نسل دوم از Cloud Run برای ارائه عملکرد بهتر، پیکربندی بهتر، نظارت بهتر و موارد دیگر استفاده می‌کنند.

مثال‌های این سند فرض می‌کنند که شما از جاوا اسکریپت با ماژول‌های CommonJS ( require وارد کردن استایل) استفاده می‌کنید، اما همین اصول در مورد جاوا اسکریپت با ESM ( import … from وارد کردن استایل) و TypeScript نیز صدق می‌کند.

روند مهاجرت

توابع نسل اول و نسل دوم می‌توانند در کنار هم در یک فایل وجود داشته باشند. این به شما امکان می‌دهد تا کدبیس خود را قطعه قطعه و در صورت نیاز منتقل کنید. توصیه می‌کنیم هر بار یک تابع را منتقل کنید و قبل از ادامه، آن را آزمایش و تأیید کنید.

نسخه‌های Firebase CLI و firebase-function را تأیید کنید

مطمئن شوید که حداقل از Firebase CLI نسخه 12.00 و firebase-functions نسخه 4.3.0 استفاده می‌کنید. هر نسخه جدیدتری از نسل دوم و همچنین نسل اول پشتیبانی خواهد کرد.

به‌روزرسانی واردات

توابع نسل دوم از زیربسته v2 در firebase-functions SDK وارد می‌شوند. این مسیر واردات متفاوت، تمام چیزی است که 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 از واردات ماژولار پشتیبانی می‌کند، تعاریف تریگر را به‌روزرسانی کنید تا واردات تغییر یافته از مرحله قبل را منعکس کند.

آرگومان‌های ارسالی به callbackها برای برخی از triggerها تغییر کرده‌اند. در این مثال، توجه داشته باشید که آرگومان‌های ارسالی به callback onDocumentCreated در یک شیء event واحد تجمیع شده‌اند. علاوه بر این، برخی از triggerها دارای ویژگی‌های پیکربندی جدید و مناسبی هستند، مانند گزینه cors در trigger مربوط به 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) => {
  /* ... */
});

با استفاده از destructuring جاوا اسکریپت، تلاش‌های بازنویسی را به حداقل برسانید

اگر توابع شما بدنه‌های پیچیده‌ای دارند که به شدت به متن نسل اول یا پارامترهای خاص ارائه‌دهنده (مانند message یا snapshot ) وابسته هستند، می‌توانید از کمک‌کننده‌های سازگاری نسل اول که در SDK نسل دوم تعبیه شده‌اند، استفاده کنید.

نسل دوم SDK به طور خودکار شیء رویداد را با getterهایی که با امضاهای نسل اول مطابقت دارند، وصله‌بندی می‌کند. این به شما امکان می‌دهد از destructuring جاوا اسکریپت برای استخراج مستقیم این ویژگی‌ها در امضای handler استفاده کنید و نیاز به بازنویسی منطق تابع خود را به حداقل برسانید.

مرجع نگاشت ارائه دهنده

ارائه دهنده آرگومان‌های نسل اول تخریب رویداد پچ شده نسل دوم
میخانه/ساب (message, context) ({ message, context }) => { ... }
فایراستور (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
});

جایگزین جدید (نسل دوم با تخریب):

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 ، رابط خط فرمان (CLI) استقرار را مسدود می‌کند، مگر اینکه همه پارامترها مقدار معتبری داشته باشند و تضمین می‌کند که تابعی با پیکربندی از دست رفته مستقر نشود.

قبل از: نسل اول

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 دارید، این پیکربندی را به عنوان بخشی از ارتقاء خود به نسل دوم منتقل کنید.

API مربوط به functions.config منسوخ شده و در مارس 2027 از رده خارج خواهد شد. پس از آن تاریخ، استقرارها با functions.config با شکست مواجه خواهند شد.

برای جلوگیری از شکست در استقرار، پیکربندی خود را با استفاده از Firebase CLI به Cloud Secret Manager منتقل کنید. این روش به عنوان کارآمدترین و امن‌ترین راه برای انتقال پیکربندی شما اکیداً توصیه می‌شود.

  1. پیکربندی را با Firebase CLI صادر کنید

    از دستور 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، از API 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) => {
  /* ... */
});

به‌روزرسانی حساب سرویس پیش‌فرض (اختیاری)

در حالی که توابع نسل اول از حساب سرویس پیش‌فرض موتور برنامه گوگل برای تأیید دسترسی به APIهای 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 (برای نسل دوم) مطابقت داشته باشد.

استفاده از همزمانی

یکی از مزایای قابل توجه توابع نسل دوم، توانایی یک نمونه تابع واحد برای ارائه بیش از یک درخواست به طور همزمان است. این امر می‌تواند تعداد شروع‌های سرد تجربه شده توسط کاربران نهایی را به طرز چشمگیری کاهش دهد. به طور پیش‌فرض، همزمانی روی ۸۰ تنظیم شده است، اما می‌توانید آن را روی هر مقداری از ۱ تا ۱۰۰۰ تنظیم کنید:

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

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

تنظیم همزمانی می‌تواند عملکرد را بهبود بخشد و هزینه توابع را کاهش دهد. برای اطلاعات بیشتر در مورد همزمانی به Allow concurrent requests مراجعه کنید.

حسابرسی استفاده از متغیرهای سراسری

توابع نسل اول که بدون در نظر گرفتن همزمانی نوشته شده‌اند، ممکن است از متغیرهای سراسری استفاده کنند که در هر درخواست تنظیم و خوانده می‌شوند. وقتی همزمانی فعال باشد و یک نمونه واحد شروع به مدیریت چندین درخواست به طور همزمان کند، این ممکن است باعث ایجاد اشکالاتی در تابع شما شود زیرا درخواست‌های همزمان شروع به تنظیم و خواندن متغیرهای سراسری به طور همزمان می‌کنند.

هنگام ارتقا، می‌توانید CPU تابع خود را روی gcf_gen1 تنظیم کنید و concurrency روی ۱ تنظیم کنید تا رفتار نسل اول بازیابی شود:

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. کد کلاینت یا فراخوانی‌کننده‌ی خود را به‌روزرسانی کنید تا به نام یا آدرس اینترنتی تابع نسل دوم جدید اشاره کند.
  4. پس از اینکه تمام ترافیک به تابع جدید منتقل شد، تابع نسل اول را با استفاده از دستور firebase functions:delete در Firebase CLI حذف کنید.

برای محرک‌های پس‌زمینه ( Pub/Sub ، Cloud Firestore ، Cloud Storage و غیره)

تریگرهای پس‌زمینه به رویدادهای پروژه شما پاسخ می‌دهند. برای جلوگیری از از دست دادن هیچ رویدادی در طول انتقال، باید توابع نسل اول و نسل دوم را به طور موقت در کنار هم اجرا کنید.

در طول دوره انتقال، هر دو تابع در یک رویداد اجرا می‌شوند. این بدان معناست که منطق کسب و کار شما در هر رویداد دو بار اجرا می‌شود. قبل از ادامه، مطمئن شوید که تابع شما idempotent است.

مرحله ۱: تابع نسل دوم را در کنار تابع نسل اول اضافه کنید

تابع نسل اول موجود خود را در کد خود نگه دارید و تابع نسل دوم را که به همان منبع رویداد گوش می‌دهد، اضافه کنید.

نکته حرفه‌ای: برای تأیید از یک گذرگاه استفاده کنید. برای جلوگیری از تکرار منطق کسب‌وکارتان در کدبیس در طول انتقال، یا برای تأیید اینکه تابع نسل دوم قبل از اعتماد کامل به آن، رویدادها را به درستی دریافت می‌کند، تابع نسل دوم را به یک گذرگاه تبدیل کنید که تابع نسل اول را با استفاده از متد 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);
});

مرحله ۲: هر دو تابع را مستقر کنید

دستور firebase deploy اجرا کنید. اکنون هر دو تابع فعال هستند و به رویدادهای یکسانی گوش می‌دهند.

مرحله ۳: تأیید کنید که تابع نسل دوم ترافیک دریافت می‌کند

لاگ‌های هر دو تابع را بررسی کنید. مطمئن شوید که تابع نسل دوم برای همه رویدادها فراخوانی شده و فراخوانی‌ها با موفقیت انجام می‌شوند.

مرحله ۴: انتقال کل منطق به تابع نسل دوم

پس از اطمینان، منطق تجاری واقعی را از تابع نسل اول به بدنه تابع نسل دوم منتقل کنید. اگر از متد passthrough استفاده کرده‌اید، فراخوانی 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 ...
});

این تغییر را اعمال کنید.

مرحله ۵: غیرفعال کردن تابع نسل اول

تعریف تابع نسل اول را از کد خود حذف کرده و دوباره مستقر کنید. رابط خط فرمان (CLI) از شما می‌خواهد که تابع نسل اول را از 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 ...
});