ליאור בר-און לפני 13 שנים כ- 8 דקות קריאה
RESTful Services – כיצד מיישמים בפועל? (2)
תזכורת: REST הוא סגנון ארכיטקטוני, המתואר כסט אילוצים שעל המערכת לציית להם. הוא מקדם שימוש נכון ומדוייק בפרוטוקול HTTP וה Web Standards. הוא "חי בהרמוניה עם ה Web" ולא רק "משתמש ב Web כאמצעי תעבורה". ארכיטקטורה ירוקה : )
קצת להכניס אתכם לסלנג של שועלי ה REST המשופשפים (לא הייתי מגדיר את עצמי ככזה):
- ראשי התיבות REST מייצגים Representational state transfer
- POX הוא Plain Old XML, על משקל POJO. על מנת לציין ש REST הוא XML פשוט על הנשלח על גבי [HTTP[1
- ל REST אפשר לקרוא גם (WOA (Web Oriented Architecture – על מנת לתאר את הקשר ל SOA או (ROA (Resource Oriented Architecture – על מנת לתאר את הקשר ל Resource-Based Distributed Systems.
- WS-* מוקצה לחלוטין. עדיף למלמל "השם ירחם" כל פעם שמזכירים אותם ולהזכיר מיד שהם מפרים עקרונות רבים של HTTP ו ה Web (כמו למשל – ביצוע queries לקריאה בלבד ב POST).
- שימוש רק ב URI ולא ב URL. יש הבדל קטן (URI הוא ללא ה filename), אבל מי שחי REST לרוב מקפיד לדייק.
זהו, עכשיו לא נביך את עצמנו בקרב המומחים, ואנו מוכנים לצלול לפרטים.
The REST Uniform Interface
REST מציג את החוקים הבאים:
שימוש ב URI כמתאר של resources
דוגמאות ל resources הם: instance של הזמנה, לקוח או פוסט בבלוג – המקביל ל instance של class ב OO.
כמה כללים צריכים להשמר:
- ה URL (אני אשתמש ב URI ו URL לסרוגין. סליחה) צריך לספק שקיפות על מבנה ה Resources, כלומר:
- http://painting.org/france/paris/louvre/leonardo-da-vinci/mona-lisa הוא URI טוב
- http://painting.org/painting/UDF-444LR123 הוא תיאור לא מוצלח, כי אינו חושף שום פרט.
- כל "/" מתאר בהכרח רמה היררכית של מבנה המשאבים.
- יש להשתמש במקף ("-") ולא בקו תחתון ("_") להפרדת מילים. כדרג אגב ע"פ התקן (RFC 3986) החלק הרלטיבי של ה URL מוגדר כ case sensitive.
- וכו'
מידול Resource-Based
חלק זה הוא בעל ההשפעה הגדולה ביותר על ארכיטקטורת המערכת, והוא לעיתים הסיבה מדוע מימוש REST אינו ישים במערכת קיימת ללא Refactoring מקיף.
כמו שציינתי בפוסט הקודם, בעזרת ה URI אני ניגש ל Resource ישירות ומבצע עליו פעולה. ה Resource אינו מגדיר מתודות (כמו service), אלא אני יכול לבצע עליו רק את פעולות ה HTTP הסטדרטיות:
- GET = קריאת ה resource. מקביל לקריאות פונקצניונליות כגון getOrderDetails אולי getOwner או findBid.
- PUT = במקור מתואר: פעולת rebind של resource. הוספת resource חדש או עדכון resource קיים.
- POST = שליחת מידע ("post") ל resource קיים. מקביל לקריאות פונקציונליות כגון executeOrder או updateOrder. שינוי ה state הקיים.
- DELETE = מחיקת ה resource. ביצוע פעולת Delete על משאב Subscription הוא מה שהיינו מתארים ב WS כ unsubscribe().
עודכן בעקבות תיקון של אלון.למי שמכיר HTTP, מוגדרות בו יותר מ 4 הפעולות הבסיסיות הנ"ל. לדוגמא: HEAD, TRACE, OPTIONS וכו'. הגדרת ההתנהגות שלהן היא קצת פחות ברורה ופתוחה לפרשנות של מפתח מערכת ה REST.
כלל חשוב נוסף הוא שאין חובה לתמוך בכל 4 הפעולות הבסיסיות על כל משאב. ייתכן משאב שעליו ניתן לבצע רק GET ומשאב אחר שעליו אפשר לבצע רק POST. מצד שני הגדרת ה URI מחייבת שכל צומת מתאר משאב שניתן לגשת אליו. לדוגמא, אם השתמשתי ב:
אזי גם:
צריכים להיות משאבים נגישים.
ובכן, על פניו היצמדות למודל זה נראית לא מעט טרחה! מה היתרונות שאני מקבל מהם:
- Cache: ה Scale של האינטרנט מבוסס לחלוטין על קיומם של Caches. ה Cache יכול להיות באפליקציה, שרת ה Web (למשל IIS או Apache), רכיבי הרשת או רשת ה CDN (כמו Akamai שסיפרתי עליה כאן). הכלל מאוד פשוט: קריאות GET (וגם HEAD או OPTIONS) הן cached וקריאות POST, PUT וכו' מוסיפות dirty flag על ה cache של אותו resource. אם כתבתם אפליקציות רשת רבות ואינכם זוכרים שכתבתם קוד כזה – זה מובן. המימוש נעשה ע"י שרת האינטרנט וברמות שונות של ה network devices השונים. כולם מתואמים ומצייתים לאותם חוקים. תארו לכם איזה שיפור אתם מקבלים כאשר ה router דרכו עובר משתמש באוסטרליה מספק לו את התשובה לאפליקציה שלכם מתוך cache אוסטרלי איי שם במקום להעמיס על המערכת שלכם. כל זאת, מבלי שאתם כותבים שורת קוד בודדת.
אפליקציות רבות נוהגות להשתמש ב POST תמיד (משיקולי אורך URL אפשרי, או שקר כלשהו לגבי אבטחה) וכך מאבדות את הייתרון המשמעותי הזה. מצד שני, אם הגדרתם קריאת GET שמשנה את ה State – אכלתם אותה: ה caches לא יהיו מעודכנים[2] - ציוד רשת כגון Proxies, Firewalls, Web Application Firewalls מנועי חיפוש ושירותי רשת שונים מכירים את כללי ה WEB / HTTP ופועלים לפיהם. אנו נהנה מאבטחה טובה יותר, פחות בעיות לגשת לשירותים שלנו, diagnostics משופרים, ביצועים וכו'. השיפור שיושג משימוש ב CDN למשל יהיה משמעותי יותר.
- היכולת להשתמש ב hyperlinks כשפת referencing. זה נשמע ייתרון קטן, אבל אני יכול להחזיר בתשובה לקריאה link למשאב אחר, אותו לינק הוא יציב – ניתן לשמור אותו ולהשתמש אח"כ. הוא תמיד מעודכן. זהו כלי מאוד שימושי. דוגמאות: פעולת GET על הזמנה נותנת לי links ל100 פריטים. אני יכול לסקור ולקרוא רק את הפריטים שמעניינים אותי ומתי שמתאים לי. פעולת POST שמייצרת דו"ח (או שאילתא – ברמת ה data זה בערך אותו הדבר) מחזירה לי URL שאפשר לגשת אליו כל פעם שאני רוצה לקבל את הדוח.
- נגישות / תפוצה רחבה: כל פלטפורמה, וכל שפת תכנות כמעט יכולה לגשת למערכת שלי בקלות ללא שימוש בספריות מיוחדות (מה שלא כ"כ נכון ל WS-*). ניתן לגשת בקלות מ JavaScript או Flash. ניתן אפילו להפעיל ידנית מה Browser (למי שקצת יותר טכני).
- פשטות: אחרי שנכנסים לראש REST הוא לא קשה במיוחד. קל לתחזק ולהרחיב את המערכת.
שימוש ב Hypermedia לניהול שינויי state
טוב, זהו נושא קצת יותר מורכב וקצת שנוי במחלוקת. אין מחלוקת שהוא חלק הגדרת ה REST, אבל פעמים רבות בוחרים לדלג עליו ולא להשתמש בו מכיוון שהוא מורכב יותר למימוש.
עקרון זה אומר שאחרי שביצעתי פעולות (לדוגמא קריאת GET של הזמנה), הפעולות האחרות הרלוונטיות האפשריות יהיו חלק מתשובת ה GET. למשל, הנה תשובה אפשרית לקריאת ההזמנה:
23
<link rel='edit'
ref='http://example.com/order-edit/ACDB' />
אני מקבל תיאור מפורש של סט הפעולות בעזרתן אני יכול להמשיך: edit עם לינק מתאים, ואת הפרטים של המוצר והלקוח.
היתרונות הם:
- על הלקוח להחזיק / לעקוב אחר URL יחיד (Entry Point) למערכת שלי. מכאן והלאה הוא יובל בעקבות פעולותיו.
- קוד הלקוח יכול לדעת באופן דינמי מהן הפעולות האפשריות ולאפשר אותן. השרת מצידו יכול להרחיב ולצמצם את סט הפעולות, עם הזמן, כרצונו[3].
- אינני צריך לבצע עוד rountrip בנוסח קריאת GET ל OrderDetails על מנת לקבל קישורים למוצר או הלקוח.
כפי שאמרתי, זה נושא מורכב (מורכב = סיבה טובה להזהר) – אך הרעיון מקורי ומעניין.
Self descriptive message
יש גם עניין של הודעות שמתארות את עצמן, כלומר קריאות ללא ידע מוקדם. לדעתי זה עוזר בעיקר ל visibility ו debug – עקרון שהייתי מגדיר כנכון אוניברסלית ולא רק ל REST.
טעויות נפוצות של מימושי REST
- שימוש ב POST לביצוע פעולות read (היה צריך להיות GET) או כל פעולה שאינה "create new". העניין הוזכר כבר למעלה. תתי בעיות:
- התעלמות מ Caches (הוזכר למעלה)
- נסיון להעביר XML שכולל מידע / פעולות מעורבות או שלא קיימות בסט המצומצם. לדוגמא פרמטר POST בשם operation ואז חזרה לעולם ה Services הוא טעות נפוצה מאוד של מתחילים.
- בניית URI בעזרת Query Parameters (רמז: Query param אמור לשמש ל… Query)
- שימוש לא נכון או שיכפול יכולות שקיימות כבר ב HTTP. דברים כמו:
- Status / Error Code. תזכורת: פרוטוקול HTTP מותיר להוסיף לשגיאה טקסט חופשי (= גוף ההודעה).
- Cookies
- Headers
- MIME Types
- נסיון לשמור Server Side State על כל client. בעיה אוניברסלית ל Web.
- המנעות מהוספת לינקים לתשובה: אמנם שימוש בלינקים (hypermedia) לניהול כל ה state הוא עקרון שנוי במחלוקת, אך המנעות מלינקים בכלל – נשמעת טעות. לא טוב להסתמך על קוד ב client שמתאר את מבנה ה API לשוא וגם חבל ליצור קריאות מיותרות.
- נסיון לבצע Implicit Transactions.
במוקדם או במאוחר תזדקקו ל Transactions. הדרך המומלצת לטעמי הוא לייצר resource שמתאר את ה transaction. כל דרך אחרת לעשות זאת בצורה לא מפורשת שנתקלתי בה – נגמרה בכאב.
מקווה שנהנתם.
[1] על מנת לדייק זה לא חייב להיות XML, ועקרונות ה REST יכולים להיות מיושמים גם ללא HTTP – פשוט אפשר להשתמש בהרמוניה בפרוטוקול אחר ובאותה הרוח ש REST "מתלבש" על HTTP.
[2] יצא לי להיתקל במערכת "REST" דיי גדולה ומורכבת שלא הקפידה על הכלל והיו לה בעיות עם Caches לא מעודכנים. המפתחים שלה התחכמו והוסיפו HEADER לכל קריאות ה GET שציין שאסור לשמור את הקריאה ב Cache. בעולם ה Security קוראים לזה (SDoS (Self Denial of Service
[3] ניתן להסתכל על זה כ interface דינאמי. ב Web Services הייתי קורא WSDL וה IDE היה יוצר לי stub – שזה מאוד נחמד. מצד שני, לכאורה, לא הייתי יכול להגיב לשינויי Interface ללא שינויי קוד.
Published