Design By Example

הרבה זמן אני מתחבט בשאלה: כיצד לומדים (או מלמדים) Software Design בצורה יעילה?

לא קשה ללמד סט של 20+ Design Patterns – אבל בטווח הקצר (ואולי גם הבינוני) זה יוצר בממוצע יותר נזק מתועלת – כי הדגש הופך להיות “למצוא הזדמנויות לממש Design Patterns”, ולא “לפתור בעיה עסקית”.

SOLID גם הוא לא מזיק, אך הוא עדיין סט כללים לאומדן נכונות של Design, וכלי חלקי מאוד להגיע בעזרתו ל Deisgn מוצלח. אני גם נוטה להסכים עם הביקורת שחלק מעקרונות ה SOLID נחלשו עם השנים (בניגוד לדעתו הנחרצת של הדוד בוב – טקסני עקשן, ובעל אינטרס מובהק).

ישנם סגנונות ארכיטקטוניים שאוהבים ללמד (Layered, Microservices, Event-Driven, וכו’) – שזו בטוח נקודת מבט חשובה, ויש Quality Attributes – טוב ונחמד, אך עדיין לא מדריך כיצד לעשות Design נכון.

על כל הנ”ל כתבתי בפוסטים בעבר – כולם טובים, כולם ראויים – אך לא מספיקים להגעה ל Design מוצלח, בטח לא בצורה עקבית. הדבר דומה בעיני לסופר-מתיימר: הוא יודע לזהות בדיחה, מתח, רגשות – אבל להרכיב סיפור מוצלח – זה הרבה מעבר לכלי המדידה הבסיסיים הללו. צריך איזה secret sauce, שקשה מאוד להגדיר אותו.

“המרכיב הסודי” הוא כנראה שילוב של:

  • היכולת להבחין בין עיקר וטפל, בהקשר נתון (להלן: “הבעיה העסקית”).
    • לחתור ולהבין את ההקשר של הבעיה.
  • מעשיות – לחתור למגע ולתוצאה, מבלי להתברבר / לשקוע בחלומות.
  • גמישות מחשבתית, להשתחרר מדפוסים קיימים / שעולים במהלך הדזיין – ולבחון באמת ובתמים אלטרנטיבות נוספות.
  • ניסיון.

אני חושש שללא הארבעה הללו, או לפחות שלושה מהם – קשה להגיע לתכנונים מוצלחים בעקביות ולא משנה כמה ידע תאורטי / UML / SysML / Patterns / Architecture Stytles – למדתם לעומק.

ידע מקצועי, ותאוריה של Design הם חשובים – אבל לא מספיקים. הייתי אפילו מנח ש Design מוצלח מורכב בערך שווה בשווה בין “המרכיב סודי” (4 הנקודות הנ”ל), ידע כללי בתוכנה, ותאוריה של עיצוב תוכנה.

התאוריה של עיצוב תוכנה (Patterns / Quality Attributes / Styles / Principles) בעיקר עוזרת לנו להעריך אפשרויות במהירות, ולהבין טוב יותר מה הנקודות החזקות והחלשות בכל אופציה – כדי ליצור / להמציא אפשרויות נוספות, וטובות יותר.

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

The Classical URL Shortener Question

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

  • כולנו (אני מניח) היינו לקוחות בשלב כזה או אחר של URL Shortner, אז יש לנו מושג ברור במה מדובר, מה הצורך, מה חווית השימוש, וכו’.
  • זו בעיה שקל לסגור בה “לופ ראשון” מהר. כלומר: תהליך של דזיין הוא להתחיל בפתרון נאיבי ולשכלל אותו, וכאן הפתרון הנאיבי הוא קל ביותר. ברור שלכתוב URL Shortner ב scale גבוה כך שיהיה יעיל ואמין – זו בעיה קשה.
    • הטעות הנפוצה ביותר אולי בקרב “מתכננים מתחילים” (בגילאי 20 עד 99) היא לצלול לפרטים ולאזור מסוים לפני שיש תמונה שלמה / מלאה מספיק טובה.

הנה השאלה:

“נניח שאנחנו רוצים לבנות URL Shortener בחברה שלנו, שלוקח URL ארוך, למשל https://softwarearchiblog.com/wp-admin/post.php?post=3658&action=edit ומקצר אותו ל URL קצקצר כמו https://short.com/xyz1234. איך היית מתכנן שירות כזה? בוא תתחיל/י לתאר בבקשה”

איך מתחילים להתמודד עם שאלת דזיין?

מה דעתם?

אולי הכי נכון לפתוח בבחירת סגנון ארכיטקטוני (microservices או event-driven, אולי space-based)?
אולי להיזכר בכל עקרונות ה SOLID ולראות איך לממש אותם?
אולי בעצם – פשוט להיות פרגמטי, ולחשוב על המימוש – ולבחור טכנולוגיה מתאימה, למשל Spring Boot או Vert.x?

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

אז מאיפה מתחילים? משתי נקודות המוצא הבאות, ובמקביל:

  • בירור הצרכים הפונקציונליים והאיכותיים מהתוכנה. יש הבדל דרמטי בין URL Shortener שיתחרה בזה של Bit.ly בנפח הבקשות שמטפלים בהן, לזה הפנימי של החברה – שאולי לא צריך לטפל יותר מכמה אלפי בקשות ביום. כנ”ל לגבי אבטחה, עלויות, retention וכו’.
    • אני מדגיש את המונח צרכים על פני דרישות – כי דרישות הן פרשנות של הצרכים, שחשוב לבקר ולאתגר את הדרישות, ולא לקבל אותן כאמת מוחלטת. חלק מטעויות ה Design הכואבות ביותר שראיתי היה קבלת הדרישות בלי שאלות – היכן שכמה שאלות הגיוניות ופשוטות, היו יכולות להוביל לנתיב אחר לגמרי.
  • לסגור לופ ראשון, נאיבי, כמודל ייחוס שאפשר להתחיל ולעבוד ממנו באיטרציות.
    • זה לא רק בסדר, אלא נכון יותר להתחיל במודל פשוט.
      • אם נתחיל במודל בסיסי/פשוט – יהיה לנו קל יותר להתמקד בעיקר ולא להיגרר לאופטימיזציות של דאגות משניות (טעות נפוצה), או להיצמד לטכנולוגיה או מבנה מוכר – גם כאשר הוא אינו רלוונטי.
      • אנו חותרים לפתרון פשוט – ועדיף לנסות ולהימנע מכל שיכלול שאינו נדרש. מערכות פשוטות יותר – כושלות פחות, וקל יותר לבצע בהן שינויים עמוקים.

בשאלת ראיון אגב, הרבה פעמים השאלה תהיה מאוד פתוחה לגבי הדרישות – מה שגם יבחן את סדר העבודה שלכם, וגם ישאיר מקום גדול לדיון / לראיון להתגלגל לכיוונים שונים, מה שיכול לעזור לראות את הנטיות האישיות שלכם (למשל: איזו נטייה יותר חזקה, לארגון דברים או לצלילה טכנית?)

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

התכנון הבסיסי הזה עוזר לי להבין כמה דברים בצורה יותר ברורה:

  • למשל, שמדובר ב 2 endpoints: יצירת shortURL, ופיענוח שלו.
  • שצפוי כנראה שהשירות גם יעשה redirect ל shortURL – כלומר, הוא יקבל traffic ישיר מהאינטרנט ולא יקבל רק בקשות לפענוח. כמובן שאני יכול להפריד את האחריות הזו לרכיב אחר, אבל אני יודע שזו דאגה מתקדמת יותר, שאין טעם לכלול אותה בדיון בשלב הזה… רק התחלנו. לעתים אנחנו מנסים להפגין ב Design שלנו חשיבה על כמה שיותר אפשרויות. נחמד לכתוב אותן בצד – מזיק להטמיע אותם ישר ב Design, עוד לפני שהובן שיש צורך – כי כך אנחנו מסבכים את ה Design וכנראה שלא לצורך.
  • כתבתי ג’אווה ו RDBMS – כי זו הסביבה שהכי טבעי לי לחשוב עליה.
    • תוך כדי שאני מביט בתרשים אני חושב שאם מדובר ב hyperscale אולי עדיף כשפה את Rust (ללא GC) ואולי בסיס נתונים Key-Value שיכול to scale out למספר nodes בצורה אמינה.
    • שוב: נחמד לחשוב את זה, אבל טעות גדולה (ולצערי: נפוצה) היא להתחיל להטמיע את המחשבות הללו ב Design לפני שברורים לי הצרכים. אין Design טוב בצורה אבסולוטית. Design הוא מוצלח רק יחסית לצרכים. לצרכים שונים תכנונים שונים יהיו טובים או גרועים.

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

  • שאלה הכי משמעותית ל Design כנראה היא שאלת ה sclae: בכמה URLs אני צפוי לטפל? בכמה בקשות כל אחד מה endpoint יתמודד בשעה (למשל), כמה shortURLs אצטרך לשמור? מיליונים? מיליארדים? יותר?
  • האם יש הנחות מסוימות לגבי מבנה ה URL? הם מגיעים מ domain מסוים / מאפיין מסוים? אם המערכת היא פנימית לחברה יותר סביר שיהיו הנחות כאלו – שיכולות לתת לי leverage אמיתי ב design. אם אני יוצר מתחרה ישיר ל bit.ly/tinyUrl – אז זה פחות סביר.
  • לכמה זמן אני צריך לשמור את ה shortURL? לזמן נתון (נניח 30 יום – מספק צורך נקודתי), או לעד (שירות כללי)? אני מניח שלעד משפיע ממש על מודל עסקי, כי הנזק מיצירת מיליארדי shortURLs ואז אי תמיכה בהם יום אחד בהיר – יפגע בהרבה מאוד אנשים. כאן הייתי רוצה מבנה עלויות מינימלי שיאפשר להמשיך את האופרציה של תמיכה ב shortURLs שכבר נוצרו, לזמן ארוך.

מראיינים שראיתי (אני מכיר את השאלה הזו כבר כעשור) נהגו להפוך אותו לשאלה של Hyperscale: “עליך לתמוך בעד 1,000 מיליארד URLs, עם 100 מיליון בקשות ביום.” זה לא מציאותי (או לפחות תאורטי-מדי), כי גם שמגיעים כאלו scales -מתכננים חכמים לא מתחילים בתכנון ל scale מרבי. עוברים שלב-שלב, מדרגה-מדרגה. ארגונים רציניים ישקיעו בתכנון יותר משעה, ולא יטילו את המשימה (לרוב) על העובד שרק הצטרף לחברה. ניחא.

בואו נבחר דרישות שדורשות לחשוב על Scale, אבל מבלי הצורך להתמודד עם נקודות קיצון (של scalability):

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

איטרציה שניה

מה בתכנון הבסיסי שלנו אינו מספיק-טוב לדרישות?

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

שרתים, מן הסתם נרצה יותר מאחד: 2-3 instances לפחות עבור High Availability, ואפשר לגדול עוד, אם תהיה עוד עבודה. קידוד של מיליון URLs בחודש, זה ממוצע של כ 35-30 אלף ביום או 1500 בשעה, פחות מאחד בשנייה – לא נשמע מאתגר, גם אם נניח שבשעות העומס יש פי 5 traffic מהממוצע.

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

מה הייתי עושה אם לא היה לי את הידע הזה? הייתי מתחיל לעשות load testing למערכת – ומגלה. באיחור של כמה שבועות את סדרי הגודל. עיכוב כזה הוא חיסרון גדול – אבל לא מונע ממני מגיע לשם. הרבה פעמים ידע הוא זרז (משמעותי) – אך חסרונו אינו showstopper.

כמובן שאני רוצה מספר שרתים (server instances) – גם אם שרת אחד מספיק חזק לטפל בכל הבקשות, עבור High Availability. אני מניח שהיום זה כבר common sense.

איך התכנון שלי עומד במדדים של Design? קרי SOLID/GRASP או עקרונות מסוימים? המבנה כ”כ פשוט שלא נראה לי שיש עקרונות שהוא ממש יכול לסתור. פוריסטים עשויים לטעות ששירות אחד בג’אווה שמבצע שתי פעולות: קידוד ופענוח של URL זה לא SRP – אבל אנחנו לא פוריסטים. כמטאפורה: עפרון עם מחק בקצה זה שימושי וטוב – ואני לא מרגיש צורך להפריד בין השניים “כי אלו שני כלים שונים, ואנחנו עושים בלאגן – כאוס ממש”.

עד כאן לא הרבה השתנה ב Design:

עכשיו אני צריך להעמיק שלב אחד הלאה בפרטים: כיצד יעבדו ה endpoints? מה הם יעשו?
תהליך יעיל של Design הוא כזה שאני עושה איטרציה מהירה לסגור “לוף” (כלומר: end-to-end) מסיק מסקנות / בוחן חלופות, ורק אז נכנס לעוד רמת עומק / פרטים. זהו תהליך איטרנטיבי ביסודו.

אני ארצה להתמודד כל פעם עם השאלות הכי “גדולות”, אלו שסביר יותר שיהפכו את התכנון שלי על פיו / יכשילו אותו – ולא עם השאלות הקלות (למשל: סכמה מפורטת של בסיס נתונים, איזה ספריית DI להשתמש), שגורמות לי להיכנס לפרטים פחות חשובים, לפני שהגיע הזמן.

זה רעיון שמאוד קל לי לחשוב עליו ולתאר אותו – אבל נראה שזה לא ה common sense, ולכן אני חוזר על הנקודה הזו כמה פעמים: צלילה מהירה לפרטים מוקדם מדי, התקבעות על רעיונות לא הכי פשוטים שנשמעים “יותר חכמים” (ניקח NoSQL Database, שפת סקאלה, בחירה ב multithreading model כזה או אחר) – זו הדרך הלא נכונה לעשות את הדברים. סיכוי טוב שאין לאופטימיזציות הללו יתרון ממשי, אבל הם מקבעים אותנו על פרטים מסוימים, שיוצרים מגבלות / סוגרים אפשרויות (למשל: scala דורש JVM, בסיס נתונים K/V מגביל אותנו ביכולות חיפוש או דורש מאתנו עוד רכיבים כדי לאפשר חיפוש יעיל) ומרחיקים אותנו מבחינת האופציות העקרוניות – שהיא החשובה ביותר בשלבי ה Design.

בואו נתחיל לצלול לרמה אחת עמוקה יותר של פרטים:

איך יעבוד ה endpoint של קידוד long URL? יש פה כמה אלטרנטיבות שעולות מיד:

  • ה Short URL הוא Hash על ה Long URL.
  • ה Short URL הוא GUID (מזהה אקראי / בלתי תלוי).

אני מדבר כמובן רק על ה “id” של ה shortURL, קרי: <https://short.com/<id
לא ברור לי מיד איזו אלטרנטיבה עדיפה, ואני שמח שיש לי יותר מאחת. אני אקדיש את הזמן להשוות ביניהן.

  • hash היא פונקציה “סטטיסטית” וייתכנו שני long URLs שונים שיניבו אותו hash.
    • ההסתברות תלויה באיכות ה hash function, גודל ה hash שנוצר, וכמות האיברים שאקדד – אבל בכל מקרה “התנגשות” היא בלתי נמנעת.
    • איך אפשר לטפל? זה ידרוש ממני בכל קידוד לגשת לבסיס הנתונים, לראות אם קיים ה hash הזה והאם הוא מצביע לאותו long URL, ואם לא – לספק איכשהו id אחר, עם לוגיקה שניתן לשחזר כאשר ה LongURL הזה מופיע שוב. אפשרי – אבל זה אומר קריאה מבסיס הנתונים בכל קידוד, וקצת סיבוכיות בטיפול בהתנגשויות.
  • GUID הוא גם סטטיסטי, אבל מספיק גדול שלא סביר שייווצרו איי פעם שניים כפולים. מצד שני, GUID תקני הוא באורך 32-36 תווים, מה שאומר שה URL שלי כבר לא כ”כ קצר. מזיכרוני כמשתמש נראה ש bit.ly לא מייצרים id ארוך ביותר מ 7-8 תווים.
    • mitigation אפשרי הוא להשתמש ב GUID קצר יותר, אך חלש יותר – עם הסתברות הולכת וגוברת ל”התנגשות”.
    • חיסרון נוסף הוא ש Long URLs זהים שיתקבלו לקידוד, יקבלו כל אחד GUID חדש – וכך לא יהיה שימוש חוזר ב shortURL, אלא אם נפנה לבסיס הנתונים ונחפש אם ה URL הזה כבר קיים בכל פעולת קידוד.

שוב, אגב, אני מתבסס על ידע (הבנה כיצד פונקציות hash עובדות, או GUID).
נראה ששתי האופציות שעומדות בפני הן יותר נקודות על רצף מאשר גישות שונות שמובילות ל tradeoffs שונים בעליל. איך מחליטים?
נחזור לדרישות ונבחן את האופציות דרכן: shortURL עם id של 32 תווים לא נשמע לי רצוי אם אנשים אמורים להקליד את ה URLs הללו. בתסריטים מסוימים זה עשוי להיות סביר.
מצד שני: טיפול בהתנגשויות גם נראה לי לא דבר רצוי – סיבוכיות.

אני רוצה URL קצר ככל האפשר, ומעדיף את גישת ה GUID שחוסכת לי טיפול בהתנגשויות. בכל מקרה בשתי האפשרויות ארצה לגשת לבסיס הנתונים בכל קידוד לבדוק אם ה LongURL כבר קיים: גם עבור שימוש חוזר ב shortURL וגם להתגבר על התנגשויות. ברור שלבחירה הזו יש מחיר בביצועים – אבל אני עדיין לא מתרשם שהגיע הזמן לעשות הקרבות בפשטות / פונקציונליות עבור הביצועים.

אני חושב שהדרך ליצור URLs הכי קצרים הוא בבסיס הנתונים לנהל auto-increment ואת המספר לקדד לתווים אלפא-נומריים. נניח יש ערכי ה ASCII שזה 256 תווים אפשריים, אני משתמש ב modulo על מנת לחלץ את המספר על בסיס 256. ה ids הראשונים שיתקבלו יהיו תו אחד (a, b, c) ואם הזמן ילכו ויתארכו ככל שישתמשו במערכת יותר. מאיפה זה בא לי? אינטואיציה / ניסיון, אני מניח.

הנה המצב שהגענו אליו:

endpoint 1 בעצם גורר שתי פעולות: 1.1 ו 1.2.


שינוי קטן לסכמה: הפסקנו לשמור shortUrl כי בעצם id של ה shortURL הוא ה autoinc בבסיס 256. כשאני מקבל id בבסיס 256 אני יכול להמיר אותו למספר בבסיס 10 (autoinc) בפעולה חשבונית פשוטה. חבל לשמור את זה בבסיס הנתונים. שווה לציין ש primary key קטן יותר (בבתים) – גם ישפר את ביצועי בסיס הנתונים.

כמובן שכל זה מתאפשר בעקבות שימוש בבסיס נתונים יחיד ומרכזי. אם היינו נאלצים להשתמש בבסיס נתונים מבוזר (עבור scale) – autoinc מרכזי כבר לא היה עובד והיינו נאלצים להשתמש בגישה אחרת: GUID/Hash שהייתה מניבה URLs ארוכים יותר, או אולי פשוט מקצים לכל שרת “bulk” של מספרים ייחודיים שהוא רץ איתם והוא יכול לקבל bulk נוסף – כאשר נגמרו לו המספרים המוקצים (ואז עדיין ה URL יהיה קצר כמעט ככל האפשר).

נעבור לבחון מעט יותר את ה endpoint השני.

ה endpoint השני: shortUrl => Redirect to longURL

כאן היישום נשמע דיי פשוט:

  • קבל shortUrl בקידוד ASCII והמר אותו לבסיס 10 (autoinc).
  • חפש בסיס הנתונים את ה URL המלא.
  • החזרת תשובת redirect (קרי HTTP 302) עם ה longUrl.

משהו נשמע כאן מוזר? אי אפשר להעביר את רוב תווי ה ASCII על גבי URL – זה לא תקני ודפדפנים לא יקבלו את זה (שוב: ידע). פספסנו את זה.
נחזור ונשנה גם את ה endpoint הקודם לא לקדד על בסיס 256 (ASCII) אלא על בסיס של תווים שמותרים ב URL, למשל a..zA..Z0..9 שזה 62 תווים, כנראה שיש עוד קצת מותרים ששווה להשתמש בהם וככה להגדיל את הבסיס (ולקצר עוד קצת את ה URL). שימו לב ש URL הוא case sensitive ויש הבדל בין אות גדולה לאות קטנה (ידע).

איטרציה שלישית

בגדול אני דיי מרוצה מהפתרון שהגענו אליו, הוא נראה ישים ופשוט – ובהחלט משהו שאפשר לצאת איתו לעבודה, ולהתחיל “להרגיש את השטח”. מה חסר בפתרון? כלום – אם לא צריך יותר.
בכל זאת, מה הצד החלש של ה Design הנוכחי שלנו? מה יכול “להפיל” אותו?
אני לא חושב על משהו פונקציונלי – אבל בהחלט יכול להיות ש scale עשויה להיות בעיה.

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

נצא ל production עם פתרון סופר-נאיבי, ניפול, נחקור ונבין למה נפלנו (למשל: השתמשנו בתווים שאסורים ב URL – שינוי קטן יחסית) נתקן ונצא שוב, וחוזר חלילה.

לא פעם ראיתי אנשים טועים בבעיות Design בסיסיות, ולא מוכנים לקבל פידבק על “היצירות שלהם”. יוצאים לפרודקשיין רק כדי ללמוד שם שזה לא עובד, ולהתחיל איטרציה מחדש. אני מעריך יותר מנהלים שיודעים אז לומר: “אני מאמין שהיה אפשר לעשות טוב יותר, אני רוצה שנלמד מזה” מאלו שנותנים למפתח שוב ושוב להיכשל בפרודקשיין, לעבוד על הפיצ’ר פי 4 מהנדרש – ובסוף משבחים אותו על העבודה הקשה והמסירות שהפגין. חכמת חלם.

חזרה ל Design: אמרנו שהנקודה הפוטנציאלית של ה Design נראית טיפול ב scale. איך נשפר את ה Design שלנו להיות מוכן יותר ל high scale?
גישה אחת, פחות רצינית, היא “לעבור להשתמש בכלים של scale”: למשל: Cassandra, Scylla, אולי ZooKeeper וכו’.

אם אתם לא מכירים את הכלים האלו לעומק, ואתם מתבססים על “סיפורי הצלחה” שאתם לא מבינים – עדיף לעצור כאן ולא להתקדם. קצרה היריעה מלספר על מקרים בהם אנשים השתמשו ב”כלים של scale”, אבל לא בכלים הנכונים – ובזבזו זמן רב (עד רב-מאוד) או ממש דרדרו את ה scalability של המערכת.

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

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

  • אם ה URL חוזרים על עצמם בצורה ניכרת (“blockbusters”) – אם בהפעלה (shortUrls מסוימים תופסים נתח מורגש מהשימוש) או בקידוד (המון משתמשים באים לקדד את ה URL שהוא google.com) – אזי caches בהחלט יכולים לעזור. Cache שיחסוך לנו גישה יקרה לבסיס הנתונים: אם בקריאת ה autoinc => longUrl או בחיפוש אחרי longUrl אם כבר קודד. שני caches שונים.
    • Central cache בנוסח Redis (או כלי אחר שאתם מבינים) יהיה יעיל ככל שמספר ה instances רב יותר.
      • כאשר יש Central cache יש מקום לשקול multi-layered cache כאשר יש minimal cache בזיכרון של כל שרת לגישה מהירה באמת, בלי פעולת רשת.
  • הפרדה בין פעולות שונות על מנת לבצע אופטימיזציה טובה יותר של משאבים לכל פעולה: אין צורך שאת הקידוד של longUrl ואת התרגום יעשו אותם שרתים – אפשר לפצל לשניים.
    • אפשר להוסיף לבסיס הנתונים read-replica רק לצורך התרגום (חיפוש לפי id) – וכך לאפשר לבסיס הנתונים לנהל את ה caches הפנימיים שלו בצורה יותר יעילה.
  • מעבר לבסיס נתונים יעיל יותר לצורך הפעולות הנתונות: בעצם אנחנו משתמשים בבסיס הנתונים רק לצורך key/value ויש בסיסי-נתונים שמתמחים בזה. כמה יותר יעילים הם יהיו? האם יהיה משתלם לעשות מעבר (ביצועים / עלויות תפעול / learning curve)?
  • מעבר לבסיס נתונים מבוזר – אם ורק אם בסיס נתונים יחיד מרכזי לא מצליח לעמוד ב capacity של ה URLs. נשתדל לא לשלם סתם על מה שלא צריך.
  • שיפורי performance: הבדיקה בכל endcoding אם קיים כבר longURL כזה היא התקורה הבולטת ביותר בעיני. אפשר לנסות ליישם טכניקות כגון Bloom Filter שמאפשר לייצג “חתימה מינימלית” של ה longURL בהרבה פחות מקום – מה שייכנס בקלות ל cache מקומי של השרת ואולי גם ל cache של המעבד.
    • פעם התחלתי לכתוב פוסט על מבני-נתונים הסתברותיים, אבל הסקתי שזה נושא נישתי שלא ייגע לרוב הקוראים…

סיכום

האם סיפקתי את ה Design הטוב ביותר לבעיית ה URL Shortner?

ברור שלא – כי URL Shortner הוא בעצם סט של בעיות דומות אך שונות. לכל צורך – משהו אחר ישתנה. למשל: אם הצורך הוא להחזיק URL רק 30 יום – כנראה שמבנה הביצועים ישתנה, ותיפתח לנו אפשרות “למחזר” URLs כדי ולשמור עליהם קצרים יותר לאורך זמן (?!).

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

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



לינקים רלוונטיים

האינטרנט מלא בפוסטים על URL Shortener וליוי הפתרון. הנה כמה לדוגמה:
URL Shortener

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

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

Continuous Knowledge Delivery

לכולנו כבר ברור לגמרי מדוע אנחנו רוצים למזג קוד כל הזמן (Continuous Integration), מדוע אנחנו רוצים לבדוק ולהכין קוד כל הזמן, (Continuous Delivery) או מדוע אנחנו רוצים לשחרר קוד כל הזמן (Continuous Deployment).

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

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

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

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

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

פעם עבדתי בארגון בו התלוצצנו שמי שמתקבל לעבוד בארגון, לא יוכל אחרי שנתיים או שלוש להתקבל לארגון מחדש: ראיונות העבודה הטכניים היו בהחלט קשים, אבל פילוסופיית העבודה לא “נשענה” על ידע עמוק של העובדים: נבנו תשתיות פנימיות שמגבילות מאוד את צורת העבודה: למשל תשתיות לעבודה מול בסיס הנתונים – סט abstractions מעל hibernate שלא היה ניתן לראות SQL והיה לאנשים רק דימיון (שגוי לרוב) מה באמת קורה מאחורי הקלעים, או מן שפת XML שתורגמה ל HTML+CSS, שהגבילה את יכולת העבודה ו”חסכה” מהאנשים חשיבה והבנה מה באמת מתרחש בדפדפן.

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

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

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

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

יותר מכל תוקפת אותי המחשבה שמפתחים רבים לא מתפתחים בצורה משמעותית לאורך הקריירה שלהם. לאורך השנים ראיינתי מאות מועמדים, פעמים רבות על תרגילי תכנות בסיסיים. ישנן מגוון סיבות לחוסר הצלחה בראיון מקצועי – אבל אני לא מצליח להסביר לעצמי את חוסר-ההצלחה של אחוז גבוה של המועמדים (חלקם עם טייטלים ושנות ניסיון לא מעטות) בתרגילי תכנות בסיסיים – מלבד המחשבה שיש מרכיב של ניוון / “עבודה בניוטרל” אצל חלק גדול מהעובדים. מאוד הייתי רוצה ש 80-90% יצליחו את אותם תרגילי יסוד בתכנות, בעוד בפועל בערך רק כ 50% מצליחים. יש פה פער שמדברים עליו מעט מדי. יש פה אחריות של הארגונים / התעשייה (וגם של העובדים) – איך להעביר יודע בצורה טובה ויעילה יותר – באופן מתמשך.

מה אפשר לעשות?

אני מגשש בנושא הגדול, המורכב והחשוב הזה, ולכן התחלתי בהשוואה זריזה בין גישות למידה שונות. נתחיל בנושא הקל יותר (נראה לי) – למידה של טכנולוגיות כלליות:

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

  • כנסים הם חוויה, אבל לא מקור למידה משמעותי בעיני. מאוד נהניתי מכמה כנסים – וכמה מהם היו פותחי-עיניים עבורי, אבל את הלמידה עצמה, עשיתי מאוחר יותר, מתוך ספרים ומאמרים. אם לא הייתי מוסיף את הלמידה העצמית, הייתי יודע לדקלם כמה ססמאות נוספות, עם מעט מאוד הבנה במה באמת מדובר.
  • יש משהו מאוד מלמד לתת לאנשים בתוך הארגון להרצות, הם בוודאי מתפתחים באופן אישי ממתן הרצאה – אבל בשלבי ההתפתחות הראשונים הם עלולים לספק סשנים ברמה לא מספיק טובה. אולי כדאי להתחיל מפורום של צוות/קבוצה, ורק בעקבות הצלחות שם – לזמן לאנשים לבמה רחבה? (נניח: 100 איש). כל סשן בינוני ומטה שמועבר ל 100 איש – הוא פספוס לדעתי, שעלול להפוך לנזק מצטבר (איבוד אמון בפורום, הורדת הסטנדרטים).
  • ספרים עדיין “עובדים”, אבל לפלח הולך ופוחת של האוכלוסייה. אני מאמין ש Video Tutorials הם המדיה העדיפה כיום. כמובן שלאנשים שונים, מתאימים דברים שונים – אבל אם לא נתתם למדיה הזו סיכוי מספיק – אני ממליץ (בתור אחד שקרא עשרות רבות של ספרים, אבל גם ראה עשרות רבות של Video Tutorials).
    • למרות מוטיבציה גבוהה של אנשים להיות עם ידע, הלמידה עצמה אינה קלה, ולעתים קרובות לא מתממשת (כמו שכולם רוצים להיות בכושר, אבל קשה ליישם. מסגרת יכולה מאוד לעזור). השלב הקשה ביותר הוא כנראה הקצאת זמן – וארגון שרוצה להצליח כנראה צריך לעזור לעובדים לפנות זמן משמעותי, ולהתחייב ללמידה. למשל: לקבוע ברבעון כמה ימים קבועים מראש (ולא ניתנים להזזה) לצורך הלמידה + לבקש לסכם ולהציג את סיכום הדברים מול הצוות.
  • חונך אישי הוא דיי ברירת מחדל, במיוחד כאשר מגיעים Juniors לארגון – אבל הם יקרים מכפי שנדמה: גם בכמות ההשקעה בפועל, וגם מכך שלא תמיד החונך ידע לחנוך בצורה טובה, או אפילו לספק תשובות מספיק טובות למגוון שאלות שיעלו. החניך לרוב לא יוכל לתת אינדקציה לאיכות החניכה, עד שלא צבר כמה שנים טובות בתעשייה – והפידבק יהיה בעיקר סביב זמינות ונחמדות.

בואו ננסה לעשות תרגיל דומה, על ידע פנימי / ספציפי לארגון:

כמו מקודם, הטבלה מסכמת את הרשמים שלי, והיא אינה מדויקת / לא עברה סבבים של פידבק ושיפור. שיתוף במחשבות. אנא התייחסו אליה לצורך התרשמות ודיון – ולא מקור מידע מדויק ובדוק-היטב. הנה כמה נקודות נוספות על הנ”ל:

  • תיעוד טוב יכול להיות מאוד יעיל (כנראה, למשל תיעוד של ספריות OSS) – אך מעולם לא עבדתי בארגון שהצליח לייצר תיעוד בעקביות: בכמות ובאיכות.
  • אני מאמין שתיעוד בקוד הוא מאוד שימושי, כפי שכתבתי באריכות בפוסט קוד ספרותי = סופן של ההערות בקוד. הפוסט מתאר אלו הערות בקוד הן מיותרות, ועדיף שייכחדו, ואלו הן שימושיות (בגדול: דברים שאפשר לפספס מקריאת הקוד, תמונה גדולה, עקרונות, עבר וכוונות עתיד). יותר קל ליצור תרבות של תיעוד בקוד – כי קל יותר לכתוב אותו ולהסביר את הערך שבו.
  • כשהארגון גדל מצטבר בו ידע וכללים, כגון Coding Conventions ו Best Practices. יש כאלו שמתנגדים לכל סוג של סדר / חוקים ורוצים להיות “Rock Stars” – ראיתי שוב ושוב את הנזקים המצטברים של הגישה הזו. לא צריך להגזים, וחשוב להשאיר חופש לסגנון אישי – אבל סדר ואחידות בדברים הבסיסיים (לחווייתי ארוכת השנים) יוצרים הרבה יותר תועלת מנזק. בלי תיעוד של הכללים האלו – קשה להפיץ ותחזק אותם. גדילה מסיבית של הארגון יכולה בקלות לגרום לאיבוד של לקחי-העבר, וגם הטובים שבהם. לקחים צריך לתחזק, לחדש, ולהתאים.
  • כלי שנראה מאוד שימושי ויעיל להפצה ואכיפה של קונבנציות של Coding Convention ו Best Practices פנימיים לארגון הוא כלי Static Analysis שניתן להרחיב ולקסטם לחוקים הספציפיים של הארגון. מאסתי מזמן מכלים שאוסרים על שורה להיות מערבר לאורך מסוים או מחייבים אותי להוציא constant מכל מספר שמופיע בקוד – אני מוקיע את הכללים האלו, וממליץ לבטל אותם! מצד שני, הצגתי אולי עשרים פעמים בארגון את כללי הבסיס של מבנה טבלאות שהוחלט עליון (חייב להיות primary key שהוא GUID או Autoinc, להוסיף 2 עמודות לזמני יצירה + triggers שימלאו אותם, שימוש ב UTF8 – לא משהו חריג), ושוב ושוב היו פספוסים ואנשים שלא שמעו מעולם על הכללים (שגם מתועדים היטב). פעם אחת יצרנו חוק של Static Analysis שלא מאפשר לעשות ל commit אם יש חריגות – ונראה שהידע החל לעבור, ובלי כמעט השקעה נוספת. כל כלל הוא הזדמנות ללמד, ולכן חשוב מאוד לא רק להציב את החוק אלא להסביר אותו, ולהפנות למקורות. למשל: מדוע למשל חשוב ב MySQL שיהיה Primary Key ומה העלות של Primary Key גדול (בבתים). בנקסט אנחנו משתמשים ב Detekt (לשפת קוטלין) ו Danger (לשפת TypeScript) לכתיבה של Custom Static Analysis rules.
    • התאוריה המקובלת היא ש 70% מהלמידה בפועל מתבצעת מתוך או תוך-כדי עשייה בפועל, וזה הרגע המתאים ביותר ללמוד בו – כאשר הנושא 100% רלוונטי אלי, ואני זקוק לידע בו. Code Analysis tools (עם הסברים מפורטים בצד) – מצטיינים בקליעה לרגעים החשובים הללו.
    • בלי קשר, כל סוג של למידה כדאי לקשור לצורך נוכחי, למשל: קורס SQL לקחת בעת עבודה על פיצ’ר מורכב בבסיס הנתונים, ולהתמקד בצרכים הספציפיים – ולא בעת ההצטרפות לחברה, שאולי שנתיים רצופות לאחר-מכן לא יעשה בידע שימוש משמעותי.

אחרית -דבר / סיכום

ציינתי ועסקתי בכמה טכניקות וכלים להפצת ידע. זו הרמה הטקטית.

הרמה האסטרטגית בהפיכת ארגון לארגון שבו “זורם ידע, באופן מתמשך” – דורשת עבודה… אסטרטגית. למשל: קביעת יעדים משמעותיים ואי התפשרות עליהם. למידה היא השקעה שפירותיה ניתנים רק לאחר זמן, ויש המון פיתויים לדחות אותה. כיצד הופכים למידה משמעותית ותמידית לערך בארגון שנלחמים עליו, ממש כמו Continous Delivery? הרבה ארגונים מוכנים להקריב הרבה בכדי להגיע ל Continuous Delivery הנכסף, אך האם לכל הארגונים הוא ייתן תרומה זהה או גבוהה יותר מ Continuous Knowledge Delivery?!

עוד אלמנט חשוב הוא מבנה הצוותים ותהליכי העבודה: עד כמה אנחנו יוצרים Silos (“מובלעות ידע”) וכמה אנחנו גורמים לערבוב וזרימה של ידע – בעיקר בידע פנימי לארגון.

הגדרת מומחיויות: איש QA, איש אוטומציה, Release Manager, איש Operations (בטעות נקרא: DevOps), DBA ועוד – עוזרות לפשט את הניהול בטווח הקצר, אבל יוצרות Silos של ידע (ואי-זרימת ידע) שיקשו עלינו בעתיד.

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

מימד אחר של יצירת Silos הוא ברמת הצוותים / מיקרו-שירותים: עד כמה לגיטימי לצוות אחד לשנות קוד של צוות אחר (תחת פיקוח)? ההחלטה שכל צוות נוגע רק בקוד שלו חוסכת חיכוכים, אבל עוצרת את זרימת הידע, והופכת את הארגון לפחות גמיש, שרק x אנשים יכולים לגעת בקוד מסוים – גם כאשר ידיים נוספות באזור יכולות היו לעזור.

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

  • שמתי לב לאורך השנים שרוב האנשים הבולטים והחזקים בארגונים שעבדתי בהם – עברו בין כמה צוותים ורק לעתים רחוקות “גדלו” ב silo בודד.
  • אחוז ניכר מפריצות הדרך הארכיטקטוניות מגיעות מערבוב של ידע: דיון עמוק בין צוותים, מעבר של אדם מצוות x לצוות y לא פעם מעורר דיונים מעמיקים וחשובים, ובעצם אחת מנקודות-הכח היחסיות של ארכיטקטים – היא שם מכירים מגוון חלקים במערכת, ולא רק חלק יחיד. הם “מערבבים” ידע.

טכניקה אפשרית כאן היא מעבר תדיר של אנשים בין צוותים. אולי הגדרה שלאחר שנתיים בארגון מעודדים אנשים לעבור צוות? (מצד שני, בקצב המעבר בין חברות של חלק מהאנשים – זה אומר שחלק מהאנשים לא יזכו לעונג…)

עוד מודל שדיברנו בו בכמה חברות שעבדתי בהן הוא “מודל החלפת סטודנטים”, נניח אנשים מצוותים שונים מחליפים צוות לרבעון או שניים כדי להעביר ידע. זה נשמע רעיון טוב, אבל זה לא קרה בפועל, לפחות לא ב scale (בעיקר בגלל שיקולים של Delivery: כל החלפה כזו משמע אנשים עם תפוקה נמוכה לפחות חודש-חודשיים, עד שיתרגלו לחלק המערכת שעברו אליו).

בקיצור: הרבה שאלות, קצת פחות תשובות.

אתם יותר ממוזמנים להוסיף את התובנות שלכם בנושא.

שיהיה בהצלחה!

לחשוב Developer eXperience (DX)

מדי כמה זמן יוצא לי “להמציא” מונח, שפשוט נשמע הגיוני – ואז לגלות שהוא בעצם נמצא כבר בשימוש. כך היה עם “יהלום הבדיקות” (כ contra לפירמידת הבדיקות) שפשוט כתבתי עליו בפוסט – ואז ראיתי אותו מופיע במקורות אחרים (ומכובדים). כנ”ל על DX (על משקל User eXperience, קרי UX) שהשתמשתי בו בפוסט – ופתאום נוכחתי שזה מונח שכבר בשימוש.

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

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

זה עבד! ומאז המטאפורה הזו התפרסמה כדוגמה.

“DX” שימושי כמטאפורה כדי להזכיר לנו, כאנשי תוכנה, לפתח תוכנה טוב יותר בכמה היבטים. ידענו כבר קודם שקוד טוב הוא חשוב, כפי שהעובד של דיסני ידע כנראה שמיקי-מאוס מעשן זה לא לגמרי לעניין – אבל החידוד יכול לעזור לנו, כדי להישאר מפוקסים בכתיבת קוד טוב יותר.

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

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

סקר של מקינזי בקרב “מומחים”/מנהלים בכירים מה משפיע על הפריון של מפתחים

Code-level DX

אני רוצה להתחיל בשימוש במטאפורה של DX לרמת הקוד, בכדי לעזור לנו לכתוב קוד טוב יותר.
נתחיל בדוגמה קצת קשוחה… אובייקט ה java.util.Date (שהוחלף לחלוטין בג’אווה 8)

  println(new Date(2020, 5, 35)) // Result: Mon Jul 05 00:00:00 IDT 3920
  println(new Date(120, 4, 1)) // Result: Fri May 01 00:00:00 IDT 2020

כאשר אני מאתחל את אובייקט ה Date בג’אווה ל 35 במאי 2020, אני מקבל את התאריך 5 ביולי 3920.
למה?? איך?!
אובייקט ה Date הציג Interface שהוא פשוט זוועה, במיוחד בגרסאות הראשונות שלו (ה constructor בו השתמשתי הוא deprecated):

  • את השנה מצפים שאספק מ 1900 (על משקל Java Epoch 🤯), כך כ 2020 היא 120.
  • את החודש מצפים שאספק באינדקס-0, כך ש 5 הוא יוני (כי 0 הוא ינואר… לא ברור?)
  • אם יש גלישה של ימים (35 ביוני) אז לא תיזרק שגיאה, אלא יוסיפו את הימים כבר לחודש הבא (ההפך מ Fail Fast) – וכך הגענו ל 5 ביולי.

אפשר לטעון, “זה ה API של ה Class. הכל כתוב בתיעוד, קראו את התיעוד – ותלמדו למה לצפות ואיך לעבוד עם זה.”
אני רואה את הטיעון הזה עולה מפעם לפעם, בדוגמאות פחות קיצוניות אמנם – אך אותו טיעון, וזה טיעון שגוי בעיני.

אם אתם רוצים לעשות DX טוב אתם צריכים לכתוב API/Interface שמפתחים ייהנו לעבוד אתו. לא… לא “יצליחו לעבוד” אתו – ייהנו לעבוד אתו!
השאיפה הזו להנאה עשויה להציב את הרף הנכון לרמת הקוד שאנו מבקשים, כפי ש”כל הפארק הוא במה” עזר לעובדים של דיסני להבין את רף החוויה שהם מצופים היו לתת למבקרים בפארקים של דיסני.

הנה דוגמה הרבה פחות קיצונית:

fun DataPoints.plus(other: DataPoints): DataPoints {
    val intersectKeys = this.keys.intersect(other.keys)
    val intersectEntries = (this.entries + other.entries).filterKeys { it in intersectKeys }
    return DataPoints(intersectEntries)
}

אני משתמש בפונקציה בשם plus, שבעצם מבצעת חיתוך של איברים – כלומר: אני מסיים עם פחות איברים.
יכול להיות שבהקשר של Data Points זה הגיוני (מי יודע?! – נניח שאני לא מכיר את הדומיין), אבל זה לא אינטואיטיבי לי כאדם ולשכל הישר שלי. האם אני כותב את הקוד כ Domain Expert או כאדם?

כנראה שאני קופץ בין שניהם, ואם לרגע אני מסיר את “כובע” ה Domain Expert וחושב בהיגיון פשוט של אדם – זה מבלבל. אם בזבזתי עכשיו חצי שעה לדבג קוד רק כדי להבין שהפונקציה plus מצמצת את מספר האיברים – זה כנראה יוביל לכך שלא אהנה מה interface של הפונקציה – וזה אומר נקודת כישלון של DX. המחשבה על DX אמורה לשים את כל הטיעונים של “הצודקים לכאורה” של ה Domain בצד “אבל ברור ש…”, “אבל ידוע ש…”, אם אני מתבלבל כי לרגע חשבתי כאדם ונותרתי עם חוויה לא טובה – זה לא DX טוב.

הפתרון: לקרוא לפונקציה combine שתתאים גם לתאר את הדומיין המורכב, אבל גם לא “תכשיל” חשיבה אנושית בסיסית שלי. שם כללי כמו combine יחייב אותי לבדוק, ולא להניח – מה הפונקציה עושה. בדיוק כמו שאנשי UX מכים על חטא אם כיתוב על כפתור גורם לאנשים להתבלבל ולבצע את הפעולה הלא-הנכונה, והם לא יאמרו לכם (אני מקווה) “המשתמש היה צריך לחשוב טוב יותר”, או “הוא לא מבין מספיק, מה הוא חשב שיקרה בשלב הבא?”

דוגמה אחרונה, הסתכלו על ה interface הבא:

interface NoFun {

  fun updateCustomerDataPoints(customerId: CustomerId, dataPoints: List<DataPoint>)
  
  data class DataPoint(
    val type: DataPointType, 
    val value: DataPointValue, 
    val effectiveDate: LocalDate
  )

}

במקרה הזה אנו מעדכנים את הלקוח עם DataPoints מסוימים, אבל בתסריטים מסוימים חשוב לעדכן את ה effective Date על כל ה Data Points. ברור למומחה העסקי מדוע זה נכון, אבל אני מקווה שברור לכם הקוראים – מדוע לצרכן של ה API זה לא אינטואיטיבי, וקל לשכוח את זה – שלא לומר שלעבור על רשימה של איברים שאני שולח ולשנות אותם עלול להרגיש כמו משהו לא נכון או hacky שאני כמשתמש עושה. פעולה כזו לא מתאימה ל”שימושיות גבוהה”

שינוי קטן ב API (הוספה של שדה אופציונלי של effectiveDate) יכול לשדרג את השימושיות שלו בצורה משמעותית:

fun updateCustomerDataPoints(customerId: CustomerId, dataPoints: List<DataPoint>, effectiveDate: LocalDate? = null)

המימוש של ה API יעבור על ה Data Points ויעדכן את ה effective date שלהם – זו פעולה פשוטה. אבל עצם זה שהאופציה ניתנת לי בחתימת ה API: א. עוזרת לי לזכור אותה, ב. נוסכת בי בטחון שאני עושה את הדבר הנכון – ולא איזה hack שתוצאותיו לא ברורות. נראה שחסר כאן תיעוד שמסביר כיצד ומתי בדיוק לשלוח ערך ב effective date – זה עדיין לא טריוויאלי מספיק מתוך החתימה.

החשיבה שה API אמור לשרת ולהיות הכי טבעי / קל / נוח עבור צרכן ה API – היא לא רעיון חדש (כתבתי עליו בבלוג בעבר). אבל לפעמים המחשבה ש “כל מפתח צריך להבין מה קורה במערכת” יכולה לגרור אותנו לפספס וליצור APIs פחות טובים.

הטיעון ש “אנחנו רוצים לתת API שכיף ונוח לעבוד אתו” (בלי קשר לרמת המומחיות של הצרכן) עשוי להכריע את הכף לטובת השימושיות הגבוהה / יישום POLA. אולי לא כולם יסכימו שה API הראשון מבלבל / מכשיל את המשתמש, אבל כולם יסכימו בוודאי שהוא לא “מפנק” ואפשר לעשות אותו נוח ו”כיפי” יותר לשימוש. זה מספיק. UX גבוה הוא קונספט מוכר ומוסכם, ואף אחד לא יצפה ש Gmail יבקש ממני להגדיר את ה HTTP Headers של ההודעה – רק בגלל שאני מומחה ואני יכול, נכון?

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

DX ברמת ארגון הפיתוח

DX הוא מטאפורה מצוינת למפתחים, שיעזור להם ליצור APIs, Intefaces, ובכלל קוד טוב וקריא יותר. השאלה “האם מי שישתמש בקוד שלי עומד ליהנות?” – תוביל למסקנה שונה (והרבה פעמים: טובה יותר) מאשר השאלה “האם הקוד הזה בסדר / תקין / מסודר?”

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

תלונות כמו:

  • “ה Build אטי”
  • “ה Security Tools והדרישה להכניס סיסמה כל פעם – פשוט מטרידים”
  • “לפתוח באג בג’ירה זה פשוט סיוט… למה זה כ”כ מסובך?”

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

השאלה למנהלים היא “האם כיף לפתח בארגון / מערכת שלנו – יותר מחברות מקבילות?” היא שאלה רלוונטית. פיתוח, במיוחד של מערכות מורכבות, תמיד כולל אלמנטים שוחקים ומתסכלים. תמיד יהיה את הבאג המתיש ברמת התשתית שנחקור לאורך ימים או שבועות, ונחווה שוב ושוב אכזבות שכמעט פתרנו אותו – אבל עדיין לא.

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

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

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

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

קראתי כמה מאמרים שטענו ש DX גבוה יותר (איך מודדים?) משפיע בצורה משמעותית על הצלחת הארגון (“פי 4-5”) ועל שביעות הרצון של העובדים – אבל לא מצאתי נתונים משמעותיים ששווה להציג. רק דיבורים.

אני מניח שאלו הדברים שבשלב הזה הם עניין של אמונה: כמה אתם מאמינים ששיפור חווית הפיתוח של המפתחים בארגון שלכם תשתלם בפריון ושביעות רצון גם בלי יכולת למדוד כל שיפור? או עד כמה אתם נצמדים למה שניתן למדוד ותשקיעו רק היכן שיש ROI מוכח (כנראה: בעיקר בפריון).

העיקרון הראשון בשיפור ה DX למפתחים ברמת הארגון הוא להקצות בצורה בלתי-מפשרת משאבים איכותיים ומשמעותיים לנושא. אלו לא נושאים קלים.

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

מונח מתאים מעולם ה UX הוא ה Friction Log – תיעוד אזורים בהם קשה למשתמשים להתקדם ב UI המערכת והם חווים בהם תסכול. באופן דומה כנראה שכדאי לבקש מהמפתחים לשתף ולתעד – היכן הם חווים קושי, עיכובים, ותסכול ברמת הפיתוח, ובטוח שיש הרבה כאלו.

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

כמו בארכיטקטורה של מערכת, כדי להיות יעילים הרבה פעמים שווה להשקיע בלהפוך בעיות ברמת חומרה “High” לבעיות ברמת חומרה “low” או אפילו “רק medium” ולעבור לבעיה הבאה – מאשר לנסות להעלים את הבעיה לגמרי. Build Pipeline איטי מציק אם הוא אורך 30 דקות, אבל כנראה ש 10 דקות זה בסדר. המאמץ להביא אותו ל 2 דקות – עשוי להיות אדיר, ולא משתלם.

נתקלתי בפונקציות לא טכניות של Developer Enablement – מישהו בארגון שמזמין קורסים, קונה כלים, מארגן כנסים וסקרים בקרב המפתחים בכדי לשפר את החוויה וההווי שלהם. קשה לי להאמין שזה מודל יעיל ש”יפגע” במקומות הקשים והכואבים. כנ”ל לגבי מישהו מצוות ה Operations (או “DevOps”) שאחראי על העניין. בטוח שיש להם מה לתרום, אבל עדיין לא ראיתי את האדם שאינו מפתח – ומצליח להבין עד הסוף את הכאבים של המפתחים, ולהביא פתרונות עומק למה שבאמת כואב.

אני מאמין ש DX גבוה יכול להגיע רק מתוך שיתוף פעולה פרואקטיבי עם המפתחים וגם מניהול של העניין ע”י מישהו שמגיע מעולמות הפיתוח. לא Operations ולא תפעול.

סיכום

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

האם המטאפורה העדיפה (בשאיפה) – תוביל לתוצאות טובות יותר? אתם תגידו – אבל יש לי אופטימיות שייתכן וכן.

שיהיה בהצלחה!

על המגבלות של REST – בשירות מערכות ווב בנות-זמננו

כבר כתבתי על REST בבלוג: ב 2011 (בהרבה התלהבות) וגם ב 2016 (במתינות) – מה ששיקף תהליך אישי של התלהבות רבה מ REST, והתפכחות לאורך הזמן.

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

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

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

על אלו מגבלות אנחנו מדברים? הרי REST הוא כמעט-מושלם, לא?

למי שלא זוכר / לא היה בתעשייה בשנות ה-2000 אזכיר בקצרה את ההיסטוריה:
בתחילת שנות ה-2000, כאשר התברר לתעשיית התוכנה שמערכות תוכנה הולכות ונעשות מורכבות יותר, ויותר – ולכן רבות מהן הופכות למערכות מבוזרות (יותר משרת אחד) – ומכאן צמח רעיון ה Service-Oriented-Architecture (בקיצור: SOA) לארגון של מערכות מבוזרות שכאלו. מייקרוסופט הציעה את פרוטוקול ה SOAP (לשעבר XML-RPC) כבסיס לתקשורת בסוג הארכיטקטורה הזו, מה שהתקבל במהרה ע”י יבמ, ושאר חברותיה.

החברות שהובילו אז את התעשייה (IBM, HPE, Microsoft, וכו’) קידמו את רעיונות ה-SOA לכדי מציאות, ועבדו על Suite של פרוטוקולים בשם *-WS שיפתרו את כל הבעיות התקשורת והאינטגרציה של מערכות Enterprise (מה שהיה מעניין באותה תקופה).

סט הפרוטוקולים המרכיבים את *-WS

בחבילת הפרוטוקולים של *-WS היה כמעט הכל, לקחו בחשבון כמעט כל צורך של Enterprise Software, מלבד אחד חשוב מאוד: פשטות.

לא כולם אהבו את SOAP ו *-WS, אחד מהם היה רוי פילדינג אחד ממגדירי התקן של HTTP. בעיניו – HTTP היה “הפרוטוקול של האינטרנט” ועל אף ש HTTP נועד להגשת “דפי ווב”, הוא חשב שפרוטוקול אפליקטיבי צריך להשתלב בצורה הרמונית (אולי “טבעית”) עם HTTP וה Web Standards הקיימים, ולא להתעלם ולהחליף אותם. זו הייתה מן גישה מוקדמת של “קיומיות וובית” – שהייתה חדשה ומרעננת באותה התקופה. היום קוראים לזה “Web-native”.

פילדינג פרסם באותה תקופה עבודת דוקטורט, שבה פרק מס’ 5 (המפורסם!) הציג סגנון ארכיטקטוני בשם Representational State Transfer (בקיצור: REST) למערכות ווב, ארכיטקטורה שמשתלבת באופן מאוד טבעי עם ה World Wide Web ו HTTP.

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

תוצר לוואי של “ארכיטקטורת ה REST”, כפי שהוגדרה בעבודת הדוקטורט של פילדינג, היה צורת התקשורת בין חלקי המערכת, מה שנקרא כיום RESTful APIs. כלומר: צורת תקשורת של העברת הודעות JSON (במקור: XML) על גבי HTTP תוך השתלבות הרמונית באופיו והתנהגותו של HTTP והווב. ל HTTP יש מנגנונים כגון Caches, ו resource naming (להלן ה URL) הנכונים גם לתקשורת אפליקטיבית – שאם משתמשים ב HTTP, לא צריך לפתח מחדש. העולם מלא בכלים (למשל: דפדפן, Firewalls, Postman, swagger) שעובדים כבר עם HTTP, והמבנים של HTTP (כלומר: URL, Status Codes, Methods, Headers, Cookies) – מוכרים היטב* בתעשייה, ומקלים על אימוץ הטכנולוגיה.


* האמירה ש”כולם מכירים HTTP” היא סופר-מקובלת בתעשייה, אבל פה אני צריך להשתעל קצת – ולהתוודות שהיא לא לגמרי נכונה. גם כיום, בעידן הידע, ה Geektime, ושנים לאחר ש RESTful APIs הפכו לסטנדרט הלא-מעורער של התעשייה – אני נתקל בחוסרים גדולים של מהנדסי תוכנה בהבנה בסיסית של פרוטוקול ה HTTP. נכון יותר לומר ש HTTP מתועד היטב ויש עליו אינסוף חומר ו References – ולכן הוא נגיש יותר מפרוטוקולים proprietary אחרים.

חזרה לתחילת שנות ה-2000: חברות האינטרנט שאז היו עוד צעירות: אמזון, יאהו, גוגל ועוד – אימצו בהתלהבות את REST (או ליתר דיוק: את ה RESTFul APIs) ושינו את מהלך ההיסטוריה. SOA, עם כל התכנון ההנדסי העמוק שנעשה מסביבו, ולמרות הגיבוי של חברות הענק – נקבר בעמודי ההיסטוריה, ו REST הפך לדרך התקשורת הנפוצה והמקובלת במערכות ווב בעשור וחצי האחרונים.

להיכן ממשיכים מכאן?

REST איננו תקן (אלא פרק בעבודת דוקטורט) והפרשנות ל RESTFul APIs אף יותר מזה – היא פרשנות, שכל אחד יכול לקחת למקום שונה. עם הזמן צצה הביקורת על “גנבי-הדעת” שפשוט מעבירים XML על גבי HTTP וקוראים למה שעשו “RESTful API” – מבלי באמת לכבד את פרוטוקול ה HTTP ו/או להשתלב בעקרונות הווב המקובלים (resources, openess, linking). מודל הביקורת שתפס יותר מכל להערכת “יישום RESTful APIs” הוא Richardson’s Maturity Model (בקיצור RMM):

הפסים שאתם רואים בשכבה הנמוכה הם בוץ, רפש – שדובקים בכל מי שמסתפק ב “XML/JSON over HTTP” ועוד קורא לזה REST.
גם אני, כשהייתי עוד REST Evangelist – כעסתי על כל מי שנוהג כך. מה, אין כבוד?

ע”פ מודל ה RMM, לשלוח XML או JSON על גבי HTTP הוא נקודות האפס הבסיסית ביותר (והמוערכת הכי פחות). משם אפשר להתקדם לעיצוב “נכון” של URLs, שימוש “נכון” ב HTTP Verbs, ועד הגביע הקדוש של Hypermedia transformations. השתלבות המערכת עם “טבע ה HTTP והווב”.

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

שאלה דומה היא שאלת ההשתלבות ב HTTP/Web ומידת ההצמדות לרעיון ה REST: יישום של RESTful APIs ברמה 3 לפי ה RMM אולי תעניק לכם חיים בעולם הבא בקרב “קהילת נאמני ה REST” – אך כנראה גם תתברר כעבודה הנדסית בינונית עד איומה, ברוב המקרים. למשל: רמה 3 טוענת שכל קריאה לשרת צריכה לבוא מתוך “גלישה טבעית בווב”. באתר ווב אני מתחיל מאתר הבית – ובסדרה של קישורים מגיע לדף בו אני מעוניין. באופן דומה, הטענה היא ששרתים שמדברים זה מזה – צריכים להתקדם למטרה בצעדים. לא לשאול: “כמה כסף יש בחשבון של לקוח X?” אלא תמיד יש להתחיל בשאלה “מי אתה? אלו לקוחות יש לך? איזה מידע יש לכל לקוח? האא… ורק אז לשאול: אז כמה כסף יש בחשבון של לקוח X?”.

אם יש לנו שרתים “קרובים” (אותה מערכת) שמתקשרים הרבה, ההבדל בין 4 קריאות (3 “נימוסים” + 1 תכל’ס) על כל בקשה מול קריאה בודדת (רק תכל’ס) – היא משמעותית. רק במקום עבודה אחד בחיים ראיתי יישום של Hypermedia transformations – ואולי עשינו HATEOAS, אבל סיבכנו מאוד את המערכת. ממש ברמת העוול / הטיפשות.

מכאן עולה השאלה המתבקשת: “כמה RESTFulness” כדאי לעשות? ואיך יודעים לאמוד מתי ההצמדות ל RESTfulness היא תורמת, ומתי היא כבר מזיקה?

אז מהן המגבלות העיקריות של RESTFul APIs?

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

Hypermedia Transformations / HATEOAS – ברוב המקרים, מבחינה הנדסית – זה פשוט טירוף לשמו. יש להימנע – וחבל להמשיך ולעסוק בעניין.

HTTP Verbs הם שפה מגוונת ועשירה למערכות פורומים / Wikis – אך דלה ומוגבלת עבור מערכות ווב מודרניות ומורכבות. הנה ההגדרות מתוך וויקיפדיה:

והנה כמה מגבלות יום-יומיות ,שכנראה שכולנו נתקלים בהן:

  • קריאת GET איננה כוללת Body. כאשר אנחנו רוצים לעשות קריאה שהיא safe (לא משנה את state המערכת), אך רוצים להעביר פרטים מגוונים של הבקשה – אנחנו חסומים לשימוש ב URL לבדו. זו נשמעת מגבלה קטנה, כי על URL אפשר גם לשים 6,000 ויותר תווים בדפדפנים של ימנו – אבל זה לא כ”כ מעשי:
    • Query String הוא נחמד, וקל להשתמש בו בהפשטה של מילון (<Map<String, String) – אל זו עדיין צורה מגבילה להעברת מידע. אין היררכיה, וקשה לתאר מבנים מורכבים יותר. בעיה.
    • על זה נוסיף את הצורך ב URL encoding, כי יש תווים שאסור להציב על ה URL. למשל: רווח (להלן ה %20 שאתם רואים ב URLs). מהר מאוד ה URL הופך ללא קריא, ולכן פחות שימושי.
    • אפשר לקודד JSON (מבנה גמיש והיררכי) על ה URL – אבל זה מקרה קיצוני יותר מהאייטם הקודם. זה כבר לא פשוט, לא שקוף, ולא Web native. פספסנו לגמרי את הרעיון של שימוש ב HTTP בגלל “קריאות ופשטות”. כבר כבר יותר RPC.
    • אותה בעיה – קיימת גם עבור פעולות Delete (לכאורה Body הוא optional – אבל כלים רבים יחסמו אותו).
    • הפתרון הנפוץ הוא לבצע פעולות POST עבור קריאות שהיו אמורות להיות GET. ב POST אפשר להעביר body – אבל בעצם השימוש ב POST התרחקנו מההקפדה על הסמנטיקה של HTTP – שזה כל העניין. לא ניתן להבחין כבר בין POST שהוא safe לכזה שלא, ללא מידע נוסף. ראיתי מגוון גישות להתמודד עם החריגה הזו (POST as GET) – כל גישה, וה tradeoffs שלה.
  • חוסר בהירות בין POST ל PUT
    • פעולת PUT ב HTTP משמשת להחלפה של משאב שנמצא ב URL בתוכן הנשלח ב Body, וזו פעולה Idempotent – ניתן לבצע את אותה הפעולה מספר פעמים, מבלי שהתוצאה תהיה שונה מביצוע יחיד של הפעולה.
    • פעולת POST ב HTTP מעדכנת / שולחת מידע למשאב שנמצא ב URL – שזו הגדרה כללית הרבה יותר. הפעולה בהגדרה איננה Idempotent (כלומר: חלק מהפעמים היא עשויה להיות – אך אסור להסתמך על זה).
    • מכאן ועד תרגום של ה Verbs של PUT ו POST ל Create/Update/פעולה אחרת – יש מגוון של פרשנויות, לפעמים פרשנויות סותרות המתבססות על דקויות נכונות מתוך התקן של HTTP. במערכת Wiki, קביעת גבולות ברורים בין PUT ל POST היא דבר קל – במערכת מוצר מורכבת ומסועפת, תקן ה HTTP משאיר אותנו “חשופים” לדילמות, פרשנויות, והתלבטויות.
    • אם אתם לא מכירים את הדיונים הרבים, הנה מאמר תמציתי אחד שמסביר מדוע POST ו PUT הם לא פשוט Create ו Update.
  • בלי להכביר במילים, חוסר בהירות דומה קיימת גם בין PUT ו PATCH. הנה מאמר של המחבר הנ”ל לגבי הדילמה הזו. יש גם מאמר על PUT vs. PATCH vs. JSON-PATCH, ועוד אחד, ועוד אחד, ועוד אחד – כל אחד עם טענה מעט שונה.
הנה פרשנות אחת (ומקובלת) מתי להשתמש ב PUT ומתי ב POST ליצירת אובייקט. יש עוד כמה פרשנויות סבירות והגיוניות.
  • אולי המגבלה הכי קשה באימוץ “REST על מלא-מלא” בהיבט ה Verbs הוא הדלות ואי-העדכניות של ה Verbs המקוריים של HTTP לצרכים ועושר-הפעולות של מערכות תוכנה מודרניות. REST גרס שלא צריך להמציא “שפה חדש” – ואפשר להשתמש ב HTTP, אבל בשפה הזו יש רק 9 פעלים (עוד כ 20 נרשמו כהצעות) – אך אנו זקוקים ל 300 פעלים בכדי לקיים שיחה אינטליגנטית בדומיין העסקי שלנו? דומיין שהוא הרבה יותר מ CRUD?
    • אולי עבור מערכת וויקי, 9 פעלים מספיקים: עמוד יכול להיווצר / להיכתב מחדש (PUT), אפשר לכתוב עליו הערות (POST), ניתן לעשות בו תיקונים קטנים (PATCH) ואפשר למחוק אותו (DELETE) – הנה הסתדרנו!
    • במערכת של חברת ביטוח (למשל), בה על פוליסת ביטוח ניתן לבצע מגוון של פעולות:
      • לחתום על הפוליסה
      • להתחיל את הפוליסה
      • להקפיא / להפשיר את הפוליסה.
      • להוסיף מוטבים.
      • להוריד עותק של המסמך
      • לשלוח את המסמך לצד שלישי
      • לשלוח אישורים על המסמך לצד שלישי
      • לעדכן פרטים של בעל הפוליסה
      • לעדכן פרטים לגבי תוכן הפוליסה
      • לשנות כיסויים בפוליסה
      • לבדוק את הכיסויים בפוליסה
      • לבדוק סימולציה של מקרה – האם מכוסה ע”י הפוליסה
      • לבצע ביקורת על הפוליסה (Audit)
      • ועוד פעולות רבות…
      • הפוליסה מתוארת בצורה RESTFul למהדרין, כ URL כמו customers/{customer_id}/policies/{policy_id}/ אבל אין לי סיכוי לתאר את מגוון הפעולות הנ”ל בעזרת POST/PUT/PATCH בלבד. כל הרעיון ש “נראה את ה verb בלוגים השונים – ונבין מה התרחש” – מאבד ערך אם עבור כל הפעולות הנ”ל אני משתמש ב POST verb (פתרון מקובל אחד). יתרה מכך: אני צריך להשלים את הסמנטיקה שמבדילה בין הפעולות השונות איפשהו (על ה URL? על ה body? כ Header? כ Query Param) – אין מקום מוגדר וברור לעשות זאת. מגוון אפשרויות, עם tradeoffs וחריגות שונות מתקן ה HTTP והכוונות מאחוריו.
  • גם ברמת ה URI, אנו נתקל במגבלות: תיאור של URL היררכי לכל אובייקט במערכת – הוא לא דבר חד-משמעי ופשוט כפי שהיה במערכות פשוטות פעם (שוב: וויקי/מערכת פורומים). אקצר מאוד כדי לא להאריך את הפוסט אבל בעיקר:
    • לא תמיד יש היררכיה בין אובייקטים. לפעמים יש אובייקטים שהם שכנים “Siblings” – ואין דרך טובה לתאר את הקשר בהיררכיה (על ה Path).
    • לפעמים פרטי הזהות של אובייקט הם מורכבים / ארוכים מדי בכדי להיכנס ל URL.
    • ב REST/HTTP אין סמנטיקה לתאר Bulk operation – פעולה על מגוון אובייקטים. ישנן workarounds אך הם שונים ומגוונים, ולכל אחד – המגבלות שלו.
  • ב REST/HTTP אין דרך מוסכמת לתאר גרסאות ועתיות של APIs. באופן אישי אני משוכנע שהשתתפתי בעשרות שעות של דיונים בנושאים הללו, בארגונים שונים ועם אנשים שונים – כי אין תשובה פשוטה של REST שניתן לגשת ולהציג אותה. הנה סדרה של גישות ודיונים בנושא, למי שרוצה לצלול… הנה גם תקציר על 4 גישות מקובלות.
מישהו ריכז כאן דוגמאות וגישות ל versioning ב REST (והיד עוד נטויה). רק להדגים שהנושא אינו “סגור בטון” ומקובל על כולם באותו האופן.
  • עוד “שפה” שהייתה טובה / עשירה בזמנה – אך היום היא מוגבלת מדי היא ה HTTP Status codes. קודים כגון OK 200 או 401 (הקוד נקרא “Unauthorized” אך הכוונה היא בעצם ל Unauthenticated) – טובים ומתאימים לשכבת התשתית, אך מזמן כבר דלים מכדי לתאר את שכבת האפליקציה. להזכיר: HTTP הוגדר עבור ניהול דפי וויב (“documents” או “resources”) ולא לתיאור של אפליקציות עסקיות מורכבות. למשל:
    • האם 404 מתאר endpoint שלא קיים, אובייקט שלא נמצא, או פריט מידע לפעולה שלא נמצא? איך מבדילים בין המקרים אם חזר רק הקוד “404”? דיון. לא פעם מפתח אחר מתכוון לדבר אחד, ומפתח אחר – מבין דבר אחר.
    • עוד דוגמה קלאסית היא המשמעות של 400 (הקרוי Bad Request).
      • ע”פ התקן – השרת לא יכול או לא יטפל בבקשה מכיוון שלא הוגשה בקשה נכונה מצד הלקוח (פורמט שגוי, שליחה ליעד הלא נכון)
      • השימוש הנפוץ הוא הרחבה של הקונספט “משהו לא טוב בבקשה” (ולא רק בפורמט/יעד) למשל: נשלח id שלא קיים של אובייקט. אבל…
        • אם נשלחו בבקשה כמה Ids – אז איזה מהם?
        • או אולי, אי אפשר לבצע את הפעולה מכיוון שעבר התאריך.
        • או אולי, לא ניתן לעשות פעולה בסכום נכון, כי החשבון מוקפא, או כי אלו פרטים אחרים אינם תואמים?
      • בקיצור: שום קוד סביר לא בצד הלקוח לא יכול להסתמך על קוד HTTP 400 להחלטה משמעותית של שינוי ב Flow ללא פרטים נוספים. נדרשים קודים מדויקים למגוון גדול של תקלות אפשריות, אז מה החשיבות להחזיר גם קוד HTTP 400? ראיתי שנים אנשים שהקפידו ותיקנו שוב ושוב קוד שהחזיר HTTP 500 להחזיר HTTP 400 – אך מעולם לא נתקלתי בערך שצץ מהפעולה הזו… פשוט עבודה לחינם.
  • עוד נושאים שאפשר לציין ש REST/HTTP לא נותנים להם מענה:
    • תקשורת אסינכרונית
    • Notifications – התקשורת של HTTP היא בקשה/תשובה. אם השרת המרוחק רוצה להודיע לי משהו ביוזמתו – כיצד הוא יעשה זאת?

אלו צרכים בסיסיים, שכנראה רוב מערכות הווב של ימנו זקוקות להם.
חשוב לציין ש HTTP/2 הוא כבר פרוטוקול בינארי, עם יכולות notifications, ואסינכרוניות (Pipeline/streams) – אך עדיין לא נתקלתי ב RESTful APIs שמשתלב ביכולות הללו בצורה הרמונית וטבעית.

סיכום

חזרנו והתבוננו ב REST בראייה מפוכחת, לא רק “REST זה טוב, SOA זה רע” – פזמון הפאנק של אמצע שנות ה-2000.

אני לא ממליץ לאף אחד לחזור ל *-WS או SOAP חלילה, הם כבר עתיקים ולא רלוונטיים. ייתכן ו RESTFul APIs זו הגדרה מספיק טובה לסט מצומצם של Public APIs של חברה (והרי ב Public API יש יתרון גדול בשימוש בתמות מוכרות), אך דיי ברור שלצורכי התקשורת הפנימיים של מערכת מורכבת / מיקרו-שירותים, RESTFul APIs היא גישה מוגבלת מדי ודלה בסמנטיקה – שחייבת הרחבה.

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

לא ענינו על התשובה לשאלה: “כמה RESTFulness כדאי לעשות? ואיך יודעים לאמוד מתי ההצמדות ל RESTfulness היא תורמת, ומתי היא כבר מזיקה?” – אל אני מניח שכל ארגון תוכנה בימנו ש”מתבסס על REST” צריך לשאול את עצמו את השאלה הזו – ולהמציא תשובות.

יש ניסיונות כנים ומשמעותיים לנסות ו”לתקן” את REST (בצד התקשורת; צד הארכיטקטורה הוא מאוד ספציפי ומתאים רק למיעוט שבמיעוט שבמערכות) למשל JSON API או להיצמד ל Guidelines של מישהו שעשה עבודה משמעותית, למשל ה Microsoft RESTFul API Guidelines. פעם מישהו הציע לי לאמץ בארגון את JSON API ושללתי את זה על הסף (“כל ארגון יגדיר את מה שמתאים לו”). היום הייתי מתייחס לאופציה הזו ברצינות גדולה יותר. לא כ “תורה מסיני שתפתור הכל”, אלא כדי לא להתחיל את תהליך ההגדרה הארגוני מאפס.

לא נכון לכעוס או להתאכזב מ RESTFul APIs כרעיון כללי כ”כ, לא מוגדר היטב, ופשטני. הפשטות הזו – פתרה בעיה גדולה לארגונים בשלב הצמיחה (גוגל, יאהו, ועשרות-אלפי סטאראט-אפים נוספים) ושירתה אותם היטב. הכלליות – התאימה ל”כולם” (כל אחד מצא שם משהו שדיבר אליו, בלי משהו גדול להתנגד אליו) ויצרה “רעיון” שמשך אחריו את כלל התעשייה – גם אם לא היה מדובר בתקן מדויק. רעיונות ה Web-Native חלחלו עמוק, והיום מהנדסים רבים כל-כך חותרים להשתלב עם הווב, במקום “להחליף את הקיים”.

תנועות כגון Forget HTTP + הרצאה או HTTP Hell-no אולי מביעות הרבה תסכול מה”אכזבה” של REST בפועל – אך מנסות להציע גם חלופות ותיקון.

טכניקות כגון Polling, Long-Polling ו Webhooks הן דוגמאות פשוטות להרחבות טכניות פשוטות – המאפשרות עוד הרבה מרחב תמרון במקומות ש HTTP/REST מוגבל בהם (“client קורא ל server”, וזהו). יש לי הסברים מפורטים על הטכניקות הללו – אניח לרגע שכל הקוראים מבינים אותן. כן גם משמרות את האופי של HTTP/REST כמעט ולחלוטין – זו רק הרחבה.

הפתיחות לאמץ אלמנטים של RPC בצורת העבודה של הארגון (להתגמש ב Resource modeling, להוסיף קודים של הארגון לכל תשובה, ו/או מבנה סטנדרטי של בקשה/תשובה לכלל המערכת) עשויים לשרת אתכם היטב, גם אם “קהילת נאמני ה REST” – ידיחו אתכם מחברות בארגון / חיי נצח / ההבטחה ל 70 מהנדסות תוכנה (טובות!) בעולם הבא.

(אם מישהו ישאל, JSON-RPC הוא פשטני ונאיבי מדי לטעמי, וגם אותו תאלצו מהר מאוד להרחיב ולהגדיר בעצמכם. RCP בינארי כמו Thrift או gRPC הוא אטקרטיבי לשיפור ביצועים / אמינות התקשורת – אך מהלך כבד מדי אם כל מה שאתם מחפשים הוא סמנטיקה וכללי עבודה טובים לבעיות מידול ה Endpoints במערכת שאינה very high scale, הפשטות הרי חשובה).

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

אם אתם עובדים ברמה של HTTP בצורה ניכרת (או ב RESTFul APIs או ב RPC כלשהו, למשל home-made) – חשוב ללמוד את התקן הפשוט והחשוב הזה, ולהבין אותו. אני רוצה להאמין שזה מאמץ קטן ומשתלם. יש את הפוסטים שלי (HTTP Basics + מבנה ה URL) אלו ואלו, יש את הרפרנס הנחמד הזה של for-get-HTTP – HTTP References שמצאתי במהלך כתיבת הפוסט – ונראה לי שאשתמש בו עוד בעתיד.

מחשבות, הערות, תוספות – יתקבלו בברכה!

ארכיטקטורת-נתונים במערכות מוצר

יש המון שיח על ארכיטקטורות נתונים בשנים האחרונות. Data Lakes, Lambda Architecture ו Delta Lake וכו’.

כמי שעובד במערכת מוצר (מיד אגדיר) – רוב הדיונים הללו עוברים מעל הראש:

  • כמות הנתונים שמתעסקים איתה היא לא כ”כ גדולה – ובטח לא עצומה.
    • למשל: בסיס נתונים המכיל “רק” 2TB של נתונים – ועוד כמה events או מסמכים התופסים יותר מקום – אך מאוכסנים ב S3 (שירות אכסון הנתונים של אמזון, בעל ממשק דומה למערכת קבצים).
  • את רוב העבודה יכולים, וגם רוצים – לעשות בבסיס הנתונים רלציוני.
    • אולי חלק מהמידע הוא Document-Based (הרי PostgreSQL ו MySQL תומכים בניהול מסמכי JSON) אולי יש עוד בסיס נתונים אחד או שניים NoSQL – אך הם משתמשים לתהליכים מאוד ספציפיים.
  • אין עיבודי מידע גדולים / מורכבים בלב המערכת – שבסיס נתונים רלציוני לא יכול להם.
    • אולי יש כמה תהליכי אצווה שמעבדים הרבה נתונים – ואז סיכומים שלהם הופכים לדוחות או חוזרים כסיכום למערכת – אך לא באופן שישנה את הארכיטקטורה הבסיסית של המערכת.
  • המערכת היא אחידה, ולא מורכבת מתתי מערכות שונות ומשונות (למשל: שנבעו מסדרה של רכישות).

יש לנו Lambda Architecture וגם Data Warehouse וגם Data Lake (ועוד אחרים) – אך בעיות הדאטה הגדולות שלנו לא נמצאות שם. 

אני רוצה להקדיש פוסט לבעיות-נתונים שאינן מסביב ל Streaming, High Scale, או Realtime. יש המון חומר על התמודדות עם הבעיות (הגדולות, המכובדות) הללו – אך יש עוד כמה בעיות, קשות, שלא זוכות לאותה רמה של דיון.

מה הן “מערכות-מוצר”, ומה בעיות הנתונים הנפוצות שלהן?

מערכת-מוצר היא מערכת שמשרתת לקוחות קצה (אנשים פרטים, עסקים קטנים או גדולים – אך מערכות אחרות) ורוב האתגר בה הוא ניהול המידע וה Flow העסקיים. למשל: LinkedIn, Moday, Gett, Next-Insurance, ועוד רבים.

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

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

“מערכת מוצר” הוא בקיצור ביטוי שבא לבדל מול מונחי-עבר כגון “מערכות-מידע” או “Enterprise System”, שמתקשרים לרבים למערכות צדדיות ושאינן מעניינות כ”כ מבחינה טכנולוגית ו/או הנדסית.

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

  • המערכת משתמשת בכאלף טבלאות בבסיס-הנתונים (יש גם טבלאות טכניות. 500-700 בעלות נתונים עסקיים בעלי-משמעות). זה בערך 10 טבלאות לכל מפתח – מדד שיכול להצביע על העומק / מורכבות של המודל העסקי המטופל.
    • אציין שיש כ 30 מיני-שירותים, ואני סופר את הטבלאות של כולם ביחד.
  • שינויי סכמה בטבלאות קיימות מתרחשים כל שבוע. כלומר – המודל דינאמי למדי.
      • עבודה ב Continous Deployment הופך את הבעיה לקשה יותר: כל שינוי – מוחל מיד על הנתונים הקיימים וישפיע על כל צרכני-הנתונים.
  • ישנם כ 20 אנליסטים (והמספר גדל) שאמורים להתמודד עם כל המגוון הזה של הנתונים – ולהסיק ממנו מסקנות מדויקות, וחד משמעיות – מסקנות שיובילו להחלטות עסקיות, למשל: מה ההשפעה של שינוי מסוים במערכת.
  • דיוק הנתונים עשוי להיות בעל חשיבות עליונה: טעות של 1 למיליון בנתונים כספיים או בנתונים לגבי כיסויים ביטוחיים – היא כבר בעיה שיש לטפל בה (בשונה ממערכות עיבוד נתונים רבות, המוכנות לספוג איבוד/אי-דיוק נתונים גדול בהרבה. לדוגמה מערכת שאיבוד / טעות בעד 0.02% מהנתונים, 1 ל 5,000 – לא נחשב בעיה)
    • כמובן שגם במערכות מוצר יש מידע חשוב יותר וחשוב פחות – ויש סובלנות לאיבוד/אי-דיוק מידע באזורים מסוימים.
  •  טווח העבודה העיקרי עם הנתונים הוא שנה עסקית, כלומר 12 חודשים לאחור. אם היה באג בנתונים מלפני 3 חודשים – חשוב לחזור ולתקן אותם, ואת כל ההשפעות שנבעו מהשינוי => מה שאולי גורר עוד ועוד תיקוני נתונים, ברקורסיה.

 

אם לא “קפצו” לכם, עד כה, כמה בעיות שנובעות ממודל שכזה, אציין אותם במפורש:

  • תהליך ה ETL (העברת הנתונים בסיס הנתונים של המערכת למשטח עבודת-נתונים “נוח יותר” כמו Data Lake או Data Warehouse) – צריך להתמודד עם שינויים יומיומיים בסכמה.
    • עם שינויים כגון הוספת טבלה חדשה – קל להתמודד. זו יכולת מובנה ברוב כלי ה ETL.
    • שינויים כגון שינוי סכמה או תיקון היסטורי של נתונים (Backfill) – כבר דורשים טיפול מיוחד, במיוחד כדי לא להוציא את הנתונים ב”משטחי העבודה של הנתונים” מעקביות (Consistency).
  • תקשור מודל הנתונים: כיצד אנליסט אמור להתמודד עם מגוון של מאות טבלאות שמתווספות ומשתנות כל הזמן?
    • מה הסיכוי לחוסר הבנה לגבי משמעות הנתונים, שיגררו ניתוח לא מדויק או אפילו שגוי לגמרי? (רמז – לא קטן בכלל)
  • כאשר ה Data Engineers (האחראים על תהליך ה ETL) והאנליסטים “סובלים” – ההשפעה מחלחלת בחזרה גם למהנדסים.
    • “לחץ” להאטה בקצב / עומק השינויים. כאשר דוחות / ניתוחים מורכבים מתבססים על מבנה נתונים מסוים – שינוי המבנה עשוי לגרור עבודת עדכון של שבועות ואף חודשים. הברירה השנייה: להורות למפתחים לא לשנות את מבנה הנתונים “ולהסתדר” עם מבנה הנתונים הקיים (מה שעשוי ליצור בעיה גדולה למפתחים).
    • דיונים ושאילתות חוזרות על משמעות הנתונים – מה שגורר זמן עבודה משמעותי בין מפתחים ואנשי-נתונים לגבי משמעות הנתונים.
      • תסריט שמי שחי בעולמות הללו בטח מכיר: מדד עסקי מראה ירידה בדוחות, ועכשיו צריך לחקור: האם הבעיה בדוחות, בשינויי מודל הנתונים, או באג בקוד? המנכ”ל או מי מטעמו מגיע למפתחים בבקשה “להוכיח שזה לא באג בקוד”… 
    • כל זה גורר לאיבוד אמון בין הקבוצות, מה שרק שעלול לגרום להגביר את הבעיות, ואז שוב לפגוע באמון – וחוזר חלילה. מחזור של הסלמה…
  • ככל שהמודל העסקי מורכב ומגוון – כך קשה גם יותר למפתחים להבין אותו, ולשמור עליו עקבי בלי שיצוצו “כפילויות” או “סתירות” במודל בין המפתחים, שיחזרו ויסבכו את על העניין.
  • כיצד מנתחים נתונים היסטוריים? החברה קיימת 10 שנים, ומודל הנתונים בה משתנה תדיר. איך מנתחים מגמות לאורך השונים על מודלים משתנים.
    • גישה אפשרית אחת: כל פעם שמשנים מודל עסקי – עושים migration של כל מידע העבר למודל המעודכן.
      • תהליך migration הוא תהליך קשה ויקר (בשעות עבודה). נדרשים לא פעם “להשלים/להמציא” נתונים שלא היו בעבר – מה שמקטין את האמינות של הנתונים לאורך זמן.
    • גישה אפשרית אחרת: הניתוחים העסקיים יידרשו להכיר את כל הווריאציות של המודלים העסקיים לדורותיהם – מה שיהפוך אותם למורכבים הרבה יותר לביצוע.

 

ארכיטקטורת-נתונים טיפוסית

אז איך מתמודדים עם בעיות הנתונים של מערכות-מוצר?

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

הנה המתודולוגיות העיקריות שאני מכיר:

הברירה הטבעית

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

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

על זה נאמר: לארגון יש מדיניות חוץ – אך אין לו מדיניות פנים.

Long Release Cycle

במערכות של חברת SAP (חברת ERP – מערכות המידע המורכבות אולי בעולם) יש מודלים עסקיים אדירים ומורכבים. היה ספר של הטיפוסים במודל – שרק הוא הגיע לאלפי עמודים, ולא ניתן היה יותר להדפיס אותו בכרך אחד. גם שם היו הרבה מפתחים (כ 18K, בזמני) – אבל הבעיות הנ”ל לא היו בעיות גדולות. מדוע?

  • היה כלל שלא משנים Schema של בסיס נתונים בגרסה שאינה major.
  • גרסת major יצאה אחת לכמה שנים.

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

זה מצב לא-רלוונטי לסטארט-אפים, בטח SaaS. אפשר לדלג.

 Event Log / Event Streaming

המודל המקובל ביותר, By Far, בעניינים של שיתוף נתונים בין המערכת האופרטיבית (מפתחים) – למערכות הנתונים (אנשי-דאטה מסוגים שונים) הוא על בסיס Events:

“אירוע: משתמש חדש נרשם במערכת… הנה כל הפרטים”

“אירוע: משתמש שינה את תמונת הפרופיל שלו…. הנה כל הפרטים”

“אירוע: משתמש על Accept לאחר 5 שניות מרגע שהופיע המסך… הנה כל הפרטים”.

בואו נעשה כמה הבחנות:

  • אירוע מתאר משהו שהתרחש – ולא את מצב קיים.
    • כדי לדעת את המצב הקיים – יש להרכיב את כל רצף האירועים על אובייקט רלוונטי.
  • אירוע הוא encapsulated – קרי מכיל את כל המידע הרלוונטי. בניגוד לטבלאות בהן עלי אולי לעשות פעולת JOIN לעוד כמה טבלאות להשלים את התמונה, האירוע מכיל את כל הנתונים הרלוונטיים.
    • זה כמובן אידאל, אף אחד לא מונע מאתנו לשלוח events חלקיים.
    • צרכנים שונים, עשויים להזדקק לאירועים שונים בתכלית. כנראה שנרצה להכליל כמה שיותר צרכנים באירוע – אבל זה לא תמיד יצליח.
  • אירוע הוא contract (“חוזה”) – השולח והמקבל חייבים להסכים על תוכן האירוע ומשמעותו, אחרת המודל לא יוכל לעבוד.
    • מה השדות ומה משמעותם. אלו שדות מופיעים באלו מקרים.
    • מתי נשלח אירוע, ומתי לא. למשל: משתמש עדכן תמונת פרופיל לאותה תמונה בדיוק. האם זה אירוע, או לא?
  • האירועים הם מסביב להתרחשות עסקית – ולא מסביב להתרחשות טכנית. ההבחנה ב”עסקיות” של אירועים כמובן תלויה בין חברה לחברה.
    • שלב הלוגין של משתמש במערכת כולל אולי עשרות צעדים טכניים, אבל מבחינה עסקית – מעניין רק הלוגין כאירוע אחד.
      • כמו שציינתי, זה אידאל, ואף אחד לא ימנע מאתנו לשלוח אירועים ברזולוציה שאינה עסקית באמת.
    • אירוע קנייה של מוצר הוא אירוע יחיד ללקוח, אבל כנראה מספר אירועים ברמה העסקית לחברה. צפייה במוצר, דפדוף במוצרים דומים, בדיקת פרטי המשלוח, ושלבי הקנייה.
      • כמו שציינתי, לארגונים שונים רזולוציה שונה היא “עסקית”. עבור גוגל (מנוע חיפוש) הצגה של תוצאה בדף החיפוש עשויה להיות “אירוע עסקי” ובוודאי כל הקלקה על קישור.

ה Events לרוב מיוצרים בקוד של המערכת האופרטיבית (מיקרו-שירותים) ועוברים על גבי Event Streaming Infrasturcture (כגון Kafka, Pulsar, Kinesis) ל Data Lake. משם הם יעברו ל DWH (או Data Lake אחר) שם “יסכמו את האירועים” לצורה של אובייקטים המציגים את המצב הקיים, או שיעשו עליהם ניתוחים ברמת ה Events.

הגדרת Events היא הזדמנות להגדיר חוזה ברור, לנתק את ה coupling בין המבנה הפנימי של בסיס-הנתונים למודל העסקי שאותו צורכים צרכני-הנתונים (ואז אין בעיה לשנות Schema כל יום – כל עוד מבנה האירועים נשמר), ובכלל לעבוד עם נתונים ברמת הפשטה גבוהה יותר (“אירועים עסקיים” ולא “שורות שנשמרות בבסיס הנתונים”).

בכל זאת, אף אחד לא מבטיח לנו שהפוטנציאל ימומש בחלקו או במלואו – מימוש טוב ויסודי (Data Dictionary, Data Modeling, ומערכות נתונים מתאימות) הם קשים ליישום, והכרחיים להצלחה.

ישנן עוד כמה בעיות מובנות במודל ה Events:

  • בסיס נתונים הוא אמין יותר מ Event Stream Infra: הודעות עלולות להיאבד או להישלח פעמיים + Events הם עיבוד הנתונים – ולא מקור הנתונים. לכאורה יש כאן ירידה מ”מערכות נתונים שצופות ישר בבסיס הנתונים, the source of truth”.
    • לכל אלו שקופצים עם “Kafka supports Exactly-once message delivery semantics” אני אזכיר שתקלות יכולות להתרחש גם לפני / אחרי Kafka.
    • בסופו של דבר, יהיה לנו כנראה תהליך Scheduled של Eventual Consistency – שיאתר פערים במידע, וישלים אותם.
  • תיקוני נתונים הם קשים הרבה יותר ב Events (יחסית לבסיס נתונים). נניח גילינו שחייבנו 15K לקוחות ביותר מדי – נרצה לחזור ולעדכן אותם. שתי הגישות העיקריות לתיקון נתונים הם:
    • הוספת אירוע מתקן (או סדרה של אירועים מתקנים), עם Effective Date של עבר – כך שמי שמרכיב את האירועים ידע להרכיב אותם נכון.
      • קושי עיקרי כאן הוא לייצר Delta “נכונה”. כלל שהלוגיקה העסקית מורכבת יותר, והאירוע הוא ברמת-הפשטה גבוהה יותר, יהיה קשה לייצר את ה Delta נכון – ולוודא שהיא נכונה. קוד המערכת כתוב בכדי לייצור אירועים חדשים, ויצירת של Deltas (נכונות) – עשוי להיות אתגר ממשי.
    • לכל אירוע מוצמד מזהה חזק (GUID), ואירועים יישלחו מחדש – מתוקנים.
      • כאן הקושי הוא לאתר אלו אירועים יש לתקן (אירוע הוא הפשטה מעל מספר טבלאות בסיס נתונים + הם יושבים ב Data Lake – מערכת נוספת שמפתחים צריכים “להתעסק” איתה). גם כאן, כשאין Delta, צריך לדעת איזה רכיב בדיוק במערכת הקוד יש להפעיל – רכיב שאולי לא נבדק במוד הרצה שכזה – וזה אתגר. (רמז: מתחילים בכתיבת Tests לתסריט החדש הזה).
    • תיקון נתונים מתחיל כתהליך, “One-off”, ידני – אך מהר מאוד מגלים שזו שגרה, ומשקיעים בתהליכים מסודרים ואוטומטיים לתקן מידע לאחור.
  • הגדרה של תיאור/Contract ברור וחד-משמעי של כל Event הוא לא תמיד דבר פשוט, בטח כשיש עשרות ומאות אירועים במערכת. 
    • כתיבת בדיקות אוטומטית שיאשרו שלא חורגים מהחוזה שהוסכם לכל אירוע. 

מודל ה Events הוא Step Up משמעותי בהשקעה ה Up-front הנדרשת, ולכן ארגונים נוטים לדחות את המעבר – ולהסתמך על “הברירה הטבעית” (בה יותר קשה למדוד את גודל הנזקים שנגרמים) רק עוד קצת.

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

Well-monitored Data Sharing

במקרים מסוימים, ולרוב רק לחלק קטן מהמידע – מחליטים להמשיך ולשתף את מבנה (סכמה) הנתונים הפנימי של המערכת עם מערכות / לקוחות ה Data של המערכת האופרטיבית. במקום להסתמך על “מזל” (להלן גישת “הברירה הטבעית”) – מנטרים ומנהלים היטב את שיתוף הנתונים:

  • הנתונים, והמשמעות מדויקת שלהם מתועדים ומוגדרים-היטב.
  • כל שינוי בסכמת נתונים מתוכנן ומתוקשר מבעוד מועד, לכל הצרכנים – על מנת שיתכוננו.
    • אולי הם שותפים שווים למבנה הנתונים, וכל שינוי הוא דיון – ולא “תקשור”.
  • מתעדפים שינויים בקוד – על פני שינוי סכמת הנתונים. למשל:
    • אם מפתחים יכולים להתאמץ קצת יותר כדי לא לשנות את סכמת הנתונים – שיעשו זאת.
    • כאשר עוברים למודל חדש – נעשה migration לכל הנתונים ההיסטוריים להתאים למודל המעודכן.

הסיבות העיקריות למודל ה Well-monitored data sharing הוא לרוב:

  • נתונים שלא נוטים להשתנות בצורה תכופה.
  • נתונים שרמת ההפשטה הטבעית שלהם (Raw Data) – תואמת לרמת ההפשטה הרצויה מבחינת העניין העסקי.
  • נתונים מאוד קריטיים ורגישים לארגון, בהם רוצים להסתכל על ה Source of Truth עם מינימום תרגומים / תחנות בדרך.

דוגמה קלאסית לכאלו נתונים הן נתוני-תשלום (כרטיס אשראי, העברות בנקאיות, וכו’):

  • מבנה הנתונים לרוב נגזר ממערכות סליקה / תקנים בינלאומיים – שכמעט ולא משתנים.
  • כל נתוני התשלום כמעט (אולי חוץ מ ids פנימיים) – עשויים להיות מעניינים לצרכני-הנתונים בארגון.
  • הארגון מעוניין לדעת על כל תשלום שהיה / נכשל / התבטל, בלי לפספס אף שורה.

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

סיכום

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

שני המודלים הרלוונטיים הם כנראה ה Event-Steaming ו Well-monitored shared Data. אפשר לעשות בהם mix and match ע”פ הצרכים של כל ארגון / מערכת.

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

כמו כן, אשמח לתובנות שיתופים על חוויות ופתרונות בנושאים הללו.

 

תודה לנדב נוה, ששימש כ Reviwer לפוסט – ועזר לי לדבר יותר לעניין.

 

לינקים רלוונטיים:

The Log: What every software engineer should know about real-time data’s unifying abstraction – מאמר של יוצר Kafka מ 2013, שגם עוסק במבנה הטכני של Event Streams – אבל גם בכל בעיות הדאטה שציינו כאן (LinkedIn היא חברת-מוצר / מערכת-מוצר קלאסית)