Passer des fonctions Node.js de 1re génération à celles de 2e génération

Les applications qui utilisent des fonctions de 1re génération doivent envisager de migrer vers la 2e génération en suivant les instructions de ce guide. Les fonctions de 2e génération utilisent Cloud Run pour offrir de meilleures performances, une meilleure configuration, une meilleure surveillance, etc.

Les exemples de ce document supposent que vous utilisez JavaScript avec des modules CommonJS (importations de style require), mais les mêmes principes s'appliquent à JavaScript avec ESM (importations de style import … from) et à TypeScript.

Le processus de migration

Les fonctions de 1re génération et de 2e génération peuvent coexister dans le même fichier source. Cela vous permet de migrer votre base de code progressivement, lorsque vous êtes prêt. Notez toutefois que ce mélange de packages ne fonctionne pas dans une fonction unique et discrète.

Nous vous recommandons de migrer une fonction à la fois, en effectuant des tests et des vérifications avant de continuer.

Vérifier les versions de Firebase CLI et de firebase-functions

Assurez-vous d'utiliser au moins la version Firebase de CLI 12.00 et la version firebase-functions de 4.3.0. Toute version plus récente sera compatible avec la 2e génération ainsi qu'avec la 1re génération.

Mettre à jour les importations

Les fonctions de 2e génération importent à partir du sous-package v2 du SDK firebase-functions. Ce chemin d'importation différent est tout ce dont le Firebase CLI a besoin pour déterminer s'il faut déployer le code de votre fonction en tant que fonction de 1re ou 2e génération.

Le sous-package v2 est modulaire. Nous vous recommandons de n'importer que le module spécifique dont vous avez besoin.

Avant : 1re génération

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

Après : 2e génération

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

Mettre à jour les définitions de déclencheurs

Étant donné que le SDK de 2e génération privilégie les importations modulaires, mettez à jour les définitions de déclencheurs pour refléter les modifications apportées aux importations à l'étape précédente.

Les arguments transmis aux rappels de certains déclencheurs ont changé. Dans cet exemple, notez que les arguments du rappel onDocumentCreated ont été regroupés dans un seul objet event. De plus, certains déclencheurs disposent de nouvelles fonctionnalités de configuration pratiques, comme l'option cors du déclencheur onRequest.

Avant : 1re génération

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

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

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

Après : 2e génération

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

Minimiser les efforts de réécriture avec la déstructuration JavaScript

Si vos fonctions comportent des corps complexes qui reposent fortement sur le contexte de 1re génération ou des paramètres spécifiques au fournisseur (comme message ou snapshot), vous pouvez utiliser les assistants de compatibilité de 1re génération intégrés au SDK de 2e génération.

Le SDK de 2e génération corrige automatiquement l'objet d'événement avec des getters qui correspondent aux signatures de 1re génération. Cela vous permet d'utiliser la déstructuration JavaScript pour extraire ces propriétés directement dans la signature du gestionnaire, ce qui minimise la nécessité de réécrire la logique de votre fonction.

Documentation de référence sur le mappage des fournisseurs

Fournisseur Arguments de 1re génération Déstructuration d'événements corrigés de 2e génération
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 }) => { ... }
File d'attente de tâches (data, context) ({ data, context }) => { ... }

Avant (1re génération) :

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

Nouvelle alternative (2e génération avec déstructuration) :

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

Utiliser une configuration paramétrée

Les fonctions de 2e génération abandonnent la compatibilité avec functions.config au profit d'une interface plus sécurisée pour définir les paramètres de configuration de manière déclarative dans votre base de code. Avec le nouveau module params, la CLI bloque le déploiement, sauf si tous les paramètres ont une valeur valide, ce qui garantit qu'une fonction n'est pas déployée avec une configuration manquante.

Avant : 1re génération

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

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

Après : 2e génération

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

Si vous disposez d'une configuration d'environnement existante avec functions.config, migrez-la dans le cadre de votre mise à niveau vers la 2e génération.

L'API functions.config est obsolète et sera mise hors service en mars 2027. Après cette date, les déploiements avec functions.config échoueront.

Pour éviter les échecs de déploiement, migrez votre configuration vers Cloud Secret Manager à l'aide de Firebase CLI. Cette méthode est fortement recommandée, car elle constitue le moyen le plus efficace et le plus sécurisé de migrer votre configuration.

  1. Exporter la configuration avec Firebase CLI

    Utilisez la commande config export pour exporter votre configuration d'environnement existante vers un nouveau secret dans 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. Mettre à jour le code de la fonction pour lier les secrets

    Pour utiliser la configuration stockée dans le nouveau secret de Cloud Secret Manager, utilisez l'API defineJsonSecret dans la source de votre fonction. Assurez-vous également que les secrets sont liés à toutes les fonctions qui en ont besoin.

    Avant

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

    Après

    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. Déployer des fonctions

    Déployez vos fonctions mises à jour pour appliquer les modifications et lier les autorisations secrètes.

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

Définir les options d'exécution

La configuration des options d'exécution a changé entre la 1re et la 2e génération. La 2e génération ajoute également une nouvelle fonctionnalité permettant de définir des options pour toutes les fonctions.

Avant : 1re génération

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

Après : 2e génération

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

Mettre à jour le compte de service par défaut (facultatif)

Alors que les fonctions de 1re génération utilisent le compte de service Google App Engine par défaut pour autoriser l'accès aux API Firebase, les fonctions de 2e génération utilisent le compte de service Compute Engine par défaut. Cette différence peut entraîner des problèmes d'autorisations pour les fonctions migrées vers la 2e génération dans les cas où vous avez accordé des autorisations spéciales au compte de service de 1re génération. Si vous n'avez modifié aucune autorisation de compte de service, vous pouvez ignorer cette étape.

La solution recommandée consiste à attribuer explicitement le compte de service par défaut App Engine de 1re génération existant aux fonctions que vous souhaitez migrer vers la 2e génération, en remplaçant la valeur par défaut de la 2e génération.App Engine Pour ce faire, assurez-vous que chaque fonction migrée définit la valeur correcte pour 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) => {
  // ...
});

Vous pouvez également modifier les détails du compte de service pour qu'ils correspondent à toutes les autorisations nécessaires sur le compte de service par défaut App Engine (pour la 1re génération) et sur le compte de service par défaut Compute Engine (pour la 2e génération).

Utiliser la simultanéité

Un avantage important des fonctions de 2e génération est la capacité d'une seule instance de fonction à traiter plusieurs requêtes à la fois. Cela peut réduire considérablement le nombre de démarrages à froid rencontrés par les utilisateurs finaux. Par défaut, la simultanéité est définie sur 80, mais vous pouvez la définir sur n'importe quelle valeur comprise entre 1 et 1 000 :

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

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

Le réglage de la simultanéité peut améliorer les performances et réduire le coût des fonctions. Pour en savoir plus sur la simultanéité, consultez Autoriser les requêtes simultanées.

Auditer l'utilisation des variables globales

Les fonctions de 1re génération écrites sans tenir compte de la simultanéité peuvent utiliser des variables globales qui sont définies et lues à chaque requête. Lorsque la simultanéité est activée et qu'une seule instance commence à traiter plusieurs requêtes à la fois, cela peut introduire des bugs dans votre fonction, car les requêtes simultanées commencent à définir et à lire des variables globales simultanément.

Lors de la mise à niveau, vous pouvez définir le processeur de votre fonction sur gcf_gen1 et définir concurrency sur 1 pour restaurer le comportement de 1re génération :

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

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

Toutefois, cette solution n'est pas recommandée à long terme, car elle annule les avantages en termes de performances des fonctions de 2e génération. Auditez plutôt l'utilisation des variables globales dans vos fonctions et supprimez ces paramètres temporaires lorsque vous êtes prêt.

Migrer le trafic vers les nouvelles fonctions de 2e génération

Comme lorsque vous modifiez la région ou le type de déclencheur d'une fonction, vous devez attribuer un nouveau nom à la fonction de 2e génération et migrer lentement le trafic vers celle-ci.

Il n'est pas possible de mettre à niveau une fonction de la 1re à la 2e génération avec le même nom et d'exécuter firebase deploy. Cela entraînera l'erreur suivante :

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

La stratégie de migration dépend du type de déclencheur utilisé par votre fonction.

Migrer les déclencheurs appelables, de file d'attente de tâches et HTTP

Ces déclencheurs sont des appels directs. Étant donné que la fonction de 2e génération aura un nouveau nom (et une nouvelle URL pour les déclencheurs HTTP), vous pouvez migrer le trafic en mettant à jour les clients.

  1. Renommez la fonction dans votre code (par exemple, remplacez myCallable par myCallableV2).
  2. Déployez la fonction. Les fonctions de 1re et 2e génération sont désormais en cours d'exécution.
  3. Mettez à jour le code ou l'appelant de votre client pour qu'il pointe vers le nouveau nom ou la nouvelle URL de la fonction de 2e génération.
  4. Une fois que tout le trafic a été transféré vers la nouvelle fonction, supprimez la fonction de 1re génération à l'aide de la Firebase CLI's firebase functions:delete commande.

Migrer les déclencheurs en arrière-plan

Les déclencheurs en arrière-plan (tels que Pub/Sub, Cloud Firestore, et Cloud Storage déclencheurs) répondent aux événements de votre projet. Pour éviter de manquer des événements pendant la transition, vous devez exécuter temporairement les fonctions de 1re et 2e génération côte à côte.

Pendant la période de transition, les deux fonctions se déclencheront sur le même événement. Cela signifie que votre logique métier s'exécutera deux fois par événement. Assurez-vous que votre fonction est idempotente avant de continuer.

  1. Ajoutez la fonction de 2e génération à côté de la fonction de 1re génération, en conservant la fonction de 1re génération existante dans votre code et en ajoutant la fonction de 2e génération qui écoute la même source d'événements.

    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. Exécutez firebase deploy. Les deux fonctions sont désormais actives et écoutent les mêmes événements.

  3. Vérifiez que la fonction de 2e génération reçoit du trafic. Surveillez les journaux des deux fonctions. Assurez-vous que la fonction de 2e génération est appelée pour tous les événements et que les appels réussissent.

  4. Une fois que vous êtes certain que la fonction fonctionne correctement, déplacez la logique métier réelle de la fonction de 1re génération vers le corps de la fonction de 2e génération. Si vous avez utilisé la méthode passthrough, supprimez l'appel à 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 ...
    });
    

    Déployez cette modification.

  5. Supprimez la définition de la fonction de 1re génération de votre code et redéployez-la. La CLI vous invite à supprimer la fonction de 1re génération de Google Cloud.