Gérer les événements de cycle de vie de votre extension

Votre extension peut inclure des fonctions Cloud Tasks qui se déclenchent lorsqu'une instance d'extension passe par l'un des événements de cycle de vie suivants:

  • Une instance de l'extension est installée
  • Une instance de l'extension est mise à jour vers une nouvelle version
  • La configuration d'une instance d'extension est modifiée

L'un des cas d'utilisation les plus importants de cette fonctionnalité est le remplissage des données. Par exemple, supposons que vous créiez une extension qui génère des vignettes d'aperçus d'images importées dans un bucket Cloud Storage. La tâche principale de votre extension consisterait à utiliser une fonction déclenchée par l'événement onFinalize Cloud Storage. Toutefois, seules les images importées après l'installation de l'extension seront traitées. En incluant dans votre extension une fonction déclenchée par l'événement de cycle de vie onInstall, vous pouvez également générer des aperçus de miniatures de toutes les images existantes lorsque l'extension est installée.

Voici d'autres cas d'utilisation des déclencheurs d'événements de cycle de vie:

  • Automatisez la configuration post-installation (création d'enregistrements de base de données, indexation, etc.).
  • Si vous devez publier des modifications non rétrocompatibles, migrez automatiquement les données lors de la mise à jour.

Gestionnaires d'événements de cycle de vie de courte durée

Si votre tâche peut s'exécuter complètement dans la durée maximale de Cloud Functions (9 minutes avec l'API de première génération), vous pouvez écrire votre gestionnaire d'événements de cycle de vie en tant que fonction unique qui se déclenche sur l'événement onDispatch de la file d'attente de tâches:

export const myTaskFunction = functions.tasks.taskQueue()
  .onDispatch(async () => {
    // Complete your lifecycle event handling task.
    // ...

    // When processing is complete, report status to the user (see below).
  });

Ensuite, dans le fichier extension.yaml de votre extension, procédez comme suit:

  1. Enregistrez votre fonction en tant que ressource d'extension avec l'ensemble de propriétés taskQueueTrigger. Si vous définissez taskQueueTrigger sur la carte vide ({}), votre extension provisionnera une file d'attente Cloud Tasks à l'aide des paramètres par défaut. Vous pouvez éventuellement ajuster ces paramètres.

    resources:
      - name: myTaskFunction
        type: firebaseextensions.v1beta.function
        description: >-
          Describe the task performed when the function is triggered by a lifecycle
          event
        properties:
          location: ${LOCATION}
          taskQueueTrigger: {}
    
  2. Enregistrez votre fonction en tant que gestionnaire d'un ou de plusieurs événements de cycle de vie:

    resources:
      - ...
    lifecycleEvents:
      onInstall:
        function: myTaskFunction
        processingMessage: Resizing your existing images
      onUpdate:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
      onConfigure:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
    
    

    Vous pouvez enregistrer des fonctions pour l'un des événements suivants: onInstall, onUpdate et onConfigure. Tous ces événements sont facultatifs.

  3. Recommandé: Si la tâche de traitement n'est pas requise pour que votre extension fonctionne, ajoutez un paramètre configuré par l'utilisateur qui permet aux utilisateurs de choisir de l'activer ou non.

    Par exemple, ajoutez un paramètre comme suit:

    params:
      - param: DO_BACKFILL
        label: Backfill existing images
        description: >
          Should existing, unresized images in the Storage bucket be resized as well?
        type: select
        options:
          - label: Yes
            value: true
          - label: No
            value: false
    

    Dans votre fonction, si le paramètre est défini sur false, quittez-la plus tôt:

    export const myTaskFunction = functions.tasks.taskQueue()
      .onDispatch(async () => {
        if (!process.env.DO_BACKFILL) {
          await runtime.setProcessingState(
            "PROCESSING_COMPLETE",
            "Existing images were not resized."
          );
          return;
        }
        // Complete your lifecycle event handling task.
        // ...
      });
    

Exécuter des tâches de longue durée

Si la tâche ne peut pas être terminée dans le délai maximal Cloud Functions, scindez-la en sous-tâches et effectuez chacune d'elles dans l'ordre en mettant les tâches en file d'attente à l'aide de la méthode TaskQueue.enqueue() du SDK Admin.

Imaginons que vous souhaitiez remplir les données Cloud Firestore. Vous pouvez diviser la collection de documents en segments à l'aide de curseurs de requête. Après avoir traité un bloc, avancez le décalage de début et mettez en file d'attente une autre invocation de fonction, comme indiqué ci-dessous:

import { getFirestore } from "firebase-admin/firestore";
import { getFunctions } from "firebase-admin/functions";

exports.backfilldata = functions.tasks.taskQueue().onDispatch(async (data) => {
  // When a lifecycle event triggers this function, it doesn't pass any data,
  // so an undefined offset indicates we're on our first invocation and should
  // start at offset 0. On subsequent invocations, we'll pass an explicit
  // offset.
  const offset = data["offset"] ?? 0;

  // Get a batch of documents, beginning at the offset.
  const snapshot = await getFirestore()
    .collection(process.env.COLLECTION_PATH)
    .startAt(offset)
    .limit(DOCS_PER_BACKFILL)
    .get();
  // Process each document in the batch.
  const processed = await Promise.allSettled(
    snapshot.docs.map(async (documentSnapshot) => {
      // Perform the processing.
    })
  );

  // If we processed a full batch, there are probably more documents to
  // process, so enqueue another invocation of this function, specifying
  // the offset to start with.
  //
  // If we processed less than a full batch, we're done.
  if (processed.length == DOCS_PER_BACKFILL) {
    const queue = getFunctions().taskQueue(
      "backfilldata",
      process.env.EXT_INSTANCE_ID
    );
    await queue.enqueue({
      offset: offset + DOCS_PER_BACKFILL,
    });
  } else {
      // Processing is complete. Report status to the user (see below).
  }
});

Ajoutez la fonction à votre extension.yaml comme décrit dans la section précédente.

État des rapports

Lorsque toutes vos fonctions de traitement sont terminées, avec succès ou avec une erreur, signalez l'état de la tâche à l'aide des méthodes d'exécution de l'extension du SDK d'administration. Les utilisateurs peuvent voir cet état sur la page d'informations de l'extension dans la console Firebase.

Exécution réussie et erreurs non fatales

Pour signaler la réussite et les erreurs non fatales (erreurs qui ne mettent pas l'extension dans un état non fonctionnel), utilisez la méthode d'exécution de l'extension setProcessingState() du SDK Admin:

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setProcessingState(processingState, message);

Vous pouvez définir les états suivants:

États non fatals
PROCESSING_COMPLETE

Utilisez-le pour signaler la réussite de la tâche. Exemple :

getExtensions().runtime().setProcessingState(
  "PROCESSING_COMPLETE",
  `Backfill complete. Successfully processed ${numSuccess} documents.`
);
PROCESSING_WARNING

À utiliser pour signaler une réussite partielle. Exemple :

getExtensions().runtime().setProcessingState(
  "PROCESSING_WARNING",
  `Backfill complete. ${numSuccess} documents processed successfully.`
    + ` ${numFailed} documents failed to process. ${listOfErrors}.`
    + ` ${instructionsToFixTheProblem}`
);
PROCESSING_FAILED

À utiliser pour signaler les erreurs qui empêchent l'exécution de la tâche, mais qui ne rendent pas l'extension inutilisable. Exemple :

getExtensions().runtime().setProcessingState(
  "PROCESSING_FAILED",
  `Backfill failed. ${errorMsg} ${optionalInstructionsToFixTheProblem}.`
);

Pour signaler les erreurs qui empêchent l'extension de fonctionner, appelez setFatalError().

NONE

Permet d'effacer l'état de la tâche. Vous pouvez éventuellement utiliser cette option pour effacer le message d'état de la console (par exemple, après un certain temps écoulé depuis le paramètre PROCESSING_COMPLETE). Exemple:

getExtensions().runtime().setProcessingState("NONE");

Erreurs fatales

Si une erreur empêche l'extension de fonctionner (par exemple, une tâche de configuration requise échoue), signalez l'erreur fatale avec setFatalError():

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setFatalError(`Post-installation setup failed. ${errorMessage}`);

Régler la file d'attente de tâches

Si vous définissez la propriété taskQueueTrigger sur {}, votre extension provisionnera une file d'attente Cloud Tasks avec les paramètres par défaut lorsqu'une instance d'extension sera installée. Vous pouvez également ajuster les limites de simultanéité et le comportement de nouvelle tentative de la file d'attente de tâches en fournissant des valeurs spécifiques:

resources:
  - name: myTaskFunction
    type: firebaseextensions.v1beta.function
    description: >-
      Perform a task when triggered by a lifecycle event
    properties:
      location: ${LOCATION}
      taskQueueTrigger:
        rateLimits:
          maxConcurrentDispatches: 1000
          maxDispatchesPerSecond: 500
        retryConfig:
          maxAttempts: 100  # Warning: setting this too low can prevent the function from running
          minBackoffSeconds: 0.1
          maxBackoffSeconds: 3600
          maxDoublings: 16
lifecycleEvents:
  onInstall: 
    function: myTaskFunction
    processingMessage: Resizing your existing images
  onUpdate:
    function: myTaskFunction
    processingMessage: Setting up your extension
  onConfigure:
    function: myOtherTaskFunction
    processingMessage: Setting up your extension

Pour en savoir plus sur ces paramètres, consultez la section Configurer des files d'attente Cloud Tasks dans la documentation Google Cloud.

N'essayez pas de spécifier des paramètres de file d'attente de tâches en les transmettant à taskQueue(). Ces paramètres sont ignorés au profit de la configuration dans extension.yaml et des valeurs par défaut de la configuration.

Par exemple, la solution ci-dessous ne fonctionnera pas:

export const myBrokenTaskFunction = functions.tasks
  // DON'T DO THIS IN AN EXTENSION! THESE SETTINGS ARE IGNORED.
  .taskQueue({
    retryConfig: {
      maxAttempts: 5,
      minBackoffSeconds: 60,
    },
    rateLimits: {
      maxConcurrentDispatches: 1000,
      maxDispatchesPerSecond: 10,
    },
  })
  .onDispatch(
    // ...
  );

La propriété taskQueueTrigger dans extension.yaml est le seul moyen de configurer les files d'attente de tâches d'une extension.

Exemples

Les extensions storage-resize-images, firestore-bigquery-export et firestore-translate-text officielles utilisent toutes des gestionnaires d'événements de cycle de vie pour remplir les données.