توابع 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) => {
  /* ... */
});

استفاده از پیکربندی پارامتری

توابع نسل دوم پشتیبانی از 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.

قبل از اینکه این مراحل را دنبال کنید، ابتدا مطمئن شوید که تابع شما idempotent است، زیرا هم نسخه جدید و هم نسخه قدیمی تابع شما در طول تغییر همزمان اجرا می‌شوند. به عنوان مثال، اگر یک تابع نسل اول دارید که به رویدادهای نوشتن در Firestore پاسخ می‌دهد، مطمئن شوید که پاسخ به یک نوشتن دو بار، یک بار توسط تابع نسل اول و یک بار توسط تابع نسل دوم، در پاسخ به آن رویدادها، برنامه شما را در حالت ثابتی قرار می‌دهد.

  1. نام تابع را در کد توابع خود تغییر دهید. برای مثال، resizeImage به resizeImageSecondGen تغییر نام دهید.
  2. تابع را مستقر کنید، به طوری که هم تابع نسل اول اصلی و هم تابع نسل دوم در حال اجرا باشند.
    1. در مورد تریگرهای قابل فراخوانی، صف وظایف و HTTP، با به‌روزرسانی کد کلاینت با نام یا URL تابع نسل دوم، شروع به ارجاع همه کلاینت‌ها به تابع نسل دوم کنید.
    2. با محرک‌های پس‌زمینه، هر دو نسل اول و دوم توابع بلافاصله پس از استقرار به هر رویدادی پاسخ می‌دهند.
  3. وقتی تمام ترافیک منتقل شد، تابع نسل اول را با استفاده از دستور firebase functions:delete فرمان فایربیس حذف کنید.
    1. به صورت اختیاری، نام تابع نسل دوم را تغییر دهید تا با نام تابع نسل اول مطابقت داشته باشد.