Обработка событий жизненного цикла расширения

Расширение может включать функции Cloud Tasks, которые активируются, когда экземпляр расширения проходит через любое из следующих событий жизненного цикла:

  • Экземпляр расширения установлен.
  • Экземпляр расширения обновлен до новой версии.
  • Изменена конфигурация экземпляра расширения.

Один из наиболее важных вариантов использования этой функции – заполнение данных. Например, предположим, что вы создаете расширение, которое генерирует предварительный просмотр миниатюр изображений, загруженных в корзину Cloud Storage. Основная работа расширения будет выполняться в функции, запускаемой событием onFinalize Cloud Storage. Однако обрабатываться будут только изображения, загруженные после установки расширения. Если вы добавите в расширение функцию, которая активируется событием жизненного цикла onInstall, то сможете генерировать уменьшенные изображения для предварительного просмотра любых существующих изображений при установке расширения.

Вот ещё несколько примеров использования триггеров событий жизненного цикла:

  • Автоматизируйте настройку после установки (создание записей в базе данных, индексирование и т. д.).
  • Если вам нужно опубликовать изменения, несовместимые с предыдущими версиями, настройте автоматический перенос данных при обновлении

Краткосрочные обработчики событий жизненного цикла

Если задача может быть полностью выполнена в течение максимальной продолжительности Cloud Functions (9 минут при использовании API первого поколения), вы можете написать обработчик событий жизненного цикла как одну функцию, которая запускается по событию onDispatch очереди задач:

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

Затем в файле extension.yaml расширения выполните следующие действия:

  1. Зарегистрируйте функцию как ресурс расширения с заданным свойством taskQueueTrigger. Если вы зададите для taskQueueTrigger пустую карту ({}), расширение создаст очередь Cloud Tasks с настройками по умолчанию. При необходимости вы можете изменить эти настройки.

    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. Зарегистрируйте функцию в качестве обработчика для одного или нескольких событий жизненного цикла:

    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
    
    

    Вы можете зарегистрировать функции для любого из следующих событий: onInstall, onUpdate и onConfigure. Все эти события необязательны.

  3. Рекомендуется. Если задача обработки не требуется для работы расширения, добавьте настраиваемый пользователем параметр, который позволит пользователям выбирать, включать ли ее.

    Например, добавьте параметр, подобный следующему:

    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
    

    А в функции, если параметр имеет значение false, выполните досрочный выход:

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

Выполнение длительных задач

Если задача не может быть выполнена в течение максимального времени Cloud Functions, разбейте ее на подзадачи и выполняйте их последовательно, добавляя задания в очередь с помощью метода TaskQueue.enqueue() Admin SDK.

Например, вы хотите дополнить данные за Cloud Firestore. Вы можете разделить коллекцию документов на фрагменты с помощью курсоров запросов. После обработки фрагмента увеличьте начальное смещение и поставьте в очередь другой вызов функции, как показано ниже.

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

Добавьте функцию в extension.yaml, как описано в предыдущем разделе.

Статус отчетов

Когда все функции обработки завершатся (успешно или с ошибкой), сообщите о статусе задачи, используя методы среды выполнения расширений Admin SDK. Пользователи могут увидеть этот статус на странице сведений о расширении в консоли Firebase.

Успешное завершение и некритические ошибки

Чтобы сообщить об успешном завершении и некритических ошибках (ошибках, которые не приводят к неработоспособности расширения), используйте метод среды выполнения расширения setProcessingState() Admin SDK:

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

// ...

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

Вы можете задать следующие состояния:

Статусы некритических ошибок
PROCESSING_COMPLETE

Используйте, чтобы сообщить об успешном выполнении задачи. Пример:

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

Используется для сообщения о частичном успехе. Пример:

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

Используйте для сообщения об ошибках, которые мешают завершить задачу, но не делают расширение непригодным для использования. Пример:

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

Чтобы сообщить об ошибках, которые делают расширение непригодным для использования, вызовите setFatalError().

NONE

Очистить статус задачи. Этот метод можно использовать, чтобы удалить сообщение о статусе из консоли (например, по прошествии некоторого времени после установки PROCESSING_COMPLETE). Пример:

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

Критические ошибки

Если возникает ошибка, которая мешает работе расширения, например не удается выполнить обязательную задачу настройки, сообщите о критической ошибке с помощью setFatalError():

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

// ...

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

Настройка очереди задач

Если вы установите для свойства taskQueueTrigger значение {}, ваше расширение будет подготавливать очередь задач Cloud Tasks с настройками по умолчанию при установке экземпляра расширения. Вы также можете настроить ограничения на параллельное выполнение и повторные попытки в очереди задач, указав определенные значения:

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

Подробную информацию об этих параметрах можно найти в статье Настройка очередей Cloud Tasks в документации Google Cloud.

Не пытайтесь задать параметры очереди задач, передавая их в taskQueue(). Эти настройки игнорируются в пользу конфигурации в extension.yaml и конфигурации по умолчанию.

Например, следующий код не будет работать:

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

Свойство taskQueueTrigger в extension.yaml – единственный способ настроить очереди задач расширения.

Примеры

Официальные расширения storage-resize-images, firestore-bigquery-export и firestore-translate-text используют обработчики событий жизненного цикла для заполнения данных.