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, aby zapewnić większą wydajność, lepszą konfigurację, lepsze monitorowanie i 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 mają zastosowanie do JavaScriptu z ESM (importy w stylu import … from) i TypeScriptu.

Proces migracji

Funkcje 1 generacji i 2 generacji mogą współistnieć w tym samym pliku źródłowym. Dzięki temu możesz przenosić bazę kodu stopniowo, gdy będziesz na to gotowy. Pamiętaj jednak, że mieszanie pakietów nie działa w ramach jednej, odrębnej funkcji.

Zalecamy migrację po jednej funkcji naraz, a przed kontynuowaniem przeprowadzenie testów i weryfikacji.

Sprawdzanie wersji Firebase interfejsu CLI i firebase-functions

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

Aktualizowanie importów

Funkcje 2 generacji są importowane z pakietu podrzędnego v2 w pakiecie firebase-functions SDK. Ta inna ścieżka importu jest wszystkim, czego potrzebuje interfejs Firebase CLI, aby określić, czy wdrożyć kod funkcji jako funkcję 1 czy 2 generacji.

Pakiet podrzędny v2 jest modułowy i zalecamy importowanie tylko konkretnego modułu, którego potrzebujesz.

Wcześniej: 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");
obsługiwane tylko przez 2 generację nie będą działać w przypadku importu w wersji 1 i odwrotnie.

Aktualizowanie definicji aktywatorów

Pakiet SDK 2 generacji preferuje importowanie modułowe, więc zaktualizuj definicje aktywatorów, aby odzwierciedlały zmiany importu z poprzedniego kroku.

Argumenty przekazywane do wywołań zwrotnych w przypadku niektórych wyzwalaczy uległy zmianie. W tym przykładzie zwróć uwagę, że argumenty wywołania zwrotnego onDocumentCreated zostały skonsolidowane w jeden obiekt event. Niektóre wyzwalacze mają też nowe, wygodne funkcje konfiguracji, np. wyzwalacz onRequest ma opcję cors.

Wcześniej: 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 nakładu pracy związanego z przepisywaniem kodu dzięki destrukcji 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 (np. message lub snapshot), możesz użyć pomocniczych funkcji zgodności z 1 generacją wbudowanych w pakiet SDK 2 generacji.

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

Informacje o mapowaniu dostawców

Dostawca Argumenty 1 generacji 2nd gen Patched Event Destructuring
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 }) => { ... }
Kolejka zadań (data, context) ({ data, context }) => { ... }

Wcześniej (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 generacji 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ą już functions.config. Zamiast tego korzystają z bezpieczniejszego interfejsu do deklaratywnego definiowania parametrów konfiguracji w bazie kodu. Dzięki nowemu modułowi params interfejs CLI blokuje wdrażanie, chyba że wszystkie parametry mają prawidłową wartość. Dzięki temu funkcja nie jest wdrażana z brakującą konfiguracją.

Wcześniej: 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 parametrem functions.config nie będą się powodzić.

Aby zapobiec niepowodzeniom wdrażania, przenieś konfigurację do usługi Cloud Secret Manager za pomocą interfejsu wiersza poleceń Firebase. Jest to zdecydowanie zalecane jako najskuteczniejszy i najbezpieczniejszy sposób przenoszenia konfiguracji.

  1. Konfigurowanie eksportu za pomocą interfejsu wiersza poleceń Firebase

    Aby wyeksportować istniejącą konfigurację środowiska do nowego sekretu w usłudze Cloud Secret Manager, użyj tego polecenia:config export

    $ 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żyć konfiguracji przechowywanej w nowym obiekcie tajnym w usłudze Cloud Secret Manager, użyj interfejsu defineJsonSecret API 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 sekretu.

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

Ustawianie opcji środowiska wykonawczego

Konfiguracja opcji środowiska wykonawczego zmieniła się między 1 a 2 generacją. W 2 generacji dodano też nową funkcję, która umożliwia ustawianie opcji dla wszystkich funkcji.

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

Aktualizacja domyślnego konta usługi (opcjonalnie)

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

Zalecanym rozwiązaniem jest jawne przypisanie 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 usługi 2 generacji. Możesz to zrobić, upewniając się, że każda migrowana funkcja ustawia prawidłową wartość zmiennej 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 dopasować je do wszystkich niezbędnych uprawnień zarówno na App Enginedomyślnym koncie usługi (w przypadku 1 generacji), jak i na Compute Enginedomyślnym koncie usługi (w przypadku 2 generacji).

Korzystanie z równoczesności

Istotną zaletą funkcji 2 generacji jest możliwość obsługiwania przez jedną instancję funkcji więcej niż jednego żądania naraz. Może to znacznie zmniejszyć liczbę uruchomień „na zimno” u użytkowników. Domyślnie równoczesność jest ustawiona na 80, ale możesz ustawić dowolną wartość z zakresu od 1 do 1000:

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

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

Dostrajanie współbieżności może poprawić wydajność i obniżyć koszty funkcji. Więcej informacji o równoczesności znajdziesz w sekcji Zezwalaj na równoczesne żądania.

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 w przypadku każdego żądania. Jeśli współbieżność jest włączona, a pojedyncza instancja zaczyna obsługiwać wiele żądań jednocześnie, w funkcji mogą pojawić się błędy, ponieważ współbieżne żądania zaczynają jednocześnie ustawiać i odczytywać zmienne globalne.

Podczas uaktualniania możesz ustawić procesor funkcji na gcf_gen1 i ustawić concurrency na 1, aby przywrócić działanie funkcji 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ę korzyści związanych z wydajnością funkcji 2 generacji. Zamiast tego sprawdź użycie zmiennych globalnych w funkcjach i usuń te ustawienia tymczasowe, gdy będziesz gotowy(-a).

Przenoszenie ruchu do nowych funkcji 2 generacji

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

Nie można uaktualnić funkcji z 1 generacji do 2 generacji o tej samej nazwie 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 wyzwalacza używanego przez funkcję.

Migracja wywoływanych funkcji, kolejek zadań i wyzwalaczy HTTP

Te wyzwalacze są bezpośrednimi wywołaniami. Funkcja 2 generacji będzie miała nową nazwę (i nowy adres URL w przypadku aktywatorów HTTP), więc możesz przenieść ruch, aktualizując klientów.

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

Migracja wyzwalaczy w tle

Aktywatory działające w tle (np. aktywatory Pub/Sub, Cloud FirestoreCloud Storage) reagują na zdarzenia w projekcie. Aby uniknąć utraty zdarzeń podczas przejścia, musisz tymczasowo uruchomić funkcje 1 i 2 generacji równolegle.

W okresie przejściowym obie funkcje będą wywoływane przez to samo zdarzenie. Oznacza to, że logika biznesowa będzie uruchamiana 2 razy na zdarzenie. Zanim przejdziesz dalej, upewnij się, że funkcja jest idempotentna.

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

    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. Uruchom firebase deploy. Obie funkcje są teraz aktywne i nasłuchują tych samych zdarzeń.

  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 są wykonywane prawidłowo.

  4. Gdy upewnisz się, że funkcja działa prawidłowo, przenieś rzeczywistą logikę biznesową z funkcji 1 generacji do treści funkcji 2 generacji. Jeśli używasz metody przekazywania, 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ę.

  5. Usuń definicję funkcji 1 generacji z kodu i wdróż ponownie. Interfejs CLI wyświetli prośbę o usunięcie funkcji 1 generacji z Google Cloud.