Gestisci gli eventi del ciclo di vita delle estensioni

L'estensione può includere funzioni Cloud Tasks che si attivano quando un'istanza dell'estensione passa per uno dei seguenti eventi di ciclo di vita:

  • È installata un'istanza dell'estensione
  • Un'istanza dell'estensione viene aggiornata a una nuova versione
  • La configurazione di un'istanza dell'estensione viene modificata

Uno dei casi d'uso più importanti di questa funzionalità è il backfill dei dati. Ad esempio, supponiamo che tu stia creando un'estensione che generi miniature di anteprima delle immagini caricate in un bucket Cloud Storage. Il lavoro principale dell'estensione verrà eseguito in una funzione attivata dall'evento onFinalize Cloud Storage. Tuttavia, verranno elaborate solo le immagini caricate dopo l'installazione dell'estensione. Se includi nella tua estensione una funzione attivata dall'onInstallevento di ciclo di vita, puoi anche generare anteprime delle miniature di eventuali immagini esistenti quando l'estensione è installata.

Ecco alcuni altri casi d'uso degli attivatori di eventi del ciclo di vita:

  • Automatizzare la configurazione post-installazione (creazione di record di database, indicizzazione e così via)
  • Se devi pubblicare modifiche non compatibili con le versioni precedenti, esegui la migrazione automatica dei dati al momento dell'aggiornamento

Gestori di eventi del ciclo di vita a breve esecuzione

Se l'attività può essere eseguita completamente entro la durata massima Cloud Functions (9 minuti utilizzando l'API di prima generazione), puoi scrivere il gestore degli eventi del ciclo di vita come una singola funzione che si attiva sull'evento onDispatch della coda di attività:

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

Poi, nel file extension.yaml dell'estensione, procedi nel seguente modo:

  1. Registra la funzione come risorsa di estensione con il set di proprietà taskQueueTrigger. Se imposti taskQueueTrigger sulla mappa vuota ({}), l'estensione eseguirà il provisioning di una coda Cloud Tasks utilizzando le impostazioni predefinite. Se vuoi, puoi ottimizzare queste impostazioni.

    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. Registra la tua funzione come gestore per uno o più eventi del ciclo di vita:

    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
    
    

    Puoi registrare le funzioni per uno qualsiasi dei seguenti eventi: onInstall, onUpdate e onConfigure. Tutti questi eventi sono facoltativi.

  3. Consigliato: se l'attività di elaborazione non è necessaria per il funzionamento dell'estensione, aggiungi un parametro configurato dall'utente che consenta agli utenti di scegliere se attivarla.

    Ad esempio, aggiungi un parametro simile al seguente:

    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
    

    E nella funzione, se il parametro è impostato su false, esci in anticipo:

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

Eseguire attività lunghe

Se l'attività non può essere completata entro la durata massima di Cloud Functions, suddividela in attività secondarie ed eseguile in sequenza accodando i job con il metodo TaskQueue.enqueue() di SDK Admin.

Ad esempio, supponiamo di voler eseguire il backfill dei dati Cloud Firestore. Puoi dividere la raccolta di documenti in blocchi utilizzando i cursori di query. Dopo aver elaborato un chunk, avanza l'offset iniziale e metti in coda un'altra chiamata di funzione come mostrato di seguito:

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

Aggiungi la funzione a extension.yaml come descritto nella sezione precedente.

Stato dei report

Al termine di tutte le funzioni di elaborazione, con esito positivo o con un errore, segnala lo stato dell'attività utilizzando i metodi di runtime dell'estensione dell'SDK Amministrazione. Gli utenti possono vedere questo stato nella pagina dei dettagli dell'estensione della console Firebase.

Completamento riuscito ed errori non irreversibili

Per segnalare il completamento riuscito e gli errori non fatali (errori che non mettono l'estensione in uno stato non funzionale), utilizza il metodo di runtime dell'estensione setProcessingState() dell'SDK di amministrazione:

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

// ...

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

Puoi impostare i seguenti stati:

Stati non irreversibili
PROCESSING_COMPLETE

Da utilizzare per segnalare il completamento dell'attività. Esempio:

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

Da utilizzare per segnalare un buon esito parziale. Esempio:

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

Da utilizzare per segnalare errori che impediscono il completamento dell'attività, ma che non rendono inutilizzabile l'estensione. Esempio:

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

Per segnalare errori che rendono inutilizzabili l'estensione, chiama setFatalError().

NONE

Utilizza questa opzione per cancellare lo stato dell'attività. Se vuoi, puoi utilizzarlo per cancellare il messaggio di stato dalla console (ad esempio, dopo un certo periodo di tempo dall'impostazione PROCESSING_COMPLETE). Esempio:

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

Errori irreversibili

Se si verifica un errore che impedisce il funzionamento dell'estensione, ad esempio un errore di configurazione obbligatorio, segnala l'errore fatale con setFatalError():

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

// ...

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

Ottimizzazione della coda di attività

Se imposti la proprietà taskQueueTrigger su {}, l'estensione effettuerà il provisioning di una coda Cloud Tasks con le impostazioni predefinite quando viene installata un'istanza dell'estensione. In alternativa, puoi ottimizzare i limiti di concorrenza e il comportamento di ripetizione della coda di attività specificando valori specifici:

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

Per informazioni dettagliate su questi parametri, consulta Configurare le code Cloud Tasks nella documentazione di Google Cloud.

Non tentare di specificare i parametri della coda di attività passandoli a taskQueue(). Queste impostazioni vengono ignorate a favore della configurazione in extension.yaml e delle impostazioni predefinite della configurazione.

Ad esempio, non funzionerà:

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 proprietà taskQueueTrigger in extension.yaml è l'unico modo per configurare le code di attività di un'estensione.

Esempi

Le estensioni ufficiali storage-resize-images, firestore-bigquery-export e firestore-translate-text utilizzano tutte i gestori degli eventi del ciclo di vita per il completamento dei dati.