Obsługa zdarzeń cyklu życia rozszerzenia

Rozszerzenie może zawierać funkcje Cloud Tasks, które są aktywowane, gdy instancja rozszerzenia przechodzi przez dowolne z tych zdarzeń cyklu życia:

  • Zainstalowana jest instancja rozszerzenia
  • Instancja rozszerzenia jest aktualizowana do nowej wersji
  • Zmieniono konfigurację instancji rozszerzenia

Jednym z najważniejszych przypadków użycia tej funkcji jest tworzenie kopii zapasowej danych. Załóżmy na przykład, że tworzysz rozszerzenie, które generuje podgląd miniatur obrazów przesłanych do zasobnika Cloud Storage. Główna praca Twojego rozszerzenia jest wykonywana w funkcji aktywowanej przez zdarzenie Cloud Storage onFinalize. Jednak tylko obrazy przesłane po zainstalowaniu rozszerzenia będą przetwarzane. Jeśli dodasz do rozszerzenia funkcję uruchamianą przez zdarzenie cyklu życia onInstall, po zainstalowaniu rozszerzenia możesz też wygenerować podgląd miniatur dowolnych istniejących obrazów.

Inne przypadki użycia aktywatorów zdarzeń cyklu życia:

  • Zautomatyzuj konfigurację po instalacji (tworzenie rekordów bazy danych, indeksowanie itp.)
  • Jeśli musisz opublikować zmiany niezgodne wstecznie, przenieś dane automatycznie po aktualizacji

Krótkie moduły obsługi zdarzeń cyklu życia

Jeśli zadanie może zostać wykonane w całości w ramach maksymalnego czasu trwania funkcji w Cloud Functions (9 minut przy użyciu interfejsu API pierwszej generacji), możesz zapisać moduł obsługi zdarzeń cyklu życia jako pojedynczą funkcję, która aktywuje zdarzenie onDispatch kolejki zadań:

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

Następnie w pliku extension.yaml rozszerzenia wykonaj te czynności:

  1. Zarejestruj funkcję jako zasób rozszerzenia za pomocą zestawu właściwości taskQueueTrigger. Jeśli ustawisz taskQueueTrigger na pustą mapę ({}), rozszerzenie udostępni kolejkę Cloud Tasks z użyciem ustawień domyślnych. Możesz opcjonalnie dostroić te ustawienia.

    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. Zarejestruj funkcję jako moduł obsługi co najmniej 1 zdarzenia cyklu życia:

    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
    
    

    Możesz zarejestrować funkcje dla tych zdarzeń: onInstall, onUpdate i onConfigure. Wszystkie te zdarzenia są opcjonalne.

  3. Zalecane: jeśli zadanie przetwarzania nie jest wymagane do działania rozszerzenia, dodaj parametr skonfigurowany przez użytkownika, który pozwoli użytkownikom zdecydować, czy chce je włączyć.

    Na przykład dodaj taki parametr:

    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
    

    Jeśli parametr jest ustawiony na false, w Twojej funkcji zwróć uwagę na wcześniejsze wyjście:

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

Wykonywanie długotrwałych zadań

Jeśli zadania nie można wykonać w maksymalnym czasie trwania Cloud Functions, podziel je na podzadania i wykonuj je po kolei, umieszczając zadania w kolejce za pomocą metody TaskQueue.enqueue() z pakietu Admin SDK.

Załóżmy na przykład, że chcesz uzupełnić dane Cloud Firestore. Możesz podzielić kolekcję dokumentów na fragmenty za pomocą kursorów zapytania. Po przetworzeniu fragmentu przejdź do przesunięcia początkowego i umieść w kolejce inne wywołanie funkcji, jak pokazano poniżej:

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

Dodaj funkcję do extension.yaml w sposób opisany w poprzedniej sekcji.

Stan raportowania

Gdy wszystkie funkcje przetwarzania zakończą się pomyślnie lub w przypadku błędu, zgłoś stan zadania za pomocą metod środowiska wykonawczego rozszerzenia Admin SDK. Użytkownicy mogą sprawdzić ten stan na stronie z informacjami o rozszerzeniu w konsoli Firebase.

Udane ukończenie i błędy niekrytyczne

Aby zgłosić błędy niekrytyczne (błędy, które nie sprawiły, że rozszerzenie przestało działać), użyj metody rozszerzenia setProcessingState() w czasie działania pakietu Admin SDK:

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

// ...

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

Możesz ustawić te stany:

Stany niekrytyczne
PROCESSING_COMPLETE

Służy do raportowania udanego ukończenia zadania. Przykład:

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

Służy do raportowania częściowego powodzenia. Przykład:

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

Umożliwia zgłaszanie błędów, które uniemożliwiają wykonanie zadania, ale nie pozostawiają rozszerzenia bezużytecznego. Przykład:

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

Aby zgłosić błędy, które pozostają bezużyteczne, wywołaj setFatalError().

NONE

Użyj tej opcji, aby wyczyścić stan zadania. Opcjonalnie możesz użyć tej opcji, aby usunąć z konsoli komunikat o stanie (na przykład po upływie pewnego czasu od ustawienia PROCESSING_COMPLETE). Przykład:

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

Błędy krytyczne

Jeśli wystąpi błąd, który uniemożliwia działanie rozszerzenia (np. nie uda się wykonać wymaganego zadania konfiguracji), zgłoś błąd krytyczny za pomocą polecenia setFatalError():

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

// ...

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

Dostrajanie kolejki zadań

Jeśli właściwość taskQueueTrigger ma wartość {}, po zainstalowaniu instancji rozszerzenia rozszerzenie udostępni kolejkę Cloud Tasks z ustawieniami domyślnymi. Możesz też dostroić limity równoczesności kolejki zadań i ponawiać próby, podając określone wartości:

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

Szczegółowe informacje o tych parametrach znajdziesz w artykule Konfigurowanie kolejek Cloud Tasks w dokumentacji Google Cloud.

Nie próbuj określać parametrów kolejki zadań przez przekazywanie ich do usługi taskQueue(). Te ustawienia są ignorowane na rzecz konfiguracji extension.yaml oraz konfiguracji domyślnej.

Na przykład to nie zadziała:

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

Jedynym sposobem na skonfigurowanie kolejek zadań rozszerzenia jest właściwość taskQueueTrigger w narzędziu extension.yaml.

Przykłady

Oficjalne rozszerzenia storage-resize-images, firestore-bigquery-export i firestore-translate-text używają modułów obsługi zdarzeń cyklu życia do uzupełniania danych.