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

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

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

فرایند انتقال

توابع نسل اول و دوم می‌توانند در کنار هم در یک فایل منبع وجود داشته باشند. این کار به شما امکان می‌دهد پایگاه کد خود را به‌تدریج و هر زمان که آماده بودید انتقال دهید. توجه داشته باشید که این ترکیب بسته‌ها در یک تابع مجزا و واحد کار نمی‌کند.

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

درستی‌سنجی Firebase CLI و نسخه‌های firebase-functions

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

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

توابع نسل دوم از زیربسته v2 در کیت توسعه نرم‌افزار firebase-functions وارد می‌شود. این مسیر وارد کردن متفاوت تنها چیزی است که Firebase «خط فرمان» برای تعیین اینکه آیا کد تابع شما به‌عنوان تابع نسل اول یا دوم مستقر شود نیاز دارد.

زیربسته 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");

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

ازآنجایی‌که کیت توسعه نرم‌افزار نسل دوم واردات واحدی را ترجیح می‌دهد، تعریف‌های راه‌انداز را به‌روز کنید تا واردات تغییریافته از مرحله قبلی را منعکس کند.

متغیرهای مستقل ارسال‌شده به توابع برگشتی برای برخی‌از راه‌اندازها تغییر کرده است. در این مثال، توجه داشته باشید که آرگومان‌های برگشت‌پذیر 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) => {
  /* ... */
});

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

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

«کیت توسعه نرم‌افزار» نسل دوم به‌طور خودکار شیء رویداد را با دریافت‌کننده‌هایی که با امضاهای نسل اول مطابقت دارند وصله می‌کند. این کار به شما امکان می‌دهد از ساختارشکنی جاوا اسکریپت برای استخراج مستقیم این دارایی‌ها در امضای مدیریت‌کننده استفاده کنید و نیاز به بازنویسی منطق تابع را به حداقل برسانید.

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

ارائه‌دهنده متغیرهای مستقل نسل اول نسل دوم تفکیک رویداد وصله‌شده
Pub/Sub (message, context) ({ message, context }) => { ... }
Cloud Firestore (snapshot, context) ({ snapshot, context }) => { ... }
Cloud Storage (object, context) ({ object, context }) => { ... }
Realtime Database (snapshot, context) ({ snapshot, context }) => { ... }
Remote Config (version, context) ({ version, context }) => { ... }
Cloud Scheduler (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، خط فرمان تا زمانی که همه پارامترها مقدار معتبری نداشته باشند، استقرار را مسدود می‌کند و از این طریق تضمین می‌کند که تابع بدون پیکربندی ناقص استقرار نیابد.

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

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 API منسوخ شده است و در مارس ۲۰۲۷ از رده خارج خواهد شد. پس‌از آن تاریخ، استقرارها با 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، از defineJsonSecret API در منبع تابع خود استفاده کنید. همچنین مطمئن شوید که رمزها به همه توابعی که به آن‌ها نیاز دارند متصل شده باشند.

    قبل‌از

    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 (برای نسل دوم) مطابقت داشته باشد.

استفاده از هم‌رس‌های بهبودیافته

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

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

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

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

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

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

درحین ارتقا دادن، می‌توانید 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

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

  1. نام تابع را در کدتان تغییر دهید (برای مثال، نام myCallable را به myCallableV2 تغییر دهید).
  2. تابع را مستقر کنید. اکنون هر دو تابع نسل اول و دوم درحال اجرا هستند.
  3. کد کارخواه یا تماس‌گیرنده را به‌روز کنید تا به نام یا نشانی وب تابع نسل دوم جدید اشاره کند.
  4. پس‌از اینکه همه ترافیک به تابع جدید منتقل شد، تابع نسل اول را بااستفاده از دستور firebase functions:delete در Firebase CLI حذف کنید.

انتقال راه‌اندازهای پس‌زمینه

راه‌اندازهای پس‌زمینه‌ای (مثل راه‌اندازهای Pub/Sub، Cloud Firestore، و Cloud Storage) به رویدادهای پروژه شما پاسخ می‌دهند. برای اینکه هیچ رویدادی را درطول انتقال ازدست ندهید، باید موقتاً هر دو عملکرد نسل اول و نسل دوم را به‌طور هم‌زمان اجرا کنید.

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

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

    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 حذف کنید.