המתנה באמצעות קריאות חוזרות (callback)

הפונקציה Callback מאפשרת להריץ תהליכי עבודה בהמתנה לשירות אחר שישלח בקשה לנקודת הקצה של ה-callback. הבקשה הזו מפעילה מחדש את תהליך העבודה.

באמצעות קריאות חוזרות (callback), אתם יכולים לציין בתהליך העבודה שאירוע מסוים התרחש, ולהמתין לאירוע הזה בלי לבצע סקר. לדוגמה, אפשר ליצור תהליך עבודה ששולח לכם התראה כשמוצר חוזר למלאי או כשפריט נשלח, או תהליך עבודה שממתין לאינטראקציה אנושית, כמו בדיקת הזמנה או אימות תרגום.

בדף הזה מוסבר איך ליצור תהליך עבודה שתומך בנקודת קצה של קריאה חוזרת (callback), וממתין לבקשות HTTP מתהליכים חיצוניים שיגיעו לנקודת הקצה הזו. אפשר גם לחכות לאירועים באמצעות קריאות חוזרות (callback) וטריגרים של Eventarc.

כדי להשתמש בקריאות חוזרות, צריך להשתמש בשתי פונקציות מובנות של ספרייה רגילה:

יצירת נקודת קצה שמקבלת בקשת קריאה חוזרת

יוצרים נקודת קצה של קריאה חוזרת שיכולה לקבל בקשות HTTP שמגיעות לנקודת הקצה הזו.

  1. פועלים לפי השלבים ליצירת תהליך עבודה חדש, או בוחרים תהליך עבודה קיים כדי לעדכן אותו, אבל לא פורסים אותו עדיין.
  2. בהגדרת תהליך העבודה, מוסיפים שלב ליצירת נקודת קצה של קריאה חוזרת:

    YAML

        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "METHOD"
            result: callback_details
        

    JSON

        [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "METHOD"
              },
              "result": "callback_details"
            }
          }
        ]
          

    מחליפים את METHOD ב-method הצפוי של HTTP, אחד מהערכים GET,‏ HEAD,‏ POST,‏ PUT,‏ DELETE,‏ OPTIONS או PATCH. ערך ברירת המחדל הוא POST.

    התוצאה היא מפה, callback_details, עם שדה url שבו מאוחסנת כתובת ה-URL של נקודת הקצה שנוצרה.

    נקודת הקצה של הקריאה החוזרת מוכנה עכשיו לקבל בקשות נכנסות עם שיטת ה-HTTP שצוינה. אפשר להשתמש בכתובת ה-URL של נקודת הקצה שנוצרה כדי להפעיל את הקריאה החוזרת מתהליך חיצוני לזרימת העבודה. לדוגמה, אפשר להעביר את כתובת ה-URL לפונקציה של Cloud Run.

  3. בהגדרת תהליך העבודה, מוסיפים שלב להמתנה לבקשת התקשרות חזרה:

    YAML

        - await_callback:
            call: events.await_callback
            args:
                callback: ${callback_details}
                timeout: TIMEOUT
            result: callback_request
        

    JSON

        [
          {
            "await_callback": {
              "call": "events.await_callback",
              "args": {
                "callback": "${callback_details}",
                "timeout": TIMEOUT
              },
              "result": "callback_request"
            }
          }
        ]
          

    מחליפים את TIMEOUT במספר השניות המקסימלי שצריך להמתין לבקשה בתהליך העבודה. ברירת המחדל היא 43,200 (12 שעות). אם הזמן חולף לפני קבלת הבקשה, מופעלת השגיאה TimeoutError.

    שימו לב שיש משך ביצוע מקסימלי. מידע נוסף זמין במאמר בנושא מגבלת הבקשות.

    המפה callback_details מהשלב הקודם, create_callback, מועברת כארגומנט.

  4. כדי לסיים ליצור או לעדכן את תהליך העבודה, צריך לפרוס אותו.

    כשמתקבלת בקשה, כל הפרטים שלה נשמרים במפה callback_request. לאחר מכן תהיה לכם גישה לבקשת ה-HTTP המלאה, כולל הכותרת, הגוף ומפת query של כל פרמטר של שאילתה. לדוגמה:

    YAML

        http_request:
          body:
          headers: {...}
          method: GET
          query: {}
          url: "/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2"
        received_time: 2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667
        type: HTTP
        

    JSON

        {
           "http_request":{
              "body":null,
              "headers":{
                 ...
              },
              "method":"GET",
              "query":{
              },
              "url":"/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2"
           },
           "received_time":"2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667",
           "type":"HTTP"
        }
          

    אם גוף ה-HTTP הוא טקסט או JSON, ‏ Workflows ינסה לפענח את הגוף. אחרת, יוחזרו בייטים גולמיים.

אישור בקשות לנקודת הקצה (endpoint) של השיחה החוזרת

כדי לשלוח בקשה לנקודת קצה של קריאה חוזרת, Google Cloud שירותים כמו Cloud Run ופונקציות של Cloud Run, וגם שירותים של צד שלישי, צריכים לקבל הרשאה לכך באמצעות הרשאות מתאימות לניהול זהויות והרשאות גישה (IAM). ההרשאות הספציפיות שנדרשות הן workflows.callbacks.send (כלולות בתפקיד Workflows Invoker).

שליחת בקשה ישירה

הדרך הכי פשוטה ליצור פרטי כניסה לטווח קצר לחשבון שירות היא לשלוח בקשה ישירה. בתהליך הזה מעורבות שתי זהויות: מבצע הקריאה וחשבון השירות שבשבילו נוצרו פרטי הכניסה. הקריאה אל תהליך העבודה הבסיסי בדף הזה היא דוגמה לבקשה ישירה. מידע נוסף זמין במאמרים שימוש ב-IAM כדי לשלוט בגישה ובקשת הרשאות ישירות.

יצירת אסימון גישה מסוג OAuth 2.0

כדי לתת לאפליקציה הרשאה לשלוח קריאה לנקודת הקצה של הקריאה החוזרת, אפשר ליצור אסימון גישה מסוג OAuth 2.0 לחשבון השירות שמשויך לתהליך העבודה. אם יש לכם את ההרשאות הנדרשות (לתפקידים Workflows Editor או Workflows Admin ו-Service Account Token Creator), אתם יכולים גם ליצור אסימון בעצמכם על ידי הרצת השיטה generateAccessToken.

אם הבקשה של generateAccessToken תאושר, גוף התגובה שיוחזר יכלול אסימון גישה מסוג OAuth 2.0 ומועד תפוגה. (כברירת מחדל, אסימוני גישה מסוג OAuth 2.0 תקפים למשך שעה אחת לכל היותר). לדוגמה:

  {
  "accessToken": "eyJ0eXAi...NiJ9",
  "expireTime": "2020-04-07T15:01:23.045123456Z"
  }
אחר כך אפשר להשתמש בקוד accessToken בקריאה ל-URL של נקודת הקצה של הקריאה החוזרת, כמו בדוגמאות הבאות:
  curl -X GET -H "Authorization: Bearer ACCESS_TOKEN_STRING" CALLBACK_URL
  curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ACCESS_TOKEN_STRING" -d '{"foo" : "bar"}' CALLBACK_URL

יצירת טוקן OAuth לפונקציית Cloud Run

אם אתם מפעילים קריאה חוזרת מפונקציית Cloud Run באמצעות אותו חשבון שירות כמו זרימת העבודה ובאותו פרויקט, אתם יכולים ליצור אסימון גישה מסוג OAuth בפונקציה עצמה. לדוגמה:

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const token = await auth.getAccessToken();
console.log("Token", token);

try {
  const resp = await fetch(url, {
      method: 'POST',
      headers: {
          'accept': 'application/json',
          'content-type': 'application/json',
          'authorization': `Bearer ${token}`
      },
      body: JSON.stringify({ approved })
  });
  console.log("Response = ", JSON.stringify(resp));

  const result = await resp.json();
  console.log("Outcome = ", JSON.stringify(result));

למידע נוסף, אפשר לעיין במדריך בנושא יצירת תהליך עבודה עם מעורבות אנושית באמצעות קריאות חוזרות.

בקשת גישה אופליין

התוקף של טוקנים לגישה פג מדי פעם, והם הופכים לפרטי כניסה לא תקינים לבקשת API קשורה. אפשר לרענן אסימון גישה בלי לבקש מהמשתמש הרשאה, אם ביקשתם גישה אופליין להיקפי ההרשאות שמשויכים לאסימון. בקשה לגישה אופליין היא דרישה לכל אפליקציה שצריכה לגשת ל-Google API כשהמשתמש לא נמצא. מידע נוסף זמין במאמר בנושא רענון של אסימון גישה (גישה במצב אופליין).

הפעלת תהליך עבודה בדיוק פעם אחת באמצעות קריאות חוזרות (callback)

הקריאות החוזרות הן אידמפוטנטיות לחלוטין, כלומר אפשר לנסות שוב קריאה חוזרת אם היא נכשלת, בלי ליצור תוצאות לא צפויות או תופעות לוואי.

אחרי שיוצרים נקודת קצה לקריאה חוזרת (callback), כתובת ה-URL מוכנה לקבל בקשות נכנסות, ובדרך כלל היא מוחזרת למתקשר לפני שהשיחה המתאימה אל await_callback מתבצעת. עם זאת, אם כתובת ה-URL של הקריאה החוזרת עדיין לא התקבלה כשמבצעים את שלב await_callback, הביצוע של תהליך העבודה נחסם עד שמתקבלת נקודת הקצה (או עד שמתרחש פסק זמן). אחרי שהבקשה מתקבלת, הביצוע של זרימת העבודה ממשיך והקריאה החוזרת מעובדת.

אחרי שמבצעים את שלב create_callback_endpoint ויוצרים נקודת קצה של קריאה חוזרת, משבצת אחת של קריאה חוזרת זמינה לתהליך העבודה. כשתתקבל בקשה להחזרת נתונים (callback), המשבצת הזו תתמלא במטען הייעודי (payload) של ה-callback עד שניתן יהיה לעבד את ה-callback. כשמבצעים את השלב await_callback, מתבצע עיבוד של פונקציית הקריאה החוזרת, והמשבצת מתרוקנת וזמינה לפונקציית קריאה חוזרת אחרת. אחרי זה אפשר להשתמש שוב בנקודת הקצה של הקריאה החוזרת ולהתקשר שוב אל await_callback.

אם הפונקציה await_callback מופעלת רק פעם אחת אבל מתקבלת קריאה חוזרת שנייה, אחד מהתרחישים הבאים מתרחש ומוחזר קוד סטטוס של HTTP מתאים:

  • קוד הסטטוס HTTP 429: Too Many Requests מציין שהקריאה החוזרת הראשונה התקבלה בהצלחה, אבל היא לא עברה עיבוד. היא ממתינה לעיבוד. הקריאה החוזרת השנייה נדחית על ידי תהליך העבודה.

  • ‫HTTP 200: Success מציין שהקריאה החוזרת הראשונה התקבלה בהצלחה והוחזרה תגובה. הקריאה החוזרת השנייה מאוחסנת, ויכול להיות שהיא לא תעובד לעולם אלא אם await_callback יופעל בפעם השנייה. אם תהליך העבודה מסתיים לפני כן, בקשת השיחה החוזרת השנייה לא מעובדת ונמחקת.

  • ‫HTTP 404: Page Not Found מציין שתהליך העבודה כבר לא פועל. יכול להיות שהקריאה החוזרת הראשונה עובדה והתהליך הסתיים, או שהתהליך נכשל. כדי לקבוע את זה, צריך לשלוח שאילתה למצב ההפעלה של תהליך העבודה.

התקשרות חזרה מקבילה

כשמבצעים שלבים במקביל, ונוצרת קריאה חוזרת (callback) על ידי השרשור הראשי (parent) והיא ממתינה בשלבי הצאצא (child), מתבצע אותו דפוס שתיארנו קודם.

בדוגמה הבאה, כשמבצעים את השלב create_callback_endpoint, נוצר משבצת אחת של callback. כל קריאה נוספת ל-await_callback פותחת משבצת חדשה להחזרת שיחה. אפשר לבצע עשר קריאות חוזרות בו-זמנית, אם כל השרשורים פועלים וממתינים לפני שליחת בקשה לקריאה חוזרת. יכול להיות שיתבצעו שיחות חוזרות נוספות, אבל הן יישמרו ולא יעברו עיבוד.

YAML

  - createCallbackInParent:
    call: events.create_callback_endpoint
    args:
      http_callback_method: "POST"
    result: callback_details
  - parallelStep:
    parallel:
        for:
            range: [1, 10]
            value: loopValue
            steps:
              - waitForCallbackInChild:
                  call: events.await_callback
                  args:
                      callback: ${callback_details}

JSON

  [
    {
      "createCallbackInParent": {
        "call": "events.create_callback_endpoint",
        "args": {
          "http_callback_method": "POST"
        },
        "result": "callback_details"
      }
    },
    {
      "parallelStep": {
        "parallel": {
          "for": {
            "range": [
              1,
              10
            ],
            "value": "loopValue",
            "steps": [
              {
                "waitForCallbackInChild": {
                  "call": "events.await_callback",
                  "args": {
                    "callback": "${callback_details}"
                  }
                }
              }
            ]
          }
        }
      }
    }
  ]

שימו לב: שיחות חוזרות מטופלות באותו סדר שבו כל שיחה מתבצעת על ידי סניף אל await_callback. עם זאת, סדר ההרצה של הענפים לא קבוע, ויכול להגיע לתוצאה באמצעות נתיבים שונים. מידע נוסף זמין במאמר בנושא שלבים מקבילים.

איך מנסים תהליך בסיסי של בקשה לחזרה טלפונית

אפשר ליצור תהליך עבודה בסיסי ואז לבדוק את הקריאה לנקודת הקצה של הקריאה החוזרת של תהליך העבודה באמצעות curl. צריכות להיות לכם ההרשאות הדרושות Workflows Editor או Workflows Admin בפרויקט שבו נמצא תהליך העבודה.

  1. יוצרים ומפעילים את תהליך העבודה הבא ואז מריצים אותו.

    YAML

        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            call: events.await_callback
            args:
                callback: ${callback_details}
                timeout: 3600
            result: callback_request
        - print_callback_request:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
        - return_callback_result:
            return: ${callback_request.http_request}
        

    JSON

        [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "call": "events.await_callback",
              "args": {
                "callback": "${callback_details}",
                "timeout": 3600
              },
              "result": "callback_request"
            }
          },
          {
            "print_callback_request": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\\"Received \\" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          },
          {
            "return_callback_result": {
              "return": "${callback_request.http_request}"
            }
          }
        ]
          

    אחרי שמריצים את תהליך העבודה, הסטטוס של ההרצה הוא ACTIVE עד שמתקבלת בקשת הקריאה החוזרת או עד שפג הזמן הקצוב לתפוגה.

  2. מאשרים את מצב ההפעלה ומאחזרים את כתובת ה-URL של הקריאה החוזרת:

    המסוף

    1. נכנסים לדף Workflows במסוף Google Cloud :

      כניסה לדף Workflows
    2. לוחצים על השם של תהליך העבודה שהפעלתם.

      מוצג מצב הביצוע של תהליך העבודה.

    3. לוחצים על הכרטיסייה יומנים.
    4. מחפשים רשומה ביומן שדומה לזו:

      Listening for callbacks on https://workflowexecutions.googleapis.com/v1/projects/...
    5. מעתיקים את כתובת ה-URL להתקשרות חזרה כדי להשתמש בה בפקודה הבאה.

    gcloud

    1. קודם כל, מאחזרים את מזהה ההרצה:
      gcloud logging read "Listening for callbacks" --freshness=DURATION
      מחליפים את DURATION בכמות זמן מתאימה כדי להגביל את רשומות היומן שמוחזרות (אם הפעלתם את תהליך העבודה כמה פעמים).

      לדוגמה, הפקודה --freshness=t10m מחזירה רשומות ביומן שלא נוצרו לפני יותר מ-10 דקות. פרטים נוספים זמינים במאמר בנושא gcloud topic datetimes.

      מוחזר מזהה ההרצה. שימו לב: כתובת ה-URL להתקשרות חזרה מוחזרת גם בשדה textPayload. מעתיקים את שני הערכים כדי להשתמש בהם בשלבים הבאים.

    2. מריצים את הפקודה הבאה:
      gcloud workflows executions describe WORKFLOW_EXECUTION_ID --workflow=WORKFLOW_NAME
      מוחזר הסטטוס של הרצת תהליך העבודה.
  3. עכשיו אפשר לקרוא לנקודת הקצה של הקריאה החוזרת באמצעות פקודת curl:
    curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" CALLBACK_URL

    הערה: בנקודת קצה של POST, צריך להשתמש בכותרת ייצוג של Content-Type. לדוגמה:

    curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" -d '{"foo" : "bar"}' CALLBACK_URL

    מחליפים את CALLBACK_URL בכתובת ה-URL שהעתקתם בשלב הקודם.

  4. במסוף Google Cloud או באמצעות Google Cloud CLI, מוודאים שהסטטוס של הפעלת זרימת העבודה הוא עכשיו SUCCEEDED.
  5. מחפשים את הרשומה ביומן עם הערך textPayload שדומה לזה:
    Received {"body":null,"headers":...

דוגמאות

בדוגמאות האלה אפשר לראות את התחביר.

טיפול בשגיאות שקשורות לפסק זמן

בדוגמה הזו, שמוסיפה על הדוגמה הקודמת, מתבצעת בדיקה של שגיאות שנובעות מזמן קצוב לתפוגה, והשגיאות נכתבות ביומן המערכת.

YAML

    main:
      steps:
        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            try:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 3600
                result: callback_request
            except:
                as: e
                steps:
                    - log_error:
                        call: sys.log
                        args:
                            severity: "ERROR"
                            text: ${"Received error " + e.message}
                        next: end
        - print_callback_result:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
    

JSON

    {
      "main": {
        "steps": [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "try": {
                "call": "events.await_callback",
                "args": {
                  "callback": "${callback_details}",
                  "timeout": 3600
                },
                "result": "callback_request"
              },
              "except": {
                "as": "e",
                "steps": [
                  {
                    "log_error": {
                      "call": "sys.log",
                      "args": {
                        "severity": "ERROR",
                        "text": "${\"Received error \" + e.message}"
                      },
                      "next": "end"
                    }
                  }
                ]
              }
            }
          },
          {
            "print_callback_result": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          }
        ]
      }
    }
      

המתנה לקריאה חוזרת (callback) בלולאת ניסיון חוזר

בדוגמה הזו משנים את הדוגמה הקודמת על ידי הטמעה של שלב של ניסיון חוזר. באמצעות פרדיקט מותאם אישית לניסיון חוזר, תהליך העבודה מתעד אזהרה כשמתרחש זמן קצוב לתפוגה, ואז מנסה שוב להמתין בנקודת הקצה של הקריאה החוזרת, עד חמש פעמים. אם מכסת הניסיונות החוזרים מוצתה לפני קבלת הקריאה החוזרת, שגיאת הזמן הקצוב לתפוגה הסופית גורמת לכך שתהליך העבודה ייכשל.

YAML

    main:
      steps:
        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            try:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 60.0
                result: callback_request
            retry:
                predicate: ${log_timeout}
                max_retries: 5
                backoff:
                    initial_delay: 1
                    max_delay: 10
                    multiplier: 2
        - print_callback_result:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
    log_timeout:
        params: [e]
        steps:
          - when_to_repeat:
              switch:
                - condition: ${"TimeoutError" in e.tags}
                  steps:
                      - log_error_and_retry:
                          call: sys.log
                          args:
                              severity: "WARNING"
                              text: "Timed out waiting for callback, retrying"
                      - exit_predicate:
                          return: true
          - otherwise:
              return: false
    

JSON

    {
      "main": {
        "steps": [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "try": {
                "call": "events.await_callback",
                "args": {
                  "callback": "${callback_details}",
                  "timeout": 60
                },
                "result": "callback_request"
              },
              "retry": {
                "predicate": "${log_timeout}",
                "max_retries": 5,
                "backoff": {
                  "initial_delay": 1,
                  "max_delay": 10,
                  "multiplier": 2
                }
              }
            }
          },
          {
            "print_callback_result": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          }
        ]
      },
      "log_timeout": {
        "params": [
          "e"
        ],
        "steps": [
          {
            "when_to_repeat": {
              "switch": [
                {
                  "condition": "${\"TimeoutError\" in e.tags}",
                  "steps": [
                    {
                      "log_error_and_retry": {
                        "call": "sys.log",
                        "args": {
                          "severity": "WARNING",
                          "text": "Timed out waiting for callback, retrying"
                        }
                      }
                    },
                    {
                      "exit_predicate": {
                        "return": true
                      }
                    }
                  ]
                }
              ]
            }
          },
          {
            "otherwise": {
              "return": false
            }
          }
        ]
      }
    }
      

המאמרים הבאים