gRPC מול REST: השוואת גישות ליצירת APIs

בטקסט של היום, אני רוצה להתבונן יותר מקרוב ב-gRPC ו-REST, שני הגישות הנפוצות ביותר ליצירת API בימינו.

I will start with a short characteristic of both tools — what they are and what they can offer. Then I will compare them according to seven categories, in my opinion, most crucial for modern-day systems.

הקטגוריות הולכות ככה:

  1. הפרוטוקולים ה-HTTP הבסיסיים
  2. פורמטי הנתונים המתמודדים
  3. גודל הנתונים
  4. יעילות העברת נתונים
  5. הגדרות
  6. קלות האימוץ
  7. תמיכה בכלים

הסיבה למה

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

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

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

מה זה REST?

REST, או Representational State Transfer, הוא כנראה הדרך הנפוצה ביותר ליצור יישום המפרסם כל סוג של API. הוא משתמש ב-HTTP כאמצעי תקשורת הבסיסי. בגלל זה, הוא יכול להפיק תועלת מכל היתרונות של HTTP, כמו מטמון.

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

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

  • תקשורת לקוח-שרת
  • תקשורת חסרת מצב
  • מטמון
  • ממשק אחיד
  • מערכת ממורכזת
  • קוד על דרישה

המושגים המהותיים של REST הם:

  1. נקודות קצה: URL ייחודי (Uniform Resource Locator) המייצג משהו מסוים; ניתן להסתכל עליו כדרך לגשת לפעולה או לאלמנט מידע מסוים באינטרנט
  2. משאב: פיסת מידע מסוימת הזמינה תחת URL ספציפי

כמו כן, קיים תיאור הנקרא מודל הבגרות של ריצ'ארדסון – מודל המתאר את רמת ה"מקצועיות" ב-REST APIs. הוא מחלק את REST APIs ל-3 רמות (או 4, תלוי אם סופרים את רמה 0) בהתאם לקבוצת התכונות ש-API מסוים מכיל.

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

  • דוגמה: DELETE user/1 במקום GET user/deleteById/1

בנוגע לשיטות HTTP ולפעולות המשוייכות להן, זה נראה כך:

  • GET – שליפת משאב ספציפי או אוסף של משאבים
  • POST – יצירת משאב חדש
  • PUT – עדכון מלא של משאב
  • PATCH – עדכון חלקי של משאב ספציפי
  • DELETE – הסרת משאב ספציפי לפי זהות

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

A full description of the maturity model is out of the scope of this blog — you can read more about it here.

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

מהו gRPC?

זו עוד התפתחות של הרעיון הישן יחסית של קריאת פעולות רחוקות. אנשים מגוגל בנו אותו – זו הסיבה לכך שיש בשמו "g". זה כנראה הכלי המודרני והיעיל ביותר לעבודה עם RPCs וגם פרויקט טרנס'פורמציה CNCF

gRRC משתמש ב-Protocol Buffers של גוגל כפורמט סריאליזציה תוך שימוש ב-HTTP/2 כאמצעי תחבורת נתונים, אם כי gRPC יכול גם לעבוד עם JSON כשכבה נתונים.

המרכיבים הבסיסיים של gRPC כוללים:

  • שיטה: המרכיב הבסיסי של gRPC, כל שיטה היא קריאת פעולה רחוקה הפועלת על קלט מסוים ומחזירה פלט. היא מבצעת פעולה בודדת שמיושמת בשפת התכנות הנבחרת. כיום, gRPC תומך ב-4 סוגים של שיטות:
  1. יחיד: מודל בקשה-תגובה קלאסי שבו השיטה מקבלת קלט ומחזירה פלט
  2. זרימת שרת: שיטות שמקבלות הודעה כקלט ומחזירות זרם של הודעות כפלט. gRPC מבטיח סדר של הודעות בתוך קריאת RPC בודדת.
  3. זרימת לקוח: השיטה מקבלת זרם של הודעות כקלט, מעבדות אותם עד שלא נשארו הודעות ואז מחזירה הודעה בודדת כפלט. כמו שצוין לעיל, gRPC מבטיח סדר של הודעות בתוך קריאת RPC בודדת.
  4. שידור דו-כיווני: השיטה מקבלת את הסטרים כקלט ומחזירה את הסטרים כפלט, משתמשת בכך בשני סטרים לקריאה וכתיבה. שני הסטרים פועלים באופן עצמאי ושמירה על סדר הודעות נשמר ברמת הסטר.
  • שירות: מייצג קבוצה של שיטות — כל שיטה חייבת להיות בשם ייחודי בתוך השירות. שירותים מתארים גם תכונות כמו אבטחה, זמני ריקון, או ניסיונות חוזרים.
  • הודעה: אובייקט המייצג את הקלט או הפלט של השיטות.

הגרפיקה של API של gRPC מתוארת בצורה של קבצים .proto שמכילים את שלושת האבני הבניין הבסיסיות מהנ"ל. בנוסף, gRPC מספק מהדורת מחשב פרוטוקול בודהר שמייצרת קוד לקליאנטים ושירותים מהקבצים .proto שלנו.

אנו יכולים ליישם שיטות בצד השרת כפי שנרצה. אנו חייבים לדבק בחוזה קלט-פלט של ה-API.

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

ההשוואה

פרוטוקול HTTP הבסיסי

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

באופן כללי, REST מבוסס על בקשה-תגובה ומשתמש ב-HTTP/1.1 כתווך ההעברה. אנו חייבים להשתמש בפרוטוקול אחר כמו WebSocket (עוד עליהם כאן) או כל סוג של זרם או קשר ממושך יותר.

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

כמובן, ניתן להשתמש ב-HTTP/2 לבניית APIs דמויי REST; עם זאת, לא כל ה servesrs והספריות עדיין תומכות ב-HTTP/2. לכן, עלולות להתעורר בעיות במקומות אחרים.

gRPC, לעומת זאת, משתמש רק ב-HTTP/2. זה מאפשר לשלוח מספר זוגות בקשה-תגובה דרך חיבור TCP יחיד. גישה זו יכולה להיות עליה בביצועים ניכרת עבור היישום שלנו.

  • תוצאה: זכות קלה ל-gRPC

פורמטי נתונים נתמכים

בהנחה שבמקרה הברירת מחדל כש-API של REST משתמש ב-HTTP/1.1, אזי זה יכול לתמוך בהרבה פורמטים.

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

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

עם זאת, כשמשתמשים ב-REST עם HTTP/2, אז רק פורמטים בינאריים של שיתוף נתונים נתמכים. במקרה זה, ניתן להשתמש ב-Protobuf או Avro. כמובן, גישה זו עשויה להיות בעלת חסרונות, אך על כך יותר בנקודות הבאות.

בינתיים, gRPC תומך רק בשני פורמטים לשיתוף נתונים:

  1. Protobuf — ברירת מחדל
  2. JSON – כשאתה צריך לשלב עם API ישן

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

  • תוצאה: ניצחון ל-REST, שכן הוא תומך ביותר פורמטים.

גודל נתונים

כברירת מחדל, gRPC משתמש בפורמט החברתי של חילוף נתונים, מה שמקטין באופן משמעותי את גודל ההודעות המועברות ברשת: מחקר מדווח על ירידה של בערך 40-50% בגודל בבתים – חוויתי מאחד הפרויקטים הקודמים אפילו פחות של 50-70%.

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

האובייקטים מהמאמר פשוטים באופן סביר. עם זאת, הכלל הכללי הוא – ככל שיש יותר אובייקטים מוטמעים ומבנה מורכב יותר של JSON, כך יהיה כבד יותר מאשר Protobuf. הבדל של 50% בגודל לטובת Protobuf הוא קו בסיס טוב.

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

  • תוצאה: במקרה הברירת מחדל, ניצחון ל-gRPC; במקרה של שניהם המשתמשים בפורמט נתונים בינארי, תיקו.

קיבולת

שוב, במקרה של REST, הכל תלוי בפרוטוקול HTTP הבסיסי ובשרת.

במקרה הברירת מחדל, REST המבוסס על HTTP/1.1, אפילו השרת הכי מושבע לא יוכל לנצח את ביצועי gRPC, במיוחד כשמוסיפים את העומס של הסריקה והסריקה ההפוכה כשמשתמשים ב-JSON. אם כי כשעוברים ל-HTTP/2, נראה שההבדל קטן.

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

  • תוצאה: במקרה הברירת מחדל, gRPC; במקרה של שניהם המשתמשים בנתונים בינאריים וב-HTTP/2, תיקו או ניצחון קל ל-gRPC.

הגדרות

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

ברוב יישומי REST, אנו פשוט מכריזים על בקשותינו ותשובותינו כמחלקות, אובייקטים, או מה שלשפה מסוימת תומכת בו. ואז אנו מסתמכים על ספריות מוצעות להמרת JSON/XML/YAML, או בכל פורמט שנצטרך.

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

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

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

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

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

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

  • תוצאה: אין מנצח כאן, רק תיאור וטיפ ממני: תבחר בגישה שתיתאם לך ביותר.

קלות אימוץ

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

בכלליות, כל שפת תכנות (Java, Scala, Python) שנתקלתי בה בקרובה הקצר שלי כמהנדס תוכנה כוללת לפחות 3 ספריות/ממשקים עיקריים ליצירת יישומים דמויי REST, שלא לדבר על מספר דומה של ספריות לניתוח JSON לאובייקטים/מחלקות.

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

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

ב-Scala, יש לנו אפילו כלי בשם tapir — שהנאתי להיות אחד השומרים עליו לזמן מה. Tapir מאפשר לנו להמיר את שרת ה-HTTP שלנו ולכתוב נקודות קצה שיעבדו עבור מספר שרתים.

gRPC עצמו מספק ספריית לקוח עבור יותר מ-8 שפות תכנות פופולריות. זה בדרך כלל מספיק כפי שספריות אלו מכילות כל מה שנדרש כדי ליצור ממשקי gRPC. בנוסף, אני מודע לספריות המספקות אבספקציות גבוהות יותר עבור Java (דרך Spring Boot Starter) ועבור Scala.

דבר נוסף הוא ש-REST נחשב היום לסטנדרט ברחבי העולם ולפורטל לבניית שירותים, בעוד ש-RPC ו-gRPC, במיוחד, עדיין נחשבים לפרים למרות שהם די ישנים בשלב זה.

  • תוצאה: REST, כפי שהוא מאוד נפוץ ויש לו הרבה יותר ספריות וממשקים מסביב

תמיכה בכלי העבודה

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

בדיקות אוטומטיות/בדיקות

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

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

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

  • יישום Postman תמיכה ב-gRPC
  • מכשיר HTTP של JetBrains המשמש בממשקי ה-IDE שלהם יכול גם לתמוך ב-gRPC עם קביעה מינימלית מסוימת

  • תוצאה אחת: ניצחון ל-REST; עם זאת, המצב נראה כמעט ומשתפר עבור gRPC.

מבחני ביצועים

כאן, ל-REST יש יתרונות משמעותיים, שכן כלים כמו JMeter או Gatling הופכים את בדיקת הלחץ של APIs REST למשימה פשוטה יחסית.

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

עד כה, עם זאת, היה לנו רק תוסף וספרייה לא רשמיים אחד בשם ghz. כל אלה טובים; זה פשוט לא ברמת התמיכה כמו של REST.

  • תוצאה שתיים: ניצחון ל-REST; עם זאת, המצב נראה כמעט ומשתפר עבור gRPC, שוב 😉

תיעוד

במקרה של תיעוד ה-API, הניצחון שוב ל-REST עם OpenAPI ו-Swagger שמקובלים באופן נרחב בתעשייה והם הסטנדרט למעשה. כמעט כל הספריות ל-REST יכולות לחשף תיעוד Swagger במאמץ מינימלי או פשוט משלב היי.

למרבה הצער, ל-gRPC אין דבר כזה.

עם זאת, השאלה היא אם gRPC צריך כלל כלי כזה. gRPC יותר תיאורטי מ-REST על ידי התכנון שלו, ולכן כלים תיעוד נוספים עשויים.

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

  • תוצאה שלוש: ניצחון ל-REST; עם זאת, השאלה על תיעוד gRPC נשארת פתוחה.

תוצאה כוללת: 

A significant victory for REST


סיכום

טבלת הציונים הסופית נראית ככה.


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

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

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

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

תודה שהקדשתם לי את זמנכם.

Source:
https://dzone.com/articles/grpc-vs-rest-comparison