Uaktualnij funkcje Node.js 1 generacji do 2 generacji

Aplikacje korzystające z funkcji 1 generacji powinny rozważyć migrację do funkcji 2 generacji zgodnie z instrukcjami w tym przewodniku. Funkcje 2 generacji korzystają z Cloud Run, co zapewnia lepszą wydajność, konfigurację i monitorowanie oraz inne korzyści.

Przykłady w tym dokumencie zakładają, że używasz JavaScriptu z modułami CommonJS (importy w stylu require), ale te same zasady obowiązują w przypadku JavaScriptu z modułami ESM (importy w stylu import … from) i TypeScriptu.

Proces migracji

Funkcje 1 generacji i 2 generacji mogą współistnieć w tym samym pliku. Dzięki temu możesz migrować bazę kodu stopniowo, gdy będziesz na to gotowy. Zalecamy migrację po jednej funkcji, a przed przejściem do następnej wykonanie testów i weryfikacji.

Sprawdzanie wersji Firebase CLI i firebase-function

Upewnij się, że używasz co najmniej Firebase CLI w wersji 12.00 i firebase-functions w wersji 4.3.0. Każda nowsza wersja będzie obsługiwać zarówno 2 generacji, jak i 1 generacji.

Aktualizowanie importów

Funkcje 2 generacji importują z podpakietu v2 w pakiecie SDK firebase-functions. Ta inna ścieżka importu wystarczy Firebase CLI, aby określić, czy wdrożyć kod funkcji jako funkcję 1 czy 2 generacji.

Podpakiet v2 jest modułowy i zalecamy importowanie tylko tego modułu, którego potrzebujesz.

Przed: 1 generacja

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

Po: 2 generacja

// explicitly import each trigger
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

Aktualizowanie definicji aktywatorów

Ponieważ pakiet SDK 2 generacji preferuje importy modułowe, zaktualizuj definicje aktywatorów, aby odzwierciedlały zmiany importów z poprzedniego kroku.

Argumenty przekazywane do wywołań zwrotnych w przypadku niektórych aktywatorów uległy zmianie. W tym przykładzie zwróć uwagę, że argumenty wywołania zwrotnego onDocumentCreated zostały połączone w jeden obiekt event. Ponadto niektóre aktywatory mają wygodne nowe funkcje konfiguracyjne, takie jak opcja cors aktywatora onRequest.

Przed: 1 generacja

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

exports.date = functions.https.onRequest((req, res) => {
  // ...
});

exports.uppercase = functions.firestore
  .document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

Po: 2 generacja

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

Minimalizowanie wysiłku związanego z przepisywaniem kodu dzięki destrukturyzacji JavaScriptu

Jeśli Twoje funkcje mają złożone treści, które w dużym stopniu zależą od kontekstu 1 generacji lub parametrów specyficznych dla dostawcy (takich jak message lub snapshot), możesz użyć pomocników zgodności z 1 generacją wbudowanych w pakiet SDK 2 generacji.

Pakiet SDK 2 generacji automatycznie uzupełnia obiekt zdarzenia o gettery, które pasują do sygnatur 1 generacji. Dzięki temu możesz używać destrukturyzacji JavaScriptu do wyodrębniania tych właściwości bezpośrednio w sygnaturze obsługi, co minimalizuje potrzebę przepisywania logiki funkcji.

Informacje o mapowaniu dostawców

Dostawca Argumenty 1 generacji Destrukturyzacja zdarzenia uzupełnionego w 2 generacji
Pub/Sub (message, context) ({ message, context }) => { ... }
Firestore (snapshot, context) ({ snapshot, context }) => { ... }
Miejsce na dane (object, context) ({ object, context }) => { ... }
Baza danych czasu rzeczywistego (snapshot, context) ({ snapshot, context }) => { ... }
Zdalna konfiguracja (version, context) ({ version, context }) => { ... }
Algorytm szeregowania (context) ({ context }) => { ... }
Kolejka zadań (data, context) ({ data, context }) => { ... }

Przed (1 generacja):

export const myPubSubV1 = functions.pubsub.topic("my-topic").onPublish((message, context) => {
  const data = message.json;
  const eventId = context.eventId;
  // ... rest of the logic
});

Nowa alternatywa (2 generacja z destrukturyzacją):

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
});

Używanie konfiguracji sparametryzowanej

Funkcje 2 generacji nie obsługują functions.config na rzecz bezpieczniejszego interfejsu do deklaratywnego definiowania parametrów konfiguracji w bazie kodu. Dzięki nowemu modułowi params interfejs wiersza poleceń blokuje wdrożenie, chyba że wszystkie parametry mają prawidłową wartość, co gwarantuje, że funkcja nie zostanie wdrożona z brakującą konfiguracją.

Przed: 1 generacja

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

exports.getQuote = functions.https.onRequest(async (req, res) => {
  const quote = await fetchMotivationalQuote(functions.config().apiKey);
  // ...
});

Po: 2 generacja

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

Jeśli masz istniejącą konfigurację środowiska z functions.config, przenieś ją w ramach uaktualnienia do 2 generacji.

Interfejs API functions.config został wycofany i zostanie wyłączony w marcu 2027 r. Po tej dacie wdrożenia z functions.config będą się nie powodzić.

Aby zapobiec nieudanym wdrożeniom, przenieś konfigurację do Cloud Secret Manager za pomocą Firebase CLI. Jest to zdecydowanie zalecane jako najskuteczniejszy i najbezpieczniejszy sposób przenoszenia konfiguracji.

  1. Eksportowanie konfiguracji za pomocą Firebase CLI

    Użyj polecenia config export, aby wyeksportować istniejącą konfigurację środowiska do nowego obiektu tajnego w 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. Aktualizowanie kodu funkcji w celu powiązania obiektów tajnych

    Aby używać konfiguracji przechowywanej w nowym obiekcie tajnym w Cloud Secret Manager, użyj interfejsu API defineJsonSecret w źródle funkcji. Upewnij się też, że obiekty tajne są powiązane ze wszystkimi funkcjami, które ich potrzebują.

    Przed

    const functions = require("firebase-functions/v1");
    
    exports.myFunction = functions.https.onRequest((req, res) => {
      const apiKey = functions.config().someapi.key;
      // ...
    });
    

    Po

    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. Wdrażanie funkcji

    Wdróż zaktualizowane funkcje, aby zastosować zmiany i powiązać uprawnienia do obiektu tajnego.

    firebase deploy --only functions:<your-function-name>
    

Ustawianie opcji środowiska wykonawczego

Konfiguracja opcji środowiska wykonawczego zmieniła się między 1 a 2 generacją. 2 generacja dodaje też nową możliwość ustawiania opcji dla wszystkich funkcji.

Przed: 1 generacja

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

Po: 2 generacja

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

Aktualizowanie domyślnego konta usługi (opcjonalnie)

Funkcje 1 generacji używają domyślnego konta usługi Google App Engine do autoryzowania dostępu do interfejsów Firebase API, a funkcje 2 generacji używają domyślnego konta usługi Compute Engine. Ta różnica może prowadzić do problemów z uprawnieniami w przypadku funkcji przeniesionych do 2 generacji, jeśli przyznano specjalne uprawnienia kontu usługi 1 generacji. Jeśli nie zmieniono żadnych uprawnień konta usługi, możesz pominąć ten krok.

Zalecane rozwiązanie polega na przypisaniu istniejącego domyślnego konta usługi App Engine 1 generacji do funkcji, które chcesz przenieść do 2 generacji, zastępując domyślne konto 2 generacji. Możesz to zrobić, upewniając się, że każda przeniesiona funkcja ustawia prawidłową wartość 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) => {
  // ...
});

Możesz też zmodyfikować szczegóły konta usługi, aby pasowały do wszystkich niezbędnych uprawnień zarówno na domyślnym koncie usługi App Engine (w przypadku 1 generacji), jak i na domyślnym koncie usługi Compute Engine (w przypadku 2 generacji).

Używanie współbieżności

Istotną zaletą funkcji 2 generacji jest możliwość obsługiwania przez pojedynczą instancję funkcji więcej niż 1 żądania naraz. Może to znacznie zmniejszyć liczbę zimnych startów, z którymi spotykają się użytkownicy. Domyślnie współbieżność jest ustawiona na 80, ale możesz ustawić dowolną wartość od 1 do 1000:

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

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

Dostosowanie współbieżności może poprawić wydajność i zmniejszyć koszty funkcji. Więcej informacji o równoczesności znajdziesz w artykule Zezwalanie na żądania równoczesne.

Sprawdzanie użycia zmiennych globalnych

Funkcje 1 generacji napisane bez uwzględnienia współbieżności mogą używać zmiennych globalnych, które są ustawiane i odczytywane przy każdym żądaniu. Gdy równoczesność jest włączona, a pojedyncza instancja zaczyna obsługiwać wiele żądań naraz, może to spowodować błędy w funkcji, ponieważ żądania równoczesne zaczynają jednocześnie ustawiać i odczytywać zmienne globalne.

Podczas uaktualniania możesz ustawić procesor funkcji na gcf_gen1 i concurrency na 1, aby przywrócić działanie 1 generacji:

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

exports.date = onRequest({
    // TEMPORARY FIX: remove concurrency
    cpu: "gcf_gen1",
    concurrency: 1
  },
  (req, res) => {
    // ...
});

Nie jest to jednak zalecane jako rozwiązanie długoterminowe, ponieważ powoduje utratę zalet wydajnościowych funkcji 2 generacji. Zamiast tego sprawdź użycie zmiennych globalnych w funkcjach i usuń te ustawienia tymczasowe, gdy będziesz na to gotowy.

Przenoszenie ruchu do nowych funkcji 2 generacji

Podobnie jak w przypadku zmiany regionu funkcji lub typu aktywatora, musisz nadać funkcji 2 generacji nową nazwę i stopniowo przenosić do niej ruch.

Nie można uaktualnić funkcji z 1 do 2 generacji pod tą samą nazwą i uruchomić firebase deploy. Spowoduje to błąd:

Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.

Strategia migracji zależy od typu aktywatora używanego przez funkcję.

W przypadku aktywatorów wywoływanych, kolejki zadań i HTTP

Te aktywatory są wywoływane bezpośrednio. Ponieważ funkcja 2 generacji będzie miała nową nazwę (a w przypadku aktywatorów HTTP – nowy adres URL), możesz przenieść ruch, aktualizując klienty.

  1. Zmień nazwę funkcji w kodzie (np. zmień myCallable na myCallableV2).
  2. Wdróż funkcję. Obie funkcje – 1 i 2 generacji – są teraz uruchomione.
  3. Zaktualizuj kod klienta lub wywołującego, aby wskazywał nową nazwę lub adres URL funkcji 2 generacji.
  4. Gdy cały ruch zostanie przeniesiony do nowej funkcji, usuń funkcję 1 generacji za pomocą polecenia firebase functions:delete w Firebase CLI.

W przypadku aktywatorów działających w tle (Pub/Sub, Cloud Firestore, Cloud Storage, itp.)

Aktywatory działające w tle reagują na zdarzenia w projekcie. Aby uniknąć pominięcia żadnych zdarzeń podczas przejścia, musisz tymczasowo uruchomić funkcje 1 i 2 generacji obok siebie.

W okresie przejściowym obie funkcje będą się aktywować w przypadku tego samego zdarzenia. Oznacza to, że logika biznesowa będzie wykonywana 2 razy na zdarzenie. Zanim przejdziesz dalej, upewnij się, że funkcja jest idempotentna.

Krok 1. Dodaj funkcję 2 generacji obok funkcji 1 generacji

Zachowaj w kodzie dotychczasową funkcję 1 generacji i dodaj funkcję 2 generacji, która nasłuchuje tego samego źródła zdarzeń.

Wskazówka: użyj przekierowania do weryfikacji Aby uniknąć duplikowania logiki biznesowej w bazie kodu podczas przejścia, lub aby sprawdzić, czy funkcja 2 generacji prawidłowo odbiera zdarzenia, zanim w pełni jej zaufasz, ustaw funkcję 2 generacji jako przekierowanie, które wywołuje funkcję 1 generacji za pomocą metody 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);
});

Krok 2. Wdróż obie funkcje

Uruchom firebase deploy. Obie funkcje są teraz aktywne i nasłuchują tych samych zdarzeń.

Krok 3. Sprawdź, czy funkcja 2 generacji odbiera ruch

Monitoruj logi obu funkcji. Upewnij się, że funkcja 2 generacji jest wywoływana w przypadku wszystkich zdarzeń i że wywołania się powiodły.

Krok 4. Przenieś całą logikę do funkcji 2 generacji

Gdy będziesz mieć pewność, przenieś rzeczywistą logikę biznesową z funkcji 1 generacji do treści funkcji 2 generacji. Jeśli używasz metody przekierowania, usuń wywołanie 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 ...
});

Wdróż tę zmianę.

Krok 5. Cofnij wdrożenie funkcji 1 generacji

Usuń definicję funkcji 1 generacji z kodu i wdróż ponownie. Interfejs wiersza poleceń wyświetli prośbę o usunięcie funkcji 1 generacji z 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 ...
});