שדרוג פונקציות של Node.js מדור ראשון לדור שני

אם אתם משתמשים כרגע בפונקציות מדור ראשון, מומלץ לעבור לפונקציות מדור שני לפי ההוראות במדריך הזה. הפונקציות של הדור השני משתמשות ב-Cloud Run כדי לספק ביצועים טובים יותר, הגדרה טובה יותר, מעקב טוב יותר ועוד.

בדוגמאות שבדף הזה ההנחה היא שאתם משתמשים ב-JavaScript עם מודולים של CommonJS (ייבוא סגנון require), אבל אותם עקרונות חלים על JavaScript עם ESM (ייבוא סגנון import … from) ועם TypeScript.

תהליך ההעברה

פונקציות מדור ראשון ודור שני יכולות להופיע זו לצד זו באותו קובץ. כך תוכלו להעביר את הנתונים בקלות, חלק אחרי חלק, כשתהיו מוכנים. מומלץ להעביר פונקציה אחת בכל פעם, ולבצע בדיקה ואימות לפני שממשיכים.

אימות הגרסאות של Firebase CLI ו-firebase-functions

מוודאים שאתם משתמשים בגרסה 12.00 ובגרסה firebase-functions 4.3.0 של Firebase CLI לפחות. כל גרסה חדשה יותר תתמוך גם בדור השני וגם בדור הראשון.

עדכון ייבוא

פונקציות מדור שני מיובאות מחבילת המשנה 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 מדור שני מעדיף ייבוא מודולרי, מעדכנים את הגדרות הטריגרים כך שישקפו את השינויים בייבוא מהשלב הקודם.

הארגומנטים שהועברו לפעולות החזרה (callbacks) של חלק מהטריגרים השתנו. בדוגמה הזו, שימו לב שהארגומנטים של הקריאה החוזרת 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) => {
  /* ... */
});

שימוש בתצורה עם פרמטרים

פונקציות מדור שני משחררות את התמיכה ב-functions.config לטובת ממשק מאובטח יותר להגדרת פרמטרים של תצורה באופן הצהרתי בתוך ה-codebase שלכם. בעזרת המודול החדש params, ה-CLI חוסם את הפריסה אלא אם לכל הפרמטרים יש ערך תקין, וכך מוודא שלא מתבצעת פריסה של פונקציה עם הגדרות חסרות.

מעבר לחבילת המשנה params

אם השתמשתם בהגדרת סביבה עם functions.config, תוכלו להעביר את ההגדרה הקיימת להגדרה עם פרמטרים.

לפני: דור ראשון

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

exports.date = functions.https.onRequest((req, res) => {
  const date = new Date();
  const formattedDate =
date.toLocaleDateString(functions.config().dateformat);

  // ...
});

אחרי: דור שני

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

const dateFormat = defineString("DATE_FORMAT");

exports.date = onRequest((req, res) => {
  const date = new Date();
  const formattedDate = date.toLocaleDateString(dateFormat.value());

  // ...
});

הגדרת ערכי פרמטרים

בפעם הראשונה שמפרסים, ה-CLI של Firebase מבקש את כל הערכים של הפרמטרים ושומר את הערכים בקובץ dotenv. כדי לייצא את הערכים של functions.config, מריצים את הפקודה firebase functions:config:export.

כדי לשפר את הבטיחות, אפשר גם לציין סוגים וכללי אימות של פרמטרים.

מקרה מיוחד: מפתחות API

המודול params משולב עם Cloud Secret Manager, שמספק בקרת גישה מפורטת לערכים רגישים כמו מפתחות API. מידע נוסף זמין במאמר פרמטרים סודיים.

לפני: דור ראשון

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());
    // ...
  }
);

הגדרת אפשרויות בסביבת זמן הריצה

ההגדרה של אפשרויות זמן הריצה השתנתה בין דור ראשון לדור שני. דור שני גם נוספה יכולת חדשה להגדרת אפשרויות לכל הפונקציות.

לפני: דור ראשון

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) => {
  /* ... */
});

שימוש בו-זמניות (concurrency)

יתרון משמעותי של פונקציות מדור שני הוא היכולת של מופע פונקציה יחיד לשרת יותר מבקשה אחת בו-זמנית. כך אפשר לצמצם באופן משמעותי את מספר ההפעלות במצב התחלתי (cold start) של משתמשי הקצה. כברירת מחדל, מספר הפעולות בו-זמנית מוגדר ל-80, אבל אפשר להגדיר אותו לכל ערך בין 1 ל-1,000:

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

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

התאמת התזמון של הפעולות בו-זמנית יכולה לשפר את הביצועים ולהפחית את העלות של הפונקציות. מידע נוסף על בו-זמניות זמין במאמר אישור בקשות בו-זמניות.

בדיקת השימוש במשתנים גלובליים

פונקציות מדור ראשון שנכתבו בלי להביא בחשבון בו-זמניות עשויות להשתמש במשתנים גלובליים שמוגדרים ונקרא בכל בקשה. כשמפעילים בו-זמניות (concurrency) ומכונה אחת מתחילה לטפל במספר בקשות בו-זמנית, הדבר עלול ליצור באגים בפונקציה מכיוון שבקשות בו-זמנית מתחילות להגדיר וקוראות משתנים גלובליים בו-זמנית.

במהלך השדרוג, אפשר להגדיר את המעבד של הפונקציה ל-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.

לפני שמבצעים את השלבים הבאים, צריך לוודא שהפונקציה עקבית, כי גם הגרסה החדשה וגם הגרסה הישנה של הפונקציה יפעלו בו-זמנית במהלך השינוי. לדוגמה, אם יש לכם פונקציה מדור ראשון שמגיבה לכתיבה של אירועים ב-Firestore, חשוב לוודא שמגיבים לכתיבה פעמיים, פעם אחת באמצעות הפונקציה של דור ראשון ופעם אחת באמצעות הפונקציה של דור שני, בתגובה לאירועים האלה האפליקציה נשארת במצב עקבי.

  1. משנים את שם הפונקציה בקוד של הפונקציות. לדוגמה, צריך לשנות את השם של resizeImage לשם resizeImageSecondGen.
  2. פורסים את הפונקציה, כך שגם הפונקציה המקורית מדור ראשון וגם הפונקציה מדור שני יפעלו.
    1. במקרה של טריגרים מסוג callable,‏ Task Queue ו-HTTP, מתחילים להפנות את כל הלקוחות לפונקציה מדור שני על ידי עדכון קוד הלקוח בשם או בכתובת ה-URL של הפונקציה מדור שני.
    2. עם טריגרים ברקע, גם הפונקציות מהדור הראשון וגם הפונקציות מהדור השני יגיבו לכל אירוע מיד לאחר הפריסה.
  3. אחרי שכל התנועה תועבר, מוחקים את הפונקציה מדור ראשון באמצעות הפקודה firebase functions:delete של CLI ב-Firebase.
    1. אפשר גם לשנות את השם של הפונקציה מדור שני כך שתתאים לשם הפונקציה מדור ראשון.