אם אתם משתמשים באפליקציות עם פונקציות מהדור הראשון, כדאי לשקול מעבר לדור השני באמצעות ההוראות במדריך הזה. פונקציות דור שני משתמשות ב-Cloud Run כדי לספק ביצועים טובים יותר, הגדרה טובה יותר, מעקב טוב יותר ועוד.
בדוגמאות שבמסמך הזה מניחים שאתם משתמשים ב-JavaScript עם מודולים של CommonJS (ייבוא בסגנון require), אבל אותם עקרונות חלים על JavaScript עם ESM (ייבוא בסגנון import … from) ועל TypeScript.
תהליך ההעברה
פונקציות מהדור הראשון ומהדור השני יכולות להתקיים זו לצד זו באותו קובץ. כך תוכלו להעביר את בסיס הקוד שלכם חלק אחרי חלק, כשאתם מוכנים. מומלץ להעביר פונקציה אחת בכל פעם, לבצע בדיקות ואימות לפני שממשיכים.
אימות הגרסאות של Firebase CLI ו-firebase-functions
מוודאים שמשתמשים לפחות ב-Firebase CLI גרסה 12.00 וב-firebase-functions גרסה 4.3.0. כל גרסה חדשה יותר תתמוך בדור השני וגם בדור הראשון.
עדכון ייבוא
פונקציות מהדור השני מיובאות מחבילת המשנה v2 ב-SDK firebase-functions. נתיב הייבוא השונה הזה הוא כל מה שצריך לממשק CLI של 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");
עדכון הגדרות הטריגר
מכיוון ש-SDK מהדור השני מעדיף ייבוא מודולרי, צריך לעדכן את ההגדרות של הטריגרים כדי לשקף את הייבוא ששונה מהשלב הקודם.
הארגומנטים שמועברים לקריאות חוזרות (callback) עבור חלק מהטריגרים השתנו. בדוגמה הזו, שימו לב שהארגומנטים לקריאה החוזרת 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) => {
/* ... */
});
צמצום המאמצים הנדרשים לכתיבה מחדש באמצעות פירוק מבנה (destructuring) של JavaScript
אם הפונקציות שלכם כוללות גופים מורכבים שמסתמכים במידה רבה על הקשר של דור ראשון או על פרמטרים ספציפיים לספק (כמו message או snapshot), אתם יכולים להשתמש בכלי העזר לתאימות לדור ראשון שמוטמעים ב-SDK של דור שני.
SDK מדור שני מתקן באופן אוטומטי את אובייקט האירוע באמצעות פונקציות getter שתואמות לחתימות של דור ראשון. כך אפשר להשתמש בפירוק של JavaScript כדי לחלץ את המאפיינים האלה ישירות בחתימת ה-handler, ולצמצם את הצורך לשכתב את הלוגיקה של הפונקציה.
הפניה למיפוי ספקים
| ספק | טיעונים מדור ראשון | דור שני של פירוק מבנה של אירועים עם תיקון |
| Pub/Sub | (message, context)
|
({ message, context }) => { ... }
|
| Firestore | (snapshot, context)
|
({ snapshot, context }) => { ... }
|
| אחסון | (object, context)
|
({ object, context }) => { ... }
|
| מסד נתונים בזמן אמת | (snapshot, context)
|
({ snapshot, context }) => { ... }
|
| הגדרת תצורה מרחוק | (version, context)
|
({ version, context }) => { ... }
|
| 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 החדש, ה-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, צריך להעביר את ההגדרה הזו כחלק מהשדרוג לדור השני.
functions.config API הוצא משימוש ויוסר במרץ 2027.
אחרי התאריך הזה, פריסות עם functions.config ייכשלו.
כדי למנוע כשלים בפריסה, צריך להעביר את ההגדרה ל-Cloud Secret Manager באמצעות Firebase CLI. מומלץ מאוד להשתמש בשיטה הזו כי היא היעילה והמאובטחת ביותר להעברת ההגדרות.
הגדרת ייצוא באמצעות 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```עדכון קוד הפונקציה כדי לקשר סודות
כדי להשתמש בהגדרה שמאוחסנת בסוד החדש ב-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; // ... });פריסת פונקציות
פורסים את הפונקציות המעודכנות כדי להחיל את השינויים ולקשר את הרשאות הסוד.
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 APIs, אבל פונקציות מהדור השני משתמשות בחשבון השירות שמוגדר כברירת מחדל ב-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 (בדור השני).
שימוש בבו-זמניות
יתרון משמעותי של פונקציות מהדור השני הוא היכולת של מופע פונקציה יחיד לטפל ביותר מבקשה אחת בו-זמנית. כך אפשר לצמצם באופן משמעותי את מספר ההפעלות במצב התחלתי (cold start) שמשתמשי הקצה חווים. כברירת מחדל, מספר הבקשות המקבילות מוגדר ל-80, אבל אפשר להגדיר אותו לכל ערך מ-1 עד 1,000:
const {onRequest} = require("firebase-functions/v2/https");
exports.date = onRequest({
// set concurrency value
concurrency: 500
},
(req, res) => {
// ...
});
שינוי הגדרות של מקביליות יכול לשפר את הביצועים ולהפחית את העלות של הפונקציות. מידע נוסף על מקביליות זמין במאמר בנושא התרת בקשות מקבילות.
ביקורת על השימוש במשתנים גלובליים
פונקציות מהדור הראשון שנכתבו בלי להתחשב במקביליות עשויות להשתמש במשתנים גלובליים שמוגדרים ונבדקים בכל בקשה. כשמקביליות מופעלת ומופע יחיד מתחיל לטפל בכמה בקשות בו-זמנית, יכול להיות שיופיעו באגים בפונקציה, כי בקשות מקביליות יתחילו להגדיר ולבדוק משתנים גלובליים בו-זמנית.
במהלך השדרוג, אפשר להגדיר את מעבד הפונקציה ל-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.
אסטרטגיית המיגרציה תלויה בסוג הטריגר שבו הפונקציה משתמשת.
לגבי טריגרים של פונקציות שאפשר לקרוא להן, Task Queue ו-HTTP
הטריגרים האלה הם הפעלות ישירות. מכיוון שלפונקציה מהדור השני יהיה שם חדש (וכתובת URL חדשה לטריגרים של HTTP), תוכלו להעביר את התנועה על ידי עדכון הלקוחות.
- משנים את שם הפונקציה בקוד (לדוגמה, משנים את השם
myCallableל-myCallableV2). - פורסים את הפונקציה. עכשיו פועלות גם הפונקציות של הדור הראשון וגם הפונקציות של הדור השני.
- מעדכנים את קוד הלקוח או את הקוד שקורא לפונקציה כך שיפנה לשם או לכתובת ה-URL של הפונקציה החדשה מהדור השני.
- אחרי שכל התנועה עוברת לפונקציה החדשה, מוחקים את הפונקציה מהדור הראשון באמצעות הפקודה
firebase functions:deleteב-Firebase CLI.
לגבי טריגרים ברקע (Pub/Sub, Cloud Firestore, Cloud Storage וכו')
טריגרים ברקע מגיבים לאירועים בפרויקט. כדי לא לפספס אירועים במהלך המעבר, צריך להפעיל באופן זמני את הפונקציות מהדור הראשון ומהדור השני זו לצד זו.
במהלך תקופת המעבר, שתי הפונקציות יופעלו באותו אירוע. המשמעות היא שהלוגיקה העסקית תפעל פעמיים לכל אירוע. לפני שממשיכים, חשוב לוודא שהפונקציה אידמפוטנטית.
שלב 1: מוסיפים את הפונקציה מהדור השני לצד הפונקציה מהדור הראשון
משאירים את הפונקציה הקיימת מהדור הראשון בקוד ומוסיפים את הפונקציה מהדור השני שמאזינה לאותו מקור אירועים.
טיפ מקצועי: שימוש ב-passthrough לאימות
כדי להימנע משכפול של הלוגיקה העסקית בבסיס הקוד במהלך המעבר, או כדי לוודא שהפונקציה מהדור השני מקבלת אירועים בצורה נכונה לפני שסומכים עליה באופן מלא, אפשר להגדיר את הפונקציה מהדור השני כ-passthrough שקורא לפונקציה מהדור הראשון באמצעות השיטה 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: ביטול הפריסה של הפונקציה מהדור הראשון
מסירים את ההגדרה של פונקציה מדור ראשון מהקוד ומבצעים פריסה מחדש. ממשק ה-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 ...
});