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 dotyczą 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 oraz przeprowadzenie testów i weryfikacji przed kontynuowaniem.

Sprawdzanie wersji interfejsu wiersza poleceń Firebase 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 SDK firebase-functions. 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.

v2 to pakiet podrzędny o budowie modułowej. 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");

Aktualizowanie definicji wyzwalaczy

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

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

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 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 (np. message lub snapshot), możesz użyć narzędzi ułatwiających zgodność z 1 generacją, które są wbudowane 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 konieczność ponownego pisania logiki funkcji.

Informacje o mapowaniu dostawców

Dostawca Argumenty dotyczące 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, dopóki wszystkie parametry nie będą miały prawidłowej wartości. Dzięki temu funkcja nie zostanie wdrożona bez brakującej konfiguracji.

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 functions.config API został wycofany i zostanie zamknięty w marcu 2027 r. Po tym terminie wdrożenia z wartością 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 wysoce zalecane, ponieważ jest to 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 w źródle funkcji interfejsu defineJsonSecret API. 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 wpisu 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 ma też nową funkcję ustawiania 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) => {
  /* ... */
});

Aktualizowanie domyślnego konta usługi (opcjonalnie)

Funkcje 1 generacji używają Google App Enginedomyślnego konta usługi do autoryzowania dostępu do interfejsów Firebase API, a funkcje 2 generacji używają Compute Enginedomyś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 uprawnień konta usługi, możesz pominąć ten krok.

Zalecane rozwiązanie to 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).

Używanie 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 równoczesność jest włączona, a pojedyncza instancja zaczyna obsługiwać wiele żądań jednocześnie, w funkcji mogą pojawić się błędy, ponieważ równoczesne żą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 1 generacji:

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

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

Nie zalecamy jednak tego rozwiązania jako długoterminowego, ponieważ powoduje ono utratę zalet funkcji 2 generacji w zakresie skuteczności. 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

Są to bezpośrednie wywołania. 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 aktywatorów w tle

Aktywatory w tle (np. aktywatory Pub/Sub, Cloud Firestore i Cloud 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 istniejącą 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 wdroż ją ponownie. Interfejs CLI wyświetli prośbę o usunięcie funkcji 1 generacji z Google Cloud.