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

התוסף יכול לכלול פונקציות 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. רישום הפונקציה כ-handler של אירוע אחד או יותר במחזור החיים:

    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.

השלמה מוצלחת ושגיאות לא קטלניות

כדי לדווח על השלמת בהצלחה ועל שגיאות לא חמורות (שגיאות שלא גורמות לתוסף למצב לא פונקציונלי), משתמשים ב-method של זמן ריצה של התוסף 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 משתמשים בטיפול באירועים במחזור החיים כדי למלא נתונים.