טיפול באירועים במחזור החיים של התוסף

התוסף יכול לכלול פונקציות של 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.

לדוגמה, נניח שרוצים לבצע מילוי חוסרים (backfill) בנתוני 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 משתמשים ב-lifecycle event handlers כדי למלא נתונים חסרים.