GraphQL vs. REST

GraphQL vs. REST

לעתים קרובות, GraphQL מוצג כדרך חדשה ומהפכנית לחשוב על ממשקי API. במקום לעבוד עם נקודות קצה נוקשות המוגדרות על ידי שרת, אתה יכול לשלוח שאילתות כדי לקבל בדיוק את הנתונים שאתה מחפש בבקשה אחת. וזה נכון – GraphQL יכול להיות טרנספורמטיבי כאשר הוא מאומץ בארגון, ומאפשר לצוותי Frontend ו-backend לשתף פעולה בצורה חלקה יותר מאי פעם. אבל בפועל, שתי הטכנולוגיות הללו כוללות שליחת בקשת HTTP וקבלת תוצאה כלשהי, ול-GraphQL יש אלמנטים רבים של מודל REST מובנים.

מחכה למישהו?

גלה את הפוטנציאל שלך בעולם ההייטק!

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

אז בואו נקפוץ מיד. נזהה כמה מאפיינים של ממשק API, ולאחר מכן נדון כיצד GraphQL ו-REST מטפלים בהם.

משאבים

הרעיון המרכזי של REST הוא המשאב. כל משאב מזוהה על ידי כתובת URL, ואתה מאחזר את המשאב על ידי שליחת בקשת GET לאותה כתובת URL. סביר להניח שתקבל תגובת JSON, מכיוון שזה מה שרוב ממשקי ה-API משתמשים בימינו. אז זה נראה משהו כמו:

// GET /books/1 

{  "title": "Black Hole Blues",

  "author": { 

    "firstName": "Janna",

    "lastName": "Levin"

  }

  // … more fields here}

}

הערה: בדוגמה שלמעלה, כמה ממשקי API של REST יחזירו "מחבר" כמשאב נפרד.

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

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

type Book {

  id: ID

  title: String

  published: Date

  price: String

  author: Author

}

type Author {

  id: ID

  firstName: String

  lastName: String

  books: [Book]}

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

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

type Query {

  book(id: ID!): Book

  author(id: ID!): Author}

כעת, אנו יכולים לשלוח בקשה דומה לבקשת REST לעיל, אך הפעם עם GraphQL:

// GET /graphql?query={ book(id: "1") { title, author { firstName } } }

{

  "title": "Black Hole Blues",

  "author": {

 "firstName": "Janna",

  }}

נחמד, עכשיו אנחנו מתקדמים לאנשהו! אנו יכולים מיד לראות כמה דברים על GraphQL שהם די שונים מ-REST, למרות שניתן לבקש את שניהם דרך URL, ושניהם יכולים להחזיר את אותה צורה של תגובת JSON.

קודם כל, אנו יכולים לראות שכתובת ה-URL עם שאילתת GraphQL מציינת את המשאב שאנו מבקשים וגם באילו שדות אכפת לנו. כמו כן, במקום שמחבר השרת יחליט עבורנו שיש לכלול את משאב המחבר הקשור, הצרכן של ה-API מחליט.

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

לסיכום

כבר זיהינו כמה קווי דמיון והבדלים:

דומה: לשניהם יש רעיון של משאב, והם יכולים לציין מזהים עבור משאבים אלה.

דומה: ניתן להביא את שניהם באמצעות בקשת HTTP GET עם כתובת URL.

דומה: שניהם יכולים להחזיר נתוני JSON בבקשה.

שונה: ב-REST, נקודת הקצה שאתה קורא היא הזהות של אותו אובייקט. ב-GraphQL, הזהות נפרדת מהאופן שבו אתה מביא אותה.

שונה: ב-REST, הצורה והגודל של המשאב נקבעים על ידי השרת. ב-GraphQL, השרת מצהיר אילו משאבים זמינים, והלקוח מבקש מה הוא צריך באותו זמן.

בסדר, זה היה די בסיסי אם כבר השתמשת ב-GraphQL ו/או ב-REST. אם לא השתמשת ב-GraphQL בעבר, תוכל לשחק עם דוגמה דומה לזו שלעיל ב-Launchpad, כלי לבנייה ולחקירה של דוגמאות של GraphQL בדפדפן שלך.

URL Routs לעומת סכמת GraphQL

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

אז אחד החלקים החשובים ביותר של API הוא תיאור של מה שניתן לגשת אליו. זה מה שאתה לומד כשאתה קורא תיעוד API, ועם מערכות גרפיקה פנימית ו-REST API סכימת כמו Swagger, ניתן לבחון את המידע הזה באופן פרוגרמטי שיטות נוספות  ב קורס Full Stack.

בממשקי REST API של היום, ה-API מתואר בדרך כלל כרשימה של נקודות קצה:

GET /books/:id

GET /authors/:id

GET /books/:id/comments

POST /books/:id/comments

אז אפשר לומר שה"צורה" של ה-API היא ליניארית – יש רשימה של דברים שאתה יכול לגשת אליהם. כאשר אתה משחזר נתונים או שומר משהו, השאלה הראשונה שיש לשאול היא "לאיזה נקודת קצה עלי להתקשר"?

ב-GraphQL, כפי שסיקרנו למעלה, אינך משתמש בכתובות URL כדי לזהות מה זמין ב-API. במקום זאת, אתה משתמש בסכימת GraphQL:

type Query {

  book(id: ID!): Book

  author(id: ID!): Author

}

type Mutation {

  addComment(input: AddCommentInput): Comment}

type Book {}

type Author {}

type Comment {}

input AddCommentInput {}

יש כאן כמה קטעים מעניינים בהשוואה למסלולי REST עבור מערך נתונים דומה. ראשית, במקום לשלוח פועל HTTP אחר לאותה כתובת אתר כדי להבדיל בין קריאה לעומת כתיבה, GraphQL משתמש בסוג התחלתי שונה – Mutation vs. Query. במסמך GraphQL, אתה יכול לבחור איזה סוג פעולה אתה שולח עם מילת מפתח: 

query {}

mutation {}

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

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

לסיכום

ב-REST, מרחב הנתונים הנגישים מתואר כרשימה לינארית של נקודות קצה, וב-GraphQL זו סכמה עם קשרים.

דומה: רשימת נקודות הקצה ב- REST API דומה לרשימת השדות בסוגי שאילתה ומוטציה ב- API של GraphQL. שניהם נקודות הכניסה לנתונים.

דומה: לשניהם יש דרך להבדיל אם בקשת API נועדה לקרוא נתונים או לכתוב אותם.

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

שונה: ב-GraphQL, אין הבדל בין השדות בסוג שאילתה לבין השדות בכל סוג אחר, מלבד שרק סוג השאילתה נגיש בבסיס השאילתה. לדוגמה, אתה יכול לקבל ארגומנטים בכל שדה בשאילתה. ב-REST, אין מושג מהשורה הראשונה של כתובת אתר מקוננת.

שונה: ב-REST, אתה מציין כתיבה על ידי שינוי הפועל HTTP מ-GET למשהו אחר כמו POST. ב-GraphQL, אתה משנה מילת מפתח בשאילתה.

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

Route Handlers vs. Resolvers

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

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

בואו נסתכל על דוגמה של שלום עולם עם express, ספריית API פופולרית עבור Node:

app.get('/hello', function (req, res) {

  res.send('Hello World!')

})

כאן אתה רואה שיצרנו נקודת קצה /hello שמחזירה את המחרוזת 'Hello World!'. מדוגמה זו אנו יכולים לראות את מחזור החיים של בקשת HTTP בשרת REST API:

השרת מקבל את הבקשה ומחזיר את פועל ה-HTTP (GET במקרה זה) ואת נתיב ה-URL

ספריית ה-API מתאימה את הפועל והנתיב לפונקציה הרשומה על ידי קוד השרת

הפונקציה מופעלת פעם אחת, ומחזירה תוצאה

ספריית ה-API מסדרת את התוצאה, מוסיפה קוד תגובה וכותרות מתאימים ושולחת אותו בחזרה ללקוח

GraphQL עובד בצורה מאוד דומה, ולאותה דוגמה שלום עולם זה כמעט זהה:

const resolvers = {

  Query: {

    hello: () => {

  return 'Hello world!';  },

  },};

כפי שאתה יכול לראות, במקום לספק פונקציה עבור כתובת אתר ספציפית, אנו מספקים פונקציה התואמת לשדה מסוים בסוג, במקרה זה שדה hello בסוג Query. ב-GraphQL, פונקציה זו המיישמת שדה נקראת פותר.

כדי להגיש בקשה אנחנו צריכים Query:

query {

  hello

}

אז הנה מה שקורה כאשר השרת שלנו מקבל בקשת GraphQL:

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

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

הפונקציה נקראת והיא מחזירה תוצאה

ספריית GraphQL והשרת מצמידים את התוצאה לתגובה התואמת את צורת השאילתה

אז היית מקבל חזרה:

{ "hello": "Hello, world!" }

אבל הנה טריק אחד, אנחנו יכולים למעשה לקרוא לשדה פעמיים!

query { hello

  secondHello: hello}

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

לדוגמא : 

{

  Query: {

    author: (root, { id }) => find(authors, { id: id }), },

  Author: {

  posts: (author) => filter(posts, { authorId: author.id }),

  },

}

פתרונות אלה יוכלו למלא שאילתה כמו:

query {

  author(id: 1) {   firstName

    posts {

      title

    }}

 

פתרונות נוספים ב קורס Full Stack.

העתיד שלך בהייטק מתחיל כאן
צור איתי קשר עוד היום

דילוג לתוכן