שלום GraphQL

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

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

בפוסט זה נדבר על הבעיות של REST, על GraphQL ונראה דוגמאות קוד ראשונות לעבודה עם GraphQL.

1. למה GraphQL

אז מה הפריע לך ב REST אתם שואלים? אני אשמח לענות בדוגמה ובשביל הסיפור ניקח את גיטהאב. לגיטהאב יש API מבוסס REST דרכו אנחנו יכולים לאסוף מידע, למשל הפניה ל:

/repos/{owner}/{repo}/issues

מחזירה את רשימת כל ה Issues של ריפוזיטורי מסוים, ופניה ל:

/repos/{owner}/{repo}/pulls

מחזירה את כל ה Pull Requests של ריפוזיטורי מסוים.

וכמובן בנתיב:

/repos/{owner}/{repo}/subscribers

נמצא רשימה של כל האנשים שעוקבים אחרי מאגר מסוים.

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

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

2. איך נראית בקשת GraphQL

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

באתר https://developer.github.com/v4/explorer/ תוכלו למצוא כלי של Github שנקרא GraphQL Explorer באמצעותו אנחנו יכולים לשלוח בקשות GraphQL ולקבל תשובות אונליין מתוך הדפדפן ל API של גיטהאב.

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

query { 
  viewer {
    login
  }
}

והתשובה שאני קיבלתי מה API:

{
  "data": {
    "viewer": {
      "login": "ynonp"
    }
  }
}

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

query { 
  viewer {
    repositories(first:10, orderBy: {field:STARGAZERS, direction: DESC}) {
      nodes {
        name,
        stargazers {
          totalCount
        }
      }
    }
  }
}

והתשובה נראית כך:

{
  "data": {
    "viewer": {
      "repositories": {
        "nodes": [
          {
            "name": "qt-webchannel-demo",
            "stargazers": {
              "totalCount": 8
            }
          },
          {
            "name": "perl-design-patterns",
            "stargazers": {
              "totalCount": 8
            }
          },
          {
            "name": "mobileweb-examples",
            "stargazers": {
              "totalCount": 7
            }
          },
          {
            "name": "mojo-demos",
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "ctstarter.pl",
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "JavaScript-Addressbook-Example",
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "Pong",
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "qt-widgets-template",
            "stargazers": {
              "totalCount": 5
            }
          },
          {
            "name": "Adv-Perl-Examples",
            "stargazers": {
              "totalCount": 4
            }
          },
          {
            "name": "qt-listview-variable-height-items",
            "stargazers": {
              "totalCount": 3
            }
          }
        ]
      }
    }
  }
}

אבל אולי הדבר הכי מלהיב עם GraphQL זו היכולת שלנו לשלב מידע מכמה נקודות קצה בשאילתה אחת. לדוגמה השאילתה הבאה מוסיפה למידע שקיבלנו קודם גם את מספר ה Issues הפתוחים על כל מאגר:

query { 
  viewer {
    repositories(first:10, orderBy: {field:STARGAZERS, direction: DESC}) {
      nodes {
        name,
        issues {
          totalCount
        }
        stargazers {
          totalCount
        }
      }
    }
  }
}

ובמקרה שלי התוצאה נראית כך:

{
  "data": {
    "viewer": {
      "repositories": {
        "nodes": [
          {
            "name": "qt-webchannel-demo",
            "issues": {
              "totalCount": 1
            },
            "stargazers": {
              "totalCount": 8
            }
          },
          {
            "name": "perl-design-patterns",
            "issues": {
              "totalCount": 0
            },
            "stargazers": {
              "totalCount": 8
            }
          },
          {
            "name": "mobileweb-examples",
            "issues": {
              "totalCount": 5
            },
            "stargazers": {
              "totalCount": 7
            }
          },
          {
            "name": "mojo-demos",
            "issues": {
              "totalCount": 2
            },
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "ctstarter.pl",
            "issues": {
              "totalCount": 0
            },
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "JavaScript-Addressbook-Example",
            "issues": {
              "totalCount": 0
            },
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "Pong",
            "issues": {
              "totalCount": 1
            },
            "stargazers": {
              "totalCount": 6
            }
          },
          {
            "name": "qt-widgets-template",
            "issues": {
              "totalCount": 1
            },
            "stargazers": {
              "totalCount": 5
            }
          },
          {
            "name": "Adv-Perl-Examples",
            "issues": {
              "totalCount": 0
            },
            "stargazers": {
              "totalCount": 4
            }
          },
          {
            "name": "qt-listview-variable-height-items",
            "issues": {
              "totalCount": 0
            },
            "stargazers": {
              "totalCount": 3
            }
          }
        ]
      }
    }
  }
}

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

3. חיבור נאיבי של GraphQL ל JavaScript

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

בשביל לנסות את התוכנית תצטרכו להיכנס לגיטהאב שלכם, ללחוץ על תמונת הפרופיל הקטנה, להיכנס למסך ההגדרות ושם לחפש את הטאב Developer Settings. בתוכו תלחצו על Personal Access Tokens וצרו לכם אסימון אישי. את האסימון תרשמו בתור ערך למשתנה REACT_APP_GITHUB_AUTH_TOKEN ושימרו את הקוד בקובץ בשם fetchGraphQL.js:

// your-app-name/src/fetchGraphQL.js
async function fetchGraphQL(text, variables) {
  const REACT_APP_GITHUB_AUTH_TOKEN =
    "YOUR-GITHUB-TOKEN-GOES-HERE";
  // Fetch data from GitHub's GraphQL API:
  const response = await fetch("https://api.github.com/graphql", {
    method: "POST",
    headers: {
      Authorization: `bearer ${REACT_APP_GITHUB_AUTH_TOKEN}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      query: text,
      variables
    })
  });

  // Get the response as JSON
  return await response.json();
}

export default fetchGraphQL;

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

import fetchGraphQL from "./fetchGraphQL";

fetchGraphQL(`
query RepositoryNameQuery {
  user(login: "ynonp") {
    starredRepositories {
      totalCount
    }
  }
}
`)
  .then((response) => {
    const data = response.data;
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

4. מה הלאה

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

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

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