יום שבת, 11 בפברואר 2012

הו-נתונים! - (AtomPub) חלק 2

זהו פוסט המשך לפוסט ההקדמה על ODATA.

במידה ואתם תוהים כיצד אמור להראות מימוש REST - אז AtomPub הוא דוגמא טובה. הוא תקן ותיק (יחסית, אתם יודעים) שהוכיח את עצמו ורץ בהרבה מערכות, גם של סטארט-אפים קטנים וגם מערכות Enterprise גדולות. יתרה מזו, הוא זכה לכמה הרחבות שהחשובות בהן הן GDATA ו ODATA. ללמוד ולהבין את AtomPub הוא תרגיל טוב על מנת להבין את ODATA - ולשם אנחנו חותרים. GDATA דומה בהרבה ל"מקור", אם כי כמה דברים מעניינים לדבר עליהם.

AtomPub - תקן פיצוץ

מבוא לפאב האטום (AtomPub)
בתחילה היה פורמט RSS - פורמט מבוסס XML לתיאור בלוגים. RSS זכה (וזוכה) לפופולאריות רבה בקרב אתרי חדשות ובלוגים.
אולם, ל RSS יש כמה בעיות: התקן לא מפורט עד הסוף ומשאיר כמה שאלות פתוחות - דבר שיכול ליצור בעיות תאימות. למשל: התקן מציין שהתוכן של רשומה יכול להיות HTML או טקסט - אך לא מגדיר דרך לומר במה השתמשו בפועל. על הצרכן לעבור על הטקסט, לחפש תגי HTML ומהם להסיק מהו הפורמט. חובבני משהו.
יתרה מכך, מפתח הפורמט מסר את הזכויות עליו (גרסה 2.0) לאוניבסיטת הארוורד וזו לא הואילה לפתוח את התקן או לשפר אותו בצורה מורגשת. מאז התקן "נעול".

סיבות אלו הספיקו ליצור צורך הולך וגובר בתקן חלופי ובעקבותיו הוגדר פורמט ה Atom: פורמט מוגדר היטב, בעל תקינה סטנדרטית של IETF (עוד גוף תקינה שעוסק באינטרנט). התקן זכה לאימוץ מתוקשר ע"י גולגל וכחלק מ AtomPub הוא אומץ ע"י מערכות תוכנה רבות.

המודל הבסיסי של אטום הוא פשוט למדי, פיד המכיל מספר רשומות:


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

הנה דוגמא אמיתית של קובץ Atom מינימלי (הכולל רשומה אחת):



<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Example Feed</title> 
  <link href="http://example.org/"/>
  <updated>2003-12-13T18:30:02Z</updated>
  <author> 
    <name>John Doe</name>
  </author> 
  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>

  <entry>
    <title>Atom-Powered Robots Run Amok</title>
    <link href="http://example.org/2003/12/13/atom03"/>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
    <updated>2003-12-13T18:30:02Z</updated>
    <summary>Some text.</summary>
  </entry>

</feed>

אטום מחייב במינימום את השדות שיש בדוגמא זו (Author וכו). זוהי החלטה דיי הגיונית לפורמט של פיד חדשות/בלוג.
הערה קטנה: בדוגמא למעלה הרשומה כוללת שדה Summary. אטום מחייב או Summary או Content (התוכן המלא).

כאשר קובץ הפיד (סיומת .xml. או atom.) יושב על שרת ווב - כל שיש לעשות על מנת לקרוא את תוכנו (עבור Feed Reader כדוגמת Outlook או Google Reader) הוא לבצע קריאת Http get פשוטה לכתובת הפיד, לקבל את הקובץ שלמעלה ולהציג אותו בצורה יפה.

Atompub
AtomPub מוסיף מימד של עריכה לפיד, ע"פ עקרונות ה REST ומאפשר לבצע פעולות CRUD שונות:

אם ארצה להוסיף רשומה לפיד, פשוט אשלח Http Post לכתובת הפיד, המכילה XML עם הרשומה להוספה וכל השדות המתאימים.
תזכורת: פעולת POST ב REST משמעה: "שלח מידע למשאב קיים". הפיד קיים ואנחנו שולחים לו עוד Entry והוא מוסיף אותו לפיד.

להלן הפעולות המוגדרות בתקן (ניתן להרחיב):
על פיד (ליתר דיוק Collection - עוד מעט אסביר) ניתן לבצע:
       GET כדי לקרוא אותו
       PUT על מנת להוסיף רשומה.
על רשומה (ליתר דיוק Member - פריט) ניתן לבצע:
      GET (קריאת רשומה בודדת)
      PUT (עדכון רשומה)
      DELETE (מחיקת רשומה).
על Service Document ניתן לבצע:
      GET.

מדוע אנחנו מדברים על Collection ו Member ולא על פיד ורשומה? מהו ה Service Document??

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

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

Service, או בעצם ה Service Document, הוא מסמך המתאר מספר אוספים (Collection) הקשורים לעולם זהה (Service). המסמך מחלק אותם לקבוצות הנקראות Workspaces - אין להם משמעות רבה.

הנה המודל של AtomPub בצורה ויזואלית:



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

<entry>
  <title>Some Report</title>
  <link href="http://host/products/some_report.atom"/>
  <link href="http://host/products/some_report.atom“ rel="edit"/>
  ...


Rel הוא קיצור ל relationship שאומר שהלינק בשורה זו הוא לא לינק לתיאור הפריט אלא בעל משמעות אחרת (במקרה זה - הלינק לעריכת הפריט).
אליה וקוץ בה: המקרה הנפוץ הוא אפליקציה שצורכת את הפידים ומכילה בתוכה את הידע על הפעולות האפשריות. אם יש פעולות חדשות - אין קוד שישתמש ויפעיל אותן והתוצאה היא תוספת מחיר משמעותית בגודל הנתונים מכיוון שיש כמה קישורים לכל רשומה.

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

מעבר לצרכים הבסיסיים (פעולות CRUD)
AtomPub מכיל יכולות נוספות, אגע בעיקריות:

Paging
ברור שאוסף פריטים יכול להיות דיי ארוך ואנו לא תמיד נרצה לקבל (או לשלוח - בצד השרת) את כל הרשימה - לפני שברור שזקוקים לכולה. אולי היא ארוכה מאוד וכוללת אלפי פריטים? ב AtomPub יש מנגנון Paging (השם הרשמי הוא Partial Lists) שנשלט ע"י צד השרת - הוא מחליט כמה פריטים להעביר בכל Chunk. מסמך האטום שיחזור יכיל קישור עם rel של next האומר "אם אתה רוצה עוד - גש ללינק הבא":

<link rel="next" href="http://example.org/entries/2" />

למפתחי UI של מערכות סגורות הבחירה לתת לצד השרת להכתיב את גודל ה paging יכולה להראות קצת תמוהה - הרי ה UI יודע הכי טוב מה הוא רוצה. אולם, חשבו REST ועל החשיבות של דפים אחידים על מנת להשתמש ב caches (של HTTP) - יש פה פוטנציאל ממשי.
השרת יכול גם להחליט אם הוא מותיר גם ללכת אחורה (קישור Pervious) או לקפוץ להתחלת או סוף האוסף. אם דיברנו על ההבדלים בין אוסף (collection) לפיד (של פורמט Atom) אז אוסף הוא הסט השלם והפיד הוא view או page שקיבלנו בכל פעם.

Categories
ב Atom (אופס, דילגנו על זה כבר) וגם ב AtomPub יש עניין של קטגוריות. אלו בעצם תגיות (tags) על הרשומות או הפריטים באוסף. יש גם "מסמך קטגוריות" שמגדיר את כל הקטגוריות (או תגיות) הקיימות במערכת. דיי בנאלי ולא כ"כ שווה להתעכב על זה לדעתי.

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

Drafts
זוהי יכולת שמתאימה לניהול רשומות שהן עוד בכתיבה, כך שיוצגו רק לעורך/ים ולא לקוראי האתר. הפתרון מבוסס על הפרדה בין פיד לקוראים ופיד לכתובים הכולל גם את הרשומות שבכתיבה. כמו נושא ה categories זוהי יכולת המאימה מאוד לדומיין הספיציפי, אם כי ניתן להכליל אותה למקרה קצת יותר כללי (כמו פריטים שעוד בשלב היצירה).

עוד נקודה מעניינת היא שיכולת זו מעודדת לייצר פידים שונים המתארים אותם פריטים - עם שוני קטן שהוא הפריטים שבעריכה. מימושים רבים המבוססים על AtomPub נוטים להגדיר מספר מצומצם של שאילותות קבועות (פידים) עם URI קבועים אותם ניתן לתשאל. זו גישה שמתאימה ל REST ולניצולת טובה של ה caches אך היא קצת מגבילה את הלקוח ולעיתים דורשת ממנו "לבחור את פיד-הבסיס" ועליו לעשות עוד מעבר של "פילטור ידני". זו לא גישה רעה - יש פה  tradeoff. בהמשך נראה ש ODATA דוגל בגישה אחרת.

מדיה (Media Library Entries (MLE
כפי שציינו Atom הוא פורמט מבוסס XML. מה קורה אם אני מעוניין לנהל מידע שלא יכול להיות מתואר בצורת XML? כגון תמונות, מסמכים (נאמר PDF) וכו'? כמובן שאפשר לעטוף את המידע הבינרי ב XML בעזרת בלוקים של CDATA - אך אז פעולת Http Get של פיד תגרום לקריאת כל התוכן הבינרי של כל הרשומות - כמות גדולה של נתונים.
הפתרון של AtomPub הוא לייצר אוספים של רשומות מדיה (מידע בינרי) רשומות אלו נקראות Media Members.

הפיד של רשומת המדיה יכלול רשומות XML המתארות את הפריט וכוללות URI שלו. מעין metadata. אם אתם זוכרים, פורמט atom מחייב מספר שדות חובה כגון "Author". האם מעניין אותי לנהל את היוצר של התמונה? אם לא - האם נספק מסמך Atom לא תקני?

ההחלטה של AtomPub היא להחזיר את שדות אלו למיטב יכולתו של השרת, כנראה על בסיס המידע שיש לו על הפיד, וסביר שקצת יפוברקו. רשומות ה Media Library Entries הן בעצם מצביע לקובץ המדיה. הרשומות יכללו לינק אחת לעריכת ה MLE - כלומר ה metadata (בצורת "rel="edit) ו URI נפרד לעריכת קובץ ה מדיה עצמו (בצורת "rel="media-edit)



הערה: פוסט זה התארך מעל לתכנון - יש לי עוד מה לומר ואיני רוצה לקצץ. לאחר כמה ימים רצופים שנאבקתי לסיים את הפוסט הארוך ללא הצלחה החלטתי לחלק את נושא ה AtomPub לשני חלקים ולפרסם כבר עכשיו את החלק הראשון.
אני מוצא את עצמי משקיע לא מעט מאמץ בכניסה לתפקיד החדש (ישן) - כך שנותרים בי פחות כוחות בערבים. אני אשתדל לאזן ולשמר את הכוחות. לילה טוב :)

4 תגובות:

  1. תודה רבה,
    כתבה מעניינת כמו תמיד :)

    השבמחק
  2. קריאה מחכימה (זה לא איחול, זו הודעה לאחר מעשה...)

    מה דעתך לתרגם את המאמר לעברית?
    מס' מילים לדוגמה:
    filtering - סינון
    abstraction - הפשטה

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

    השבמחק
  3. הודעה או הודאה לאחר מעשה? ;-)

    ידידי המשכיל, אני משוכנע שניתן לכתוב יותר בעברית ופחות בבין-לאומית, אך אני חושש שהצלחה בעברית עלולה להיות פגיעה בבלוג. גם יידרש ממני מאמץ נוסף (בעיה) וגם אני חושש שעלול להידרש מאמץ נוסף מהקוראים: הפשטה? מנשק? (כך אומרים), גרסך (המילה העברית לפיקסל - גרגר מסך, שיהיו בריאים הח'ברה מהאקדמיה ללשון עברית).

    לכן, במקום לשמור על טוהר הממשק, אני נוטה לממשק (כלומר שפה) שנראית לי הכי-טבעית ובלתי אמצעית בין שני הצדדים. אם אני ממש טועה - אמרו לי.

    בעניין הכללת תכנים בינאריים:
    ברירת המחדל היא קישורים לקבצים. אמנם יכול להיות מעניין לבצע embedding (מילה עברית?) של קבצים קטנים בתוך הפיד - אך כנראה שזה לא use-case ("מקרה שימוש?!") נפוץ. האם הבנתי אותך נכון?

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

    השבמחק