האמת? לא ידעתי להסביר בצורה חזקה מדוע זו דוגמה שגויה.
יכולתי כמובן לחרטט, אבל הלכתי קראתי את הפוסטים – והחלטתי שאני רוצה לחדש אותם.
האם רעיונות ה REST השתנו מאז בצורה משמעותית?בכל זאת – חמש שנים…
הפוסט הזה הוא שונה – אך בעיקר בגלל התפיסה שלי על הנושא – שהשתנתה.
הניסיונות למסד את רעיונות ה REST שהזכרתי בפוסט המקורי – פרוטוקולי GData ו OData (של גוגל ומייקרוסופט, בהתאמה) – לא ממש תפסו. לניסיון הנוכחי, שזוכה לתשומת לב בימים אלו, JSON API – אני מעריך שיהיה גורל דומה: השפעה מוגבלת באזורים מסוימים בתעשייה. רעיון יפה שיהיו כאלו שישתמשו בו בהצלחה – אך הוא לא יסחוף את כלל התעשייה כמו REST.
רעיונות ה REST באמת גרמו לסחף אדיר בתעשיה, ושינו בתוך שנים ספורות את האופן בו אנו מגדירים APIs בין מערכות / תתי-מערכות.
מה כ"כ חזק ברעיונות ה REST שגרם לשינוי האדיר הזה?
מדוע פרוטוקולים "נבונים" יותר ו"מעודכנים" יותר, כמו GData, OData, או JSON API – לא זוכים לכזו תהודה?
מה סוד הקסם של REST?
– אני אקח סיכון קטן, ואומר שסוד הקסם של REST שהוא פשוט ופתוח לאלתור.
בכלל, אנשים רבים לא מבינים אותו ממש, והם קוראים לאלתור שלהם "REST". בפרוטוקולים הדוקים יותר (כמו OData) – התרגיל הזה עובד פחות טוב.
כלומר: אפשר לומר שמרבית התעשייה עובדת עם "פסדו-REST", ורק חלקים קטנים יותר עובדים עם "REST תקני" או אפילו "REST תקני חלקית".
אם נשווה את מספר המשתמשים ב "REST תקני (חלקית)" – סדרי הגודל של המשתמשים דומים כנראה למספרי השמתמשים בפרוטוקולים חלופיים, קרי JSON API, OData, GData, או Thrift (שפותח במקור ע"י פייסבוק).
בעוד הפוסט המקורי מ-2011 נועד להוות "קריאת השכמה" ולהראות כיצד ליישם "REST תקני"… בפוסט הזה אני אנקוט גישה מעט שונה. בכל זאת, מתבגרים… 🙂
מה הרעיון ב REST?
הרעיון, ביישום המקובל שלו, הוא שבמקום ש"נמציא" פרוטוקול תקשורת חדש לווב, נשתמש בפרוטוקול של הווב – הרי הוא פרוטוקול ה HTTP. פרוטוקול אמין, מוכר, ומוכח ב Scales אדירים.
בגישה זו, יש מספר לא מבוטל של יתרונות:
- ל HTTP יש המון ספריות, לכל שפת תכנות בערך, והרבה אנשים מכירים אותו לעומק. (אם אתם לא מכירים – זה זמן טוב לקורא את הפוסט בנושא!)
- אם נפעל בהתאם ל HTTP, נוכל להינות בהשקעה קטנה מצידנו מ Caching שרכיבי הרשת השונים כבר מספקים (Proxies, gateways, CDNs, וכו').
- רכיבי אבטחת רשת מקובלים (IDS או WAF) מכירים HTTP. הנה לנו פרוטוקול שכבר יש לו תמיכה בלא-מעט כלי אבטחה.
- HTTP הוא פשוט ויעיל. מתחת למכסה המנוע הוא פשוט כמה headers שנשלחים לפני ההודעה על גבי חיבור ה TCP.
- HTTP הוא stateless (ברובו הגדול), וה Scalability שלו היא מוכחת! (ה world wide web, דאא)
- HTTP קל לניטור והבנה (יש הרבה כלים + הוא מבוסס clear text).
ע"פ ההגדרות הפורמאליות, REST אינו בהכרח קשור ל HTTP. הוא מבסס עקרונות (או Constraints) של ה API של המערכת, שדומים מאוד לאלו של רשת האינטרנט:
- תקשורת שרת-לקוח
- ה API הוא Stateless
- ניתן לבצע Cache לבקשות
- ממשק אחיד, לכלל המערכת:
- מבוסס מיפוי של משאבים (כלל מנחה: שמם מורכב משמות-עצם)
- על כל המשאבים ניתן לבצע סט מוגדר של פעולות: קריאה, כתיבה, עדכון, וכו'. (דומה מאוד ל HTTP GET, HTTP POST, וכו').
- ה API מתאר את עצמו – ולקוח ה API משתמש בתיאור זה בצורה "גנרית" על מנת להשתמש ב API.
כיצד הסתירה הזו מתיישבת? נסביר בהמשך.
היסטוריה: כיצד נולד ה REST?
רעיונות ה REST הופיעו בפעם הראשונה בעבודת הדוקטורט של Roy T. Fielding, חבר בצוות שהגדיר את פרוטוקול ה HTTP וכן co-author של ה Apache Web Server.
עבודת הדוקטורט של פידלינג עסקה באפיון של רעיון שנקרא "סגנונות ארכיטקטוניים", מן דפוסים מרכזיים וחוזרים בארכיטקטורות של תוכנות שונות. בפרק מספר 5 בעבודה הוא תיאר סגנון ארכיטקטוני בשם (Representational State Transfer (REST – אימוץ העקרונות מאחורי ה world wide web, על מנת לתקשר בין שרתים.
העבודה של פידלינג כנראה נקראה ע"י מספר לא מבוטל של אנשים – הוא היה בחור מוכר בתעשייה. בכל זאת, ההשפעה שלה לא ניכרה בצורה ברורה – והיא שכבה לה איי שם במדפי האינטרנט (או ספריית האוניברסיטה) במשך עוד כמה שנים.
כמה שנים לאחר מכן, ב 2003 עד 2005 בערך – העולם נכנס לסערה של Service Oriented Architecture (בקיצור: SOA) והרעיון של Web Services (רשימת תקנים בשם *-WS).
אפשר להשוות את ההשפעה של SOA לאופנה הנוכחית של Microservices. לי, כמפתח צעיר, הרעיונות נשמעו מלהיבים וגדולים – ובחברה שעבדתי בה באותה התקופה החליטו "לא לפגר מאחור – ולאמץ SOA עד הסוף". כמובן שלי ולחברי לא היה מושג בדיוק כיצד להוציא מ SOA את המיטב – ופשוט מימשנו SOA ו Web Services.
Web-Services היו הפשטה גבוהה ועשירה/מסובכת מעל הפרימיטיביים של הווב. היה שם XMLs גדולים ומורכבים, שנעטפו בתוך XML נוסף (ה envelope) שהיה חלק מפרוטוקול התקשורת – SOAP, ותוארו ע"י פורמט שנקרא WSDL והופץ על גבי פרוטוקול בשם UDDI ואז היה ניתן להשתמש בכלי בשם DISCO על מנת למצוא אותם. וזה היה רק הבסיס.
הוגדר שם עולם חדש ושונה מאוד מהווב שאנו מכירים. הכל בשכבות הפשטה רבות, כאשר כל concerns – מבודד משאר ה concerns האחרים. ריכוז של הרבה רעיונות בהנדסת תוכנה.
קהילה הולכת וגוברת של אנשי תוכנה החלו לזעוק: "הגזמתם!", "זו הנדסת יתר", "זה לא יעיל מבחינת ביצועים", "ולא – לא נחכה עוד שנתיים שהאינטרנט יהיה כ"כ מהיא שלא יהיה אכפת"…
הקולות הללו חיפשו אלטרנטיבה להתאחד מאחוריה. משהו פשוט, מהיר לכתיבה, ויעיל מבחינת הביצועים.
ב 2004 ה W3C (ארגון התקינה של האינטרנט) הוציא מסמך שדן בארכיטקטורות אינטרנט. לקראת סוף המסמך, הופיעה פסקה קצרה המרחיבה את ההגדרה של Web Services: היא גם ציינה את REST כבסיס התיאורטי לפעולת ה Web Services, וגם ציין שהם יכולים להיות בלתי-תלויים בפרוטוקולים כמו WSDL או SOAP.
יריית הפתיחה נורתה, ושני מחנות החלו להיווצר:
- אלו התומכים ב Web Services ע"פ סט תקני ה *-WS.
- אלו התמוכים ב RESTful Web Services, כלומר: עלינו רק לתאום לכללי ה REST – וזהו Web Service בר-תוקף, בהכשרת ה W3C!
היום כמעט כבר אין ממש זכר ל *-WS, ומעט מאוד מ SOA. הקונצנזוס הגדול התעשייה הוא סביב REST – כאשר עדיין יש הרבה חוסר הבנה מסביב לעקרונות הללו.
קצת עזרה מידידנו ריצארדסון
את חוסר ההבנה ב REST, ניסה קצת להבהיר / לסדר בחור בשם לאונרד ריצרדסון.
ריצרדסון הוא המחבר של שני ספרים מוכרים (הוצאת O'Reilly):
- RESTful Web Services – 2007
- RESTful Web APIs – 2013
בהרצאה בכנס QCON-2008, ריצרדסון הציג מודל בגרות של אימוץ רעיונות ה REST:
רמה 0 – הביצה של POX (קיצור של Plain Old XML. פורמט ה XML היה בזמנו יותר פופולרי מ JSON)
רמה זו מתארת שימוש ב HTTP בכדי להעביר קבצי JSON או XML, נניח תמיד במתודת HTTP POST. זה יישום פשוט, Freestyle (שזה סוד הקסם שלו) – אך זה לא REST.
זה כמובן לא מפריע לקרוא לו REST – ויש אנשים שבאמת מתייחסים לכל פורמט סטנדרטי (נניח JSON) שעובר ל HTTP – כ REST. לא נבוא איתם בחשבון 🙂
רמה 1 – מיפוי משאבים
ברמה זו אנו מקפידים על תיאור של ה URIs כך שיתארו משאבים. עבור הווב של משתמשי הקצה משאבים הם מסמכים: html, js, או css. עבור API – משאבים הם דומים יותר לאובייקטים, או חלקים בתוך האובייקטים.
בעוד שיכולנו לתאר API של מערכת לניהול כנסים כ:
- POST /create_session
- POST /set-session-title
- POST /sessions/create
- POST /sessions//title
- כאשר אין קשר היררכיי ברור בין ה resources – מה הסדר הנכון?
- למשל: הזמנת-נסיעה ונהג – מי מכיל את מי? (בהנחה שכל אחד מהם הוא אובייקט שחי בפני עצמו. הזמנת-נסיעה נוצרת בלי קשר להשמה לנהג).
- רזולוציה: האם למפות כל תכונה של אובייקט (למשל: title) לresource שיש לו URI משלו, או פשוט להסתפק ב sessions/update/ עבור כל התכונות יחדיו (בהתאם לפרמטרים שנשלחים בבקשה)?
- כיצד לתאר גרסאות שונות של ה API? כחלק מה URI או כפרמטר?
- ועוד…
- GET – על מנת לבקש את ה state של המשאב אליו מפנה ה URI.
- POST – על מנת לשלוח content ("נתונים"/"הודעה") למשאב לצורך עיבוד, עיבוד שייתכן וישנה את ה state של המשאב. לעתים הודעה זו תיצור תת-משאב (ואז נהוג להחזיר Status Code של 201 – "נוצר").
- PUT – על מנת להחליף (rebind – "רישום מחדש") של התוכן של המשאב.
- PATCH – על מנת לבצע עדכון חלקי של ה state של המשאב.
- DELETE – על מנת למחוק את המשאב.
כלל חשוב נוסף הוא שאין חובה לתמוך בכל 4 הפעולות הבסיסיות על כל משאב. ייתכן משאב שעליו ניתן לבצע רק GET ומשאב אחר שעליו אפשר לבצע רק POST. מצד שני הגדרת ה URI מחייבת שכל צומת מתאר משאב שניתן לגשת אליו. לדוגמה, אם הגדרתי משאב כ:
http://example.com/orders/2009
http://example.com/orders
http://example.com
- המכונה מכירה את כל ערכי ה rel האפשריים – ולכן יודעת לפעול על פיהם.
- המכונה לא מבינה.
מי שממשיך ומתקדם מעל רמה 3 – ראוי להיכנס להיכל התהילה של REST, כמי שיישם את מודל ה REST בצורתו הטהורה ביותר!
מודל ה REST בפועל
אם אתם מפתחים מערכת ווב טיפוסית – ברור שלא!
ההסתייגויות הן שכללים רבים של "REST טהור" עשויים להיות פשוט לא יעילים ליישום במערכת שלכם. הם יכולים לגזול זמן מיותר – ולספק מעט תועלת.
הנה כמה כללים שאנחנו נוהגים להתעלם מהם במערכת הקיימת שלנו (בידיעה):
- הפער הגדול ביותר: אנו משתמשים בפעולות POST גם לצורך קריאות שלא משנות את ה state של האובייקט. פשוט כמו GET שמכיל payload גדול יותר, אפילו אם לא מתבצע processing מורכב מאחורי הקלעים.
- זה אומר ששברנו כמה כללים של ה HTTP: חלק מקריאות ה POST הן עכשיו cacheable ו idempotent.
- מצד שני: שרשור של פרמטרים ה URL זה פחות קל, ופחות קריא. הכי מעצבן היה לגלות שוב ושוב חסימות לאורך ה URL ברכיבי הרשת השונים: פעם ב WAF, פעם nginx – ופעם ב Load Balancer. אמנם אין הגבלה בתקן ה HTTP עצמו, אבל כנראה שבעקבות הדפדפנים שנהגו להגביל את אורך ה URL – נראה שגם רכיבי הרשת אמצו, כברירת מחדל, את המסורת…
- לא כל חלק של URI הוא בר-תוקף. ייתכן וישנו URI בשם {companies/{id}/roles/{role_id/ מבלי שיש ל companies/{id}/roles/ כל תוקף. למה לנו לפתח API שאף אחד לא הולך לקרוא לו?
- אנחנו משלבים שמות של פעולות, ולא רק משאבים ב URI. אנחנו משלבים אותן בסוף, כמעט-תמיד. למשל:
areas/by_point ו areas/by_rectangle. - אם פעולה יצרה משאב חדש, אנו לא מחזירים header של location עם ה URI למשאב שנוצר. הרבה יותר פשוט להחזיר id של האובייקט החדש בתוך ה JSON של התשובה.
- ויש כנראה עוד…
כללי ה REST הופיעו בדוקטורט (של בחור מאוד חכם, שמבין HTTP יותר טוב מכולנו) בשנת 2000 – ומאז לא השתנו. רק הפרשנויות אליהם השתנו מעט עם הזמן.כמו שאני נוהג להטיף, גם כאן אני מציע: השתמשו בכל כלי בצורה שהכי מועילה לכם, ולא ע"פ כלל שרשום איפשהו.
"לשבור את הכללים" ללא כל שיטה, זה גם לא דבר נכון. סדר הוא חשוב, במיוחד ככל שהמערכת גדלה ומסתבכת. לעתים נכון לשמור על הסדר הכללי, גם כאשר הוא לא נכון מאזורים נקודתיים במערכת.
אנו כן מקפידים ב REST לשמור על הכללים הבסיסיים:
- למדל בצורה סמנטית (קרי – ברורה מפתח) ובחשיבה על resources במבנה היררכי את URIs שאנו משתמשים בהם.
- להקפיד על שימוש ב Verbs של HTTP ועל Status Codes בסמנטיקה שהוגדרה להם. אנו לא משתמשים ב Verbs ו Status Codes שאינם חלק מתקן ה HTTP (על אף שתקן ה HTTP עצמו – מותיר זאת).
- להיות "Good Citizens", ולהתאים את התנהגות ה APIs שלנו לכללי ה HTTP במידת האפשר.
שיהיה בהצלחה!
—
קישורים רלוונטיים
הבלוג של מרטין פאוולר, על Richardson Maturity Model ו REST:
http://martinfowler.com/articles/richardsonMaturityModel.html
מצגת נחמדה שמתארת כמה מהבעיות שעלולות לצוץ ביישום REST (הרשאות, גרסאות שונות של API, טיפול בשגיאות, וכו'):
http://www.slideshare.net/MasoudKalali/masoudkalalirest-new-format

