דפוסי עיצוב (Design Patterns): ימים יפים, ימים קשים

[הפוסט נכתב בשיתוף עם רחלי אבנר, Area Architect בחברת SAP]

[ליאור:] שוחחתי לפני כשבוע עם ארכיטקט ותיק בחברת תוכנה ישראלית. סיפרתי לו שאנו מעבירים קורס של Design Patterns בחברה.
"למה….?!" – הוא שאל, תופס את ראשו בידיו במבט מיוסר.
"כן…. גם אני לא מתלהב מהנושא" עניתי. "אבל אנשים רוצים. מה, נמנע מהם את הידע הזה??"

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

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

כיצד ומדוע עלו ה Patterns לגדולה?
מדוע אם כן יש עליהן ביקורת, והאם יש לכך הצדקה?

על כך בפוסט שלפניכם.

כריסטופר אלכסנדר

שורשים

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

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

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

מ-ש-ע-מ-ם

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

עדיין, זה לא מבנה שגורם לנו להרגיש "בבית".

הנה מבנה מוצלח יותר:

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

את שרשרת ההבחנות סיכם בשלושה ספרים, שהמשמעותי ביניהם נקרא: A Pattern Language: Town, Building, Construction.

כריסטופר אלכסנדר הציג דפוסי עיצוב כגון "כניסה ראשית", "אזור ישיבה", "גומחה" (Alcoves), "שדרה", וכו'. כל אחד מהם היה זיקוק של תובנות כיצד יש לתכנן אלמנט זה במבנה בצורה טובה יותר. הוא הגדיר אותם כדפוסי עיצוב המעודדים חיים, הוא השתמש גם בביטוי "איכות ללא שם". הספר כלל 253 דפוסי-עיצוב [ה].

ספרי דפוסי העיצוב של כריסטופר אלכסנדר

דפוסי-עיצוב בתוכנה

את הקשר בין כריסטופר אלכסנדר לעיצוב תוכנה מונחה-עצמים מצאו כמה. מצאתי, למשל, את המאמר הזה של דאג לי (Douge Lea)[א] המנסה להשליך מהרעיונות של כריסטופר אלכסנדר על הנדסת תוכנה מונחת-עצמים. המאמר התפרסם כשנה לפני הספר המפורסם….

את הרעש הגדול עשו ארבעה: אריך גאמה[ב] ניהל שיחה עם ריצארד הלם בה עלו הרעיונות לספר. אח"כ הם צרפו את ראלף ג'ונסון וג'ון וילידס. ארבעתם ידועים כ "Gang Of Four" (או בקיצור GOF). רובם חוקרים / בעלי רקע משמעותי של מחקר במדעי המחשב – אבל הם כתבו את הספר, כנראה הנמכר ביותר בתחום, אחד המשפיעים ביותר ושנחשב למאוד מעשי ושהקדים את "האקדמיה" בשנים.

"הספר"

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

Facade, Proxy, Adapter, Singleton ועוד – הם מושגים שהטביע בעולם התוכנה ספר זה.
"דפוסי העיצוב" היו כבר קיימים בעולם: לכל דפוס עיצוב הם סיפקו מספר דוגמאות של יישומים במערכות קיימות. בספר הם אספו אותם, תיעדו אותם ויצרו שפה חדשה להנדסת תוכנה Object Oriented.

ההצלחה של הספר הייתה חסרת תקדים, וזעזעה (לטובה) את עולם התוכנה. בעקבותיה קמה "תנועה" של Patterns ונכתבו, בזמן קצר יחסית, מספר רב של ספרים עם Patterns רבים נוספים (ביניהם סדרות כגון PLOP או POSA). ה GOF החלו אף לדבר על הוצאת מהדורה שנייה.

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

גם היום, 20 שנה אחרי, ספרים חדשים משתמשים במותג "Patterns" בכדי לקסום לנו, ולהימכר טוב יותר:

סדקים ראשונים

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

הזמן גם עבר… והאופנות קצת השתנו – ויצרו סדקים נוספים:

  • תנועת האג'ייל: את התכנונים (Design) ה Top Down, החליפו ב Evolutionary Design ו Bottom Up. היכן ש Design Patterns כבר פחות מתאימים.
  • בזמן הוצאת ספר דפוסי-העיצוב, תכנות OO (או תכנות מונחה היבטים – AOP) היה "הדבר הגדול הבא". היום תכנות OO הוא נפוץ מאוד – אבל "הדבר הגדול הבא" הוא משהו בין תכנות דינאמי לפונקציונלי – קצת שונה מהעקרונות של OO. לשפות אלו, דפוסי העיצוב המוכרים – פחות מתאימות.
  • "אמיתות של הנדסת תוכנה" גם הן השתנו. אם פעם שימוש חוזר בקוד היה העיסוק המרכזי, היום אלו יותר פשטות של הקוד ו time-to-market. העיקרון הפתוח-סגור (Open-Closed Principle) פעם היה מאוד פופולרי (ורואים את חותמו בספר של GOF) – אך כיום הוא שנוי במחלוקת. דפוסי העיצוב מהספר של GOF מקדמים הרבה שימוש חוזר והפשטה – וקצת פחות פשטות.

את הסדק הגדול ביותר ניתן לתאר בעזרת הסיפור (לא מצאתי את המקור) על מרצה שהרצה על Design Patterns רק כדי לשים לב לאחר שנים לשאלות המוגזמות-מיסודן שמעלים אנשים: "האם עדיף להשתמש ב Flyweight או במשפט If"?
Flyweight או משפט If? האם Simplicity היא לא ערך – האם הגיוני להחליף שורת קוד אחת במבנה שלם (Flyweight)?!

דפוסי העיצוב באו להלחם בעולם עם Under-Engineering, אך יצרו עולם של Over-Engineering.

לזרוק ולשרוף?!

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

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

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

אני אסביר:
כשלמדתי עיצוב תוכנה אמרו לי בערך כך: "תרשום על דף את הדרישות, תחשוב, תעשה איזה תרשים UML והנה יש לך עיצוב תוכנה". לא הרגשתי שלמדתי תובנות משמעותיות כלשהן, או שלמדתי כיצד לעשות את התהליך נכון.
אני מניח שהחוויה של רוב האנשים בתעשייה שלנו איננה שונה בהרבה.

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

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

לו רק הייתה יותר הכוונה…

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

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

כיצד להשתמש בדפוסי עיצוב בצורה נבונה?

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

צדדים טובים:

  • פתרונות מוכנים ומעולים לבעיות תכנון. נכון חלקית: אלו פתרונות טובים לבעיות מאוד מסוימות, וקיים סיכון סביר (ומוכח) – שאלו לא הפתרונות הכי טובים למערכת שלכם. זכרו: גם בספר "Design Patterns" – לא תמיד באמת מדברים עליכם.
  • שפה משותפת: ברגע שגם אני וגם בין-שיחי מכירים את דפוסי העיצוב (למשל) "Strategy" – יהיה לנו הרבה יותר קל לתקשר על בעיה / פתרון טכני מסוים. מילה אחת יכולה להחליף הסבר של כמה דקות. חשוב ללמוד דפוסי-עיצוב בכדי לקבל מושגים משותף ולהפוך את התקשורת בזמן העבודה ליעילה ומדויקת יותר. כמו כן, היחסים המוכרים בין תבניות שונות מאפשרות בסיס לשיחה על מבנים מורכבים יותר (למשל: MVC). שפה משותפת היא התועלות המוסכמות ביותר בימנו המיוחסות לדפוסי-עיצוב.
  • מקור השראה: לא חייבים ליישם את דפוס העיצוב כלשונו. ניתן לעשות וריאציה, ניתן לקחת רק רעיון אחד מדפוס העיצוב – ולעשות משהו אחר לגמרי. לגישה זו יש הרבה פוטנציאל, ולשם כך טוב (באופן כללי בכלל) להכיר תכנונים לא-טריוואילים של מערכות אחרות.
צדדים פחות טובים:
  • הנדסת-יתר (over-engineering) – עסקנו בבעיה זו די והותר.
  • פוטנציאל לקיבעון: מצב בו למישהו קשה ליישם מבנה דומה-חלקית לדפוס העיצוב, כי הוא מרגיש ש"דפוס העיצוב" חכם בוודאי ממנו – ואז הוא נגרר ליישום פחות מוצלח (אם כי שמתועד בספר אגדי).
  • פוטנציאל לקיבעון כרוני: אנשים שלא מוכנים להאמין שיש פתרון טוב יותר (בהקשר המסוים) מזה המתואר בספר האגדי של "Design Patterns". לכו תשכנעו אותם.

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

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

  • ככלל: אל תתחילו לכתוב קוד ולהשתמש בדפוסי-עיצוב. אל תעשו הכנה ל Adapters – כאשר יש רק יישום אחד מוכר. "בטח בעתיד יהיו עוד – בואו נכין את הקרקע מעכשיו" – זו לא גישה אג'ילית.
  • Refactoring to Patterns – במידה והגעתם לקוד שיש בו ריחות (code smells) המצביעים על בעיה בקוד (שכפול קוד ברור, תלויות לא רצויות וכו')….
  • שקלו את דפוס העיצוב המתאים. זכרו שדפוסי עיצוב מגבילים לפעמים את גמישות הקוד בכיוון מסוים. לא על כל 5 שורות קוד כפולות – כדאי להשתמש ב State, למשל.
  • עשו Refactoring ושנו את הקוד כך שיישם את דפוס העיצוב שהחלטתם שהוא מוצדק – או גרסה דומה שלו. חשוב להבין "מה הקטע" של כל דפוס עיצוב – ולא סתם ליישם אותו "ע"פ הספר".
הספר הזה הוא כבר בן עשור. לו היה נכתב כיום – אני מניח שהמסרים שלו היו נראים קצת אחרת (יותר החלטיים).
עדיין – זה ספר שיכול לעזור

אפרופו: בהינתן ההבחנה ש"שפה משותפת" היא ערך עיקרי של דפוסי עיצוב ו"שימוש מופרז" היא הסכנה המרכזית – ניתן להצביע על Anti-Patterns ככלי שיכול להיות מצוין: Anti-Patterns הם תיאורים של דפוסים שליליים ולא-מומלצים מהם יש להימנע או לפחות להיזהר. אפשר לציין Anti-Patterns כגון "Gold Plating" (השקעה מופרזת באלמנט לא-חשוב), "Project Chicken" (אף אחד בפרויקט לא יכול לעמוד במשימה / לוחות הזמנים – אך כולם פוחדים להיות האדם הראשון שמודה בזה. לאחר שאחד הודה – כל השאר מצטרפים "אם הוא לא יעמוד ביעד – זה יתקע גם אותי"), "Death March" (עבודה על פרויקט ללא סיכוי הצלחה – אך מישהו "למעלה" מסרב להכיר במציאות הזו והפרויקט ממשיך) ועוד.

היופי ב Anti-Patterns הוא שאף אחד לא "ירוץ" ליישם אותם – זה לא מקור לגאווה. אין over-engineering.
החיסרון: שום מקור לא הצליח לייצר מומנטום מספיק גדול בכדי להפוך שפה כלשהי של Anti-Patterns לנפוצה מספיק בכדי שתהיה ידועה ומוסכמת בקרב קהל משמעותי [ד].

דפוסים כתהליך של התחדשות

[רחלי:] לפני שנים רבות השתתפתי בקורס שהעביר Jim Coplien בישראל (כמדומני כאורח של מכללת סלע) ובה הוא לימד איך כותבים דפוסים. בכל פרויקט תוכנה ניתן לזהות בעיות שחוזרות על עצמן ושיטות קבועות לפתרון של אותן בעיות. יש שיכנו זאת "הארכיטקטורה של המערכת". בחלק מן המקרים השיטות האלה מוכיחות את עצמן כנכונות וטובות, בהקשר של הפרויקט המדובר, ואז יש ערך רב לפרמל אותן ולתת להן שם. כדאי לתאר את הבעיה החוזרת, את ההקשר של השימוש בדפוס-העיצוב ואת הפתרון המתאים. חשוב לספק הנחיות מתי כדאי להשתמש בתבנית העיצוב – ומתי לא.

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

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

Proxy ו Observer אולי הפכו לחלק משפת JavaScript (בצורת bind ו object.observe, בהתאמה) אבל עם התפתחות השפה והאתגרים שלה נוצרו גם דפוסים המתאימים להתמודדות עם האתגרים החדשים (לדוגמה: Module).

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

סיכום

  • היללנו את דפוסי העיצוב.
  • השמצנו וביקרנו את דפוסי העיצוב.
  • לקראת הסוף: ניסינו לספק תמונה רחבה ומאוזנת.

כדאי ללמוד על דפוסי-עיצוב כשפה, תוך כדי שימוש בדוגמאות עדכניות ורלוונטיות. למשל:
במקום ללמד על Proxy על ידי ניתוח ה UML שהובא בספר של GOF ועיקרו היה מימוש פרוקסי ב ++C, אפשר להתעכב על הרעיונות שפרוקסי מייצג, ואז לראות אותם ממומשים בשפות השונות (proxy$ בספריית jQuery או bind בג'אווהסקרפיט. Dynamic Proxy בג'אווה וכו').

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

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

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

—-

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

דפוסי עיצוב של חווית משתמש (בעברית) וגם פרק בפודקאסט

—-

[א] איש מדעי המחשב שעסק רבות ב concurrency והספריה שכתב כהשלמה לג'אווה – נכנסה מאוחר יותר לסט הספריות הסטנדרטיות.

[ב] מאוחר יותר היה שותף לכתיבת הספרייה JUnit והוביל את התכנון של סביבת הפיתוח Eclipse.

[ג] "All problems in computer science can be solved by another level of indirection", שפעם נחשב לאמת מקובלת. רפרנס: http://www.dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html

[ד] והיו כמה ניסיונות, למשל הספר AntiPatterns או הספר Adrenaline Junkies and Template Zombies.

[ה] על עבודתו של אלכסנדר בתחום הארכיטקטורה אפשר לספר הרבה. הנה שני מקורות מעניינים מאד בעברית:
פרוייקט תרגום שיתופי לעברית של הדפוסים של אלכסנדר, והבלוג של יודן רופא, שהיה תלמידו של אלכסנדר
מי שקורא את מה שנכתב על עבודתו מגלה תאוריה עמוקה הרבה יותר מ"רשימת מכולת של דפוסים" וכוללת אלמנטים פילוסופיים כמו גם שיטות מעשיות להוצאה לפועל של התאוריות האלה. אגב, שיתוף משתמשים ועבודה איטרטיבית – שמוכרים היום כחלק מעקרונות האג'יליים בהנדסת תוכנה, היו גם חלק מהותי בשיטת העבודה של אלכסנדר, וניתן לקרוא עליהם בשני ספריו האחרים The Oregon experiment ו-  A Timeless way of building.

צינורות ומסננים

בפוסט זה אני רוצה לדבר על Pipes And Filters (בעברית: "צינורות ומסננים", בקיצור: PAF).אז מה זה PAF?

  1. האם זהו סגנון ארכיטקטוני?
  2. אולי Design Pattern?
  3. אולי סתם רעיון?

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

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

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

צינורות ומסננים – כמטאפורה

כאשר יש תהליך לעיבוד חומר (כימיקלים, למשל) ישנו תהליך קווי של פעולות ההופכות כמה חומרי גלם X1,…,Xn לחומר רצוי אחר Z. בד"כ פס ייצור שכזה בתעשייה הכימית יראה כקו או "עץ לוגי" עם מספר מיכלים (מערבלים, ראקטורים, מסננים וכו') המחוברים בצינורות. במיכלים מתבצע שינוי בחומר או חיבור של כמה חומרים שונים. הצינורות נועדו בכדי להעביר את החומרים משלב לשלב בצורה יעילה.

מבנה צורני (="ארכיטקטורה") זה יכול לתאר גם תהליך חישובי:

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

"צינורות ומסננים" כתהליך פיתוח תוכנה

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

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

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

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

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

"צינורות ומסננים" כארכיטקטורה / סגנון ארכיטקטוני

PAF הוא גם תיאור של מבנה של תוכנה. מבנה זה יתאים ל:

  • "מנוע חישובי" – בו יש הרבה פעולות לוגיות של חישוב ומעט / ללא אינטגרציה למערכות אחרות או I/O.
  • חישוב מסובך אותו נרצה "לפרק" ליחידות קטנות ("Divide and Conquer") בכדי לפשט.
  • חישוב נתונים ב stream – כאשר המידע מגיע בהדרגה או גדול מדי בכדי להכיל בזכרון.
  • מערכת בה נרצה לעשות שינויים בדרכי עיבוד הנתונים, על בסיס הפונקציות שכבר קיימות ("חיווט מחדש") או ע"י הוספת פונקציות.

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

גמישות לשינויים בצורת החישוב + עלות תחזוקה נמוכה (לשינויי "חיווט")  >  ביצועים 
ארכיטקטורת PAF היא בד"כ איננה הדרך היעילה ביותר (מבחינת ניצלות משאבים) לבצע את החישוב: יש העתקות רבות בזיכרון, cache trashing של זיכרונות המטמון במעבד, לפעמים context switch מרובים וכו'. במעבדים המוכרים לנו, עיבוד נתונים ב "batch" (ליתר דיוק batch לכל core של המעבד) הוא לרוב יעיל יותר.

הנה class diagram המתאר את PAF בצורה פורמלית:

והנה object diagram שאולי מעט יותר מוחשי לצורך ההבנה:

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

  • מהם ה Filters, האם אלו threads בלולאה? האם הם עושים pull מה pipe שמזין אותם או שה pipe עושה push ואולי מספק את ה thread?
  • אולי בכלל מדובר על event loop יחיד לכל המערכת ללא threads?
  • מהם ה pipes? האם הם streams בזיכרון, או אולי פשוט channel של events?
  • כיצד מתמודדים עם concurrency של Filters אקטיביים. כיצד עושים זאת ללא תקורה משמעותית?
  • הגדרת הממשקים בין ה pipes וה filters (במיוחד כאשר רוצים מערכת שניתנת לשינויים בקלות / מאפשרת שימוש חוזר ב filters)
  • כיצד מטפלים ב error handling / מתאוששים מנתונים לא תקניים (מכיוון שהמערכת מחולקת ליחידות בלתי-תלויות – עניין זה הופך למורכב יותר)
  • ועוד

דוגמאות מעשיות לשימוש בארכיטקטורת "צינורות ומסננים"

ב UNIX / לינוקס משתמשים ב PAF באופן מובנה, בעבודה השוטפת ב Console קרי Bash (או וריאציות דומות).
למשל פקודה בסיסית כמו:

find /var -name "*log*" | grep log

היא בעצם יישום של רעיון ה Pipes and Filters, כאשר התוכנות find ו grep הן המסננים ויש סט של צינורות (pipes) מובנים ב shell.המסננים ביוניקס הם מן הסתם אקטיביים (נוצר תהליך OS process) עבור כל תוכנה ברצף ה PAF. יש מספר pipes זמינים כאשר כל אחר מתנהג מעט אחרת ( | = מעביר עותק כקלט, < = שכתוב, << = הוספה, וכו'). הממשק בין pipe ל filter היא stream של נתונים. ה datasync הוא בד"כ קובץ או פלט על המסך.

Pipes and Filters כפי שמומשו ב Unix. מקור: Ariel Ortiz Ramírez

אפשר לציין גם את כלי ה build שנקרא Gulp, שבנוי בארכיטקטורה של PAF: תהליך ה build הוא בדיוק כזה שאנו רוצים לשנות בקלות ע"י "תפירה מחדש" של ה pipes. את הכתיבה לדיסק (temp files, compilations – למשל כמו שמתרחשת ב Maven או Grunt) מחליפים ב pipes בזיכרון כך ש:

  • לא כותבים לדיסק –> תהליך ה build מהיר יותר.
  • הימנעות מקבצי ה build הזמניים –> אין מה "לנקות" / אין צורך בשלב "clean" = פחות תקלות והתעסקויות.
היתרון של Gulp. מקור: http://slides.com/contra/gulp
דוגמה אחרונה, ומאוד מייצגת היא מערכת / Framework בשם Apache Storm (לשעבר של טוויטר) לביצוע חישובים / ניתוח נתונים בזמן אמת (realtime). האמת, שזה לא ממש realtime (גם אם כמה עשרות ms – זה עדיין לא "realtime") ואני מעדיף לקרוא לה "מערכת לניתוח נתונים ב latency נמוך".

Storm היא סוג של התממשות הסגנון הארכיטקטוני של PAF, ומעט יותר. הוא בעצם מאפשר, בצורה מאוד קלה ומהירה להרים רשת (בטרמינולוגיה של Storm – "טופולוגיה") של pipes and filters ולהריץ אותה מול data sources שונים (מימושים שמגיעים OOTB יכולים להתחבר ל Kerstel, JMS, Redis pubsub וכו'), בעזרת מה שנקרא Spouts (תרגום חופשי: "פיה של ברז") – סוג של Adapter למקור המידע.

אמנם החלוקה ל Pipe ו Filter היא לא בדיוק ע"פ ההגדרה הקלאסית: ה Filter ב Storm (נקרא bolt – בורג) הוא זה שבעצם מגדיר את ה Stream Grouping, להיכן וכיצד להעביר את הפלט שלו (שזה סוג של Pipe). האמת שחיפשתי בגוגל אחר Apache Storm בהקשר של Pipes and Filters – ולא מצאתי שומדבר. האם אני היחידי שרואה את הקשר החזק?!

הממשק של Storm הוא של tuples – רשימות של ערכים (דומה ל HashMap, מפתחות וערכים) אותן מעבדים. כל bolt יכול להמיר את ה tuples, לפלטר אותם (להעביר הלאה רק חלק) או לבצע עליהן סיכומים (למשל: ספירה או ממוצע של הערכים שהוא ראה).

בניגוד ל Bash, למשל, שם ה PAF הוא מקומי וקטן מימדים, טופולוגיה של Storm יכולה להכיל הרבה מאוד nodes, על גבי cluster, שיעסיק הרבה מאוד CPU cores. חלק לא קטן מההתעסקות ב Storm היא התאמה של הטופולוגיה ל cluster שכזה ויישום אופטימיזציות ביצועים / חלוקת משאבים / ניטור ופתרון בעיות וכו'. Storm גם מתמודדת עם נושאים של Fault Tolerance, שאינם חלק מ PAF (דומה בהיבט זה מעט יותר ל Hadoop). שלושת התסריטים הנפוצים לפתור בעזרת Storm הם:

  • Stream Processing – חישוב מתמשך של נתונים המגיעים מ (Stream(s. אולי התסריט הקלאסי ביותר.
  • תסריט של Distributed RPC – לחלוקת עבודה בין מחשבים שונים.
  • Continuous Computation (חישוב מתמשך) – למשל חישוב טרנד שכל רגע משנה כיוון ועצמה, על בסיס stream של נתונים.
אילוסטרציה של טופולוגיה (פשוטה) ב Storm. הקוביות הצהובות הם ה tuples על ה streams. בצד ימין יהיו ה data sync, פעמים רבות יהיה זה  cassandra, או בסיס נתונים דומה.

סיכום

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

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

תבנית עיצוב: Null Object

לא כתבתי הרבה בזמן האחרון – ככה זה שמשפצים דירה…
בכל זאת, אנסה לכתוב פוסט קצר על תבנית עיצוב בשם Null Object.יש לנו מסורת ב SAP, להעביר פעם בשנה קורס "תבניות עיצוב" (Design Patterns) לעובדים החדשים. חלקם מכירים את הנושא – אבל תמיד יש מה לשפר. אני כותב פוסט זה תוך כדי הכנה של החלק שלי בקורס.
האמת? יש לי הסתייגות פנימית קלה מנושא תבניות העיצוב: תבניות עיצוב הוא תחום שעלול לגרום ליותר נזק מתועלת – כאשר מנסים "לדחוף כמה שיותר תבניות עיצוב במטר מרובע", וזה יכול לקרות הרבה.
מצד שני, כשמתבגרים ונרגעים ניתן להשתמש בהן בצורה מאוזנת ומועילה. חוץ מזה: זה נושא שאנשים רוצים לדעת היטב – ומי אני שאמנע מהם? האם אני הייתי מוכן לוותר על להכיר את הנושא? בטח שלא!

בקורס, אנו מנסים להדגיש את הנקודות החשובות הבאות:

  • תבניות עיצוב כשפה רק עבור התקשורת הפשוטה, כדאי ללמוד תבניות עיצוב: "ה class הזה הוא בעצם כמו Strategy" יכול להגיד המון למי שמכיר את ה Pattern או לגרור דיון של כמה דקות – למי שלא.
  • תבניות עיצוב כתיקון למצב בעייתי, או מה שנקרא Refactoring To Patterns: כדאי ומומלץ להימנע משימוש בתבניות עצוב במחשבה על "מצב עתידי", קרי "יהיה יותר קל לטפל גם במספר רב של אובייקטים אם נשתמש כאן ב Composite". חכו שיהיה מצב בו יש מספר רב של אובייקטים – ורק אז עשו Refactoring ל Composite.
    ההמלצה היא לזהות צורך מיידי ש Pattern יכול לפתור, או לסירוגין smell בקוד שנובע מאי-שימוש ב Pattern ורק אז לעשות refactoring ולהכניס את ה Pattern.
אם "smell" נשמע עבורכם סינית – מדובר על code smells, שהם חלק מהשפה של טכניקת ה refactoring.

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

קצת על null בג'אווהסקריפט

ג'אווהסקריפט הופכת לשפה משמעותית עבורנו – ולכן הדוגמאות שאספק יהיו בעיקר מעולם זה. לא מעניין אתכם javaScript או ה nulls שלה – דלגו ל section הבא.

למה "nulls" ולא "null"? כי יש כמה כאלו בג'אווהסקריפט. תלוי את מי שואלים, יש שניים או שלושה, אני אציג את נקודת המבט המחמירה:

  • יש ערך "null", שמשמעותו היא "נקבע למשתנה זה ערך ריק / חסר משמעות". בניגוד לג'אווה זהו איננו ערך ברירת המחדל למשתנים.
  • יש ערך "undefined" ששמו דיי מבלבל: נשמע שהוא מתאר משתנים שלא הוגדרו – אך בעצם הוא נקבע כערך ברירת מחדל למשתנים שהוגדרו אך לא נקבע להם ערך. שם מוצלח יותר היה יכול להיות unassigned.
  • יש מצב בו לא הוגדר משתנה. אפשר לקרוא לו "בום!": ניסיתם לגשת למשתנה, אפילו בזהירות? – ייזרק error. שם שהייתי מעדיף לכזה משתנה הוא undefined, אבל השם כבר תפוס. זה לא בדיוק null value, אבל הוא דורש התגוננות דומה (ונוספת).
בואו נראה דוגמת קוד:
אפשר לראות שהבדיקה שאלו 2 ערכים שונים בעליל ולא תת מקרה אחד של השני.
אני לא מתעסק בשוויון ללא בדיקת טיפוס (==), מכיוון שהיא לא הדבר הכי צפוי בג'אווהסקריפט (אבל גם לא הדבר הכי לא-צפוי):
כלומר: כדי לא לחטוף "NullPointerException" וגם / או "UndefinedPointerException" (בקריצה לג'אווה) האם עלינו לבדוק כל משתנה שאנו מקבלים – פעמיים בפני אפשרות ל null?
לשמחתנו, אנו יכולים לנצל את "הגמישות" בבדיקת הטיפוסים בג'אווהסקריפט מדי פעם כדי לבדוק את שני ערכי ה null בפעם אחת. התחביר המועדף עלי הוא שימוש ב ! (או !!, כאשר השמת הערך כפי שהוא לא אפשרית / ברורה):
נראה עקבי.
אבל… מה קורה עם משתנה z שלא הגדרתי (כלומר הוא: truly undefined)?
בואו ננסה:
אולי סוג אחר של הגנה?
אאוץ! גם לא.
ה errors שאתם רואים הם לא "ערך החזרה רגיל של פונקציה", הם פשוט יעיפו לכם את התוכנית – אם תנסו לבדוק האם ערך כלשהו, שלא הוגדר, הוא בעצם null. מה עושים? הנה הפתרון:
אוי ואבוי? האם צריך לעשות בדיקה כזו בכל מקום בקוד?
בכלל לא… ארגומנט של פונקציה תמיד יהיה מוגדר, לפחות undefined.
יש לעשות בדיקה מקיפה זו כל פעם שניגשים (פעם ראשונה?!) למשתנה במרחב הגלובלי.
הערה: גישה למשתנה גלובאלי בצורה  .window היא דווקא בטוחה בפני ReferenceError. תודה לקורא שתיקן אותי.
אז האם יש 2 או 3 ערכי null בג'אווהסקריפט?
  • אולי אלו 2: null ו undefined
  • אולי אלו 2: null/undefined ו truly undefined
  • אולי אלו 3.

תלוי את מי שואלים….

מה הבעיה עם null?

ראשית, ש"אבא" שלו – פשוט לא אוהב אותו.
טוני אוהר'ה, מי שהמציא את הקונספט בשפת ALGOL איפשהו בשנות השישים, אומר שזו פשוט הייתה עצלנות. "לא היה לי זמן למצוא פתרון יותר אלגנטי באותה התקופה". הוא קורא להחלטה ליצור את null טעות, ולא סתם טעות: "טעות מיליארד הדולר שלי" (טריליון דולר – בתרגום למונחים ראליים של היום). איזה נזק לתעשייה כולה! ומאז ששפת C החליטה לאמץ את הרעיון – אבדה הדרך חזרה…
מה הבעיה עם null?
איזה Exception מעיף לכם את התוכנה, בשפה ה strongly types עם הקומפיילר ההדוק שלה (קרי: Java), הכי הרבה פעמים?
רגע… תנו לי לנחש (לתקשר) … האם זה אולי NullPointerException?
כשאנו מפעילים פונקציה (/מתודה) ורשום לנו שהפונקציה מחזירה אובייקט מסוג ListItem, בעצם זה נכון נכון: היא מחזירה אובייקט מסוג ListItem או null.
כשרשום שהפונקציה מחזירה אובייקט מסוג List, גם זה לא נכון! היא מחזירה אובייקט מסוג List או null.
ב List אנחנו זוכרים לטפל: זהו ה common scenario – מה שצפוי, רצוי וקל לחשוב עליו.
את ה null אנחנו מפספסים הרבה יותר. והיכן אנו מגלים את הבעיות הללו? בתוך ה IDE? – לא כ"כ. אולי בזמן בדיקות, אולי בשרת האינטגרציה… אולי ב production … ואולי אצל הלקוח.
אם כ"כ הרבה מפתחים שוכחים אותו דבר – כל כך הרבה פעמים, אולי הבעיה היא בשפה / כלים ולא במתכנתים?!
יתרה מכך, אפילו אם היה לנו קומפיילר שמתריע על כל מקרה שבו שכחנו לטפל ב null (ואין כזה – למיטב ידיעתי), עדיין הקוד היה הרבה יותר מסובך. לפחות עליה ברמה אתה של Cyclomatic complexity – ע"פ הגדרה: תמיד יש עוד מקרה אחד, עוד הסתעפות אחת של התרחשות שיש לטפל בה. בכל פונקציה.
הנה דוגמה מקוד של jQuery, ספרייה שעפו בה המון referenceErrors לאורך פיתוחה. באופן כמעט אקראי בחרתי בפונקציה add שרושמת אירוע על אלמנט בדום בעקבות קריאה ל (…)on.$. היא לא מקבלת את הפרמטרים כמו שהם – פונקציית on עושה המון בדיקות קלט, כולל nulls למיניהם. שימו לב עדיין מה יש בה:
סימנתי בצהוב כל מיני בדיקות null למיניהן שעדיין יש לעשות, למרות ההגנה של פונקציית on.
מה זה?? האם jQuery הוסיפה טיפוס null משלה בשם "strundefined"?
לא לדאוג. זהו פשוט קיצור של "typeof undefined" – כנראה בגלל שיש הרבה בדיקות כאלו.
בקיצור: null מחרבן לנו את הקוד והופך אותו מסובך יותר לבדיקה. הוא עוזר, כנראה, לכתוב גרסה ראשונה – אבל בדרך ייצוב המערכת לגרסה 5 אנחנו נאכל איתו עוד הרבה קש…
"מראה מראה שעל הקיר, מי השפה עם ה Null המזיק-פחות, שאותו יש להוקיר?" מקור: הבלוג הטכני של לוסידצ'ארטס https://www.lucidchart.com/techblog/

מה עושים?

וואהו! הצלחתי למלא את הפוסט דיי מהר, ובלי לשים לב. ככה זה ארכיטקטים: דברנים!
ישנן שפות מעטות (ואמיצות!) שהחליטו להיפטר מ null. אני שמעתי על Cyclone (דיי נדירה) ועל Haskell [א].
הייתי פעם בהרצאה של מישהו שהציע תחביר בו מצב ברירת המחדל הוא שאובייקט לא יכול להיות null. אם פונקציה יכולה להחזיר null, חובה עליה להכריז על ערך ההחזרה עם prefix של סימן-שאלה (כמו nullable object ב #C). למשל:
public ?ListItem getChild(….);
או
private ?List calcItems(…);
אני מוצא את הפתרון הזה מאוד אלגנטי – אך אינני מכיר שפה שאמצה אותו.
פתרון אחר הוא Design Pattern בשם Null Object שאומר: במקום להחזיר null, עם אופציה ברורה לשבור את הקוד במקומות אחרים שלא מוכנים לכך – החזר אובייקט ("Null Object") שיתאר מצב null-י, מבלי לחרב את המערכת. על האובייקט הזה לדמות בצורה הטובה ביותר שניתן (אך סטנדרטית) מצב של "לא ידוע" / "אין".
הנה דוגמה (שקצת יותר דומה לג'אווה דווקא, לא רוצה להתעסק עכשיו עם תיאור מחלקות בג'אווהסקריפט):
class Employee {
 
  public final static NULL_EMPLOYEE = new Person(", [], 0, ");
 
  Employee (String name, String[] expertise, int age, String desc) {
        …
  }
}
הרעיון הוא שבמקום להחזיר null, אני אחזיר את Employee.NULL_EMPLOYEE, שתאפשר לקוד להמשיך לרוץ – מבלי לשבור אותו.
אם יש לולאה שרצה על מערך מומחיויות העובד – ריצה על מערך ריק (שמגיע מה null object) לא תשבור אותו, אין בעיה. הקוד ימשיך בד"כ gracefully. פה ושם יהיו מסכים מוזרים שמדברים על עובד ששמו "" – אך זה הרבה יותר טוב מהודעת שגיאה לא ברורה, והמתנה ל patch הקרוב.
את תבנית העיצוב של Null Object מתארים באופן פורמאלי בצורה הבאה:
אפשר בהחלט לא להשתמש ב Null Object אחד בעל משמעות כללית, אלא לייצר כמה, עם משמעות סמנטית עשירה יותר, ועם התנהגויות שמתארות בצורה טובה יותר את ה "null behavior" (או "do nothing") של אותו האובייקט. למשל במערכת העובדים:
פתרון אחר, אולי "פתרון חצי-דרך", הוא בשלב מוקדם ככל האפשר – להחליף ערכי null בערכים סבירים אחרים – עבור שאר ה flow. זכרו שאם בדקתם בפני null נקודתית אז פתרתם את הבעיה של היום, אבל לא את התיקון / תוספת של "עוד-שעה". פתרון זה (שאני קורא לו "inline null object") הוא לא אידאלי, אבל מהיר וקצת יותר ארוך טווח מטיפול נקודתי ב null. כמו פלסטר עם דבק חזק במיוחד.
הוא נראה כך (בכתיבה המקוצרת שלו בג'אווהסקריפט):
 
init: function(options) {
    this.user = options.user | { name: “unkown”, items: []};
},

עדכון יוני 2014:

אפל הודיעה על שפת Swift, שתחליף עם הזמן את Objective-C לפיתוח OS X/iOS ומה שאומר שצפוי לה שימוש נרחב. מה הקשר? ב Swift משתנים לא יכולים להיות nil אלא אם הוגדרו כ Option Type (מה שנקרא גם "Maybe") – שבתחביר השפה זהו סימן שאלה בסוף – למשל ?Int. פתרון מאוד אלגנטי לטעמי לעניין ה null!
התחביר דוגמה מאוד ל nullable types ב #C – תחביר המאפשר nulls ב primitives. כלומר – עשו שם רק את החצי הקל של העבודה…

סיכום

  • התרגלנו (בעצם: נולדנו) לעולם בו פונקציות עלולות להחזיר null בכל רגע, וללא התראה.
  • null יכול להפתיע בכל מקום בקוד – מה שהופך אותו לבעיית תחזוקה קשה.
  • NullObject יכול לעזור.
    • לצמצם הפתעות.
    • להוסיף סמנטיקה עשירה יותר למצב ה null.
  • NullObject הוא לא מושלם: הוא דורש מעט יותר עבודה. מומלץ לשימוש באופן מקיף במערכת שמתקרבת לשלב התחזוקה (לא MVP).
הפוסט נכתב בזמן שיא (לפוסט מסוגו) של שעתיים וחצי בערך. סליחה מראש במידה ויש תקלות הגהה הגעה הגאההה.
שיהיה בהצלחה!
—-
[א] יש לה משהו שנקרא maybe שאמור לטפל בצורה פשוטה במקרים לא צפויים. אני לא יודע איך הוא בדיוק עובד.

התיאוריה המאוחדת: קוד, תכנון וארכיטקטורה

ניתן לומר שהגדרת ארכיטקטורה מורכבת מ 4 פעולות בסיסיות:

  • חלוקת המערכת למודולים / תתי מערכות
  • ניהול תלויות (בין המודולים)
  • יצירת הפשטות (Abstractions) והגדרת API
  • תיעוד הארכיטקטורה.
כמעט כל העקרונות והטכניקות של הגדרת ארכיטקטורה (למשל Quality Attributes או חלוקה ל Views) הן הנחיות כיצד לבצע פעולות בסיסיות אלו בצורה נכונה יותר."ניתוח Quality Attributes" היא טכניקה שכתבתי עליה בפוסט הזה והזה.

תכנון מונחה אובייקטים – OOD

תחום ה Object Oriented Design הוא תולדה של רעיונות שהתפרסמו במספר ספרים / מאמרים משפיעים – אך בניגוד למה שניתן לחשוב, אין הגדרה "חד-משמעית וברורה" מהם "עקרונות ה Object Oriented Design".
2 המודלים המקובלים ביותר להגדרת OOD כיום הם:

חוצמזה ניתן בהחלט להזכיר את תנועת ה Patterns (קרי POSA, PLOP, GOF) שלא ניסחה חוקים אלא "תיעדה" תבניות עיצוב מוצלחות – אבל יש לה השפעה ניכרת על הדרך בה אנו עושים היום Design (ולא תמיד לטובה).

העשור האחרון

זרם ה Agile השפיע גם הוא רבות על OOD וקידם כמה רעיונות:
  • "כשאתה מקודד – אתה בעצם עושה Design" (מקור: TDD) –> ומכאן רעיונות כמו "Design by Tests/Coding"
  • ההכרה שביצוע Design או הגדרת ארכיטקטורה הם Waste – שיש לנסות ולייעל אותם ("Just Enough Software Architecture")
  • ההבנה שחיזוי העתיד הוא דבר בלתי-מציאותי, גם על ידי אנשים נבונים למדי, במיוחד במוצרים חדשים אך גם במוצרים קיימים. ירידת קרנם של "העיקרון הפתוח-סגור" (מתוך SOLID) ו "(Predictable Variations (PVs" (מתוך GRASP) והצבת סימני שאלה בפני כמה מהעקרונות האחרים…

התאוריה המאוחדת

בכל מקרה ישנה השאלה: בהינתן עקרונות ל"ארכיטקטורה", "תכנון" ו"כתיבת קוד" – היכן בדיוק עוברים הגבולות הללו? מתי יש להשתמש בעקרון ארכיטקטוני ומתי בטכניקת Design?

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

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

ניסיתי למפות את "ארבע הפעולות הבסיסיות של הגדרת ארכיטקטורה" לפעולות תכנון וכתיבת קוד:

אפשר לראות שמות שונים ("גבוהים" ו"נמוכים") לרעיונות דומים – אבל ההקבלה הרעיונית יפה למדי.

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

הנה שתי דוגמאות:

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

עקרון ארכיטקטוני: Interface Segregation Principle 
עקרון זה אומר שמודול לא אמור להיות תלוי ב interfaces רבים שאינם בשימוש. אם נוצר מצב כזה – יש לפצל את ה Interfaces כך שהמודול יהיה תלוי, עד כמה שאפשר, רק ב Interfaces שהוא משתמש בהם בפועל. העיקרון נכון מאוד גם למתודות בתוך interface יחיד (רמת ה Design) או לפרמטרים בחתימה של פונקציה בתוך הקוד (רמת הקוד).

עוד היבט קוד שאני מאמץ מעקרון ה ISP הוא לנסות ולהימנע משימוש בספריות (כגון "Open Source") שיש לי מעט שימוש בהן. אני אשתדל לא להכליל במערכת ספרייה של אלף שורות קוד – אם אני משתמש רק ב 50 מהן. אני מעדיף למצוא ספרייה אחרת או אפילו לכתוב אותן שורות קוד לבד. מדוע? א. סיכוי לבעיות מ 950 שורות קוד לא רלוונטיות, ב. מסר לא ברור האם נכון "להשתדל" להשתמש במתודות אחרות בספריה או לא. ג. אם צריך לשנות / לדבג – ייתכן וצריך להבין הרבה קוד בדרך שלא רלוונטי למקרה שלנו.

אפשר להראות כמה דוגמאות של עקרונות ש"עוברים פחות נקי":

  • מקבילה ל"ניהול תלויות" ברמת הקוד – לא מצאתי בדיוק. הסתפקתי בעקרון של הימנעות ממשתנים גלובליים.
  • לרעיון של חלוקת מודולים ע"פ "Unit Of Work" (כך שקבוצות פיתוח שונות יוכלו לעבוד במקביל עם מינימום תלות) – אני לא חושב שיש הקבלה אמיתית ברמת קוד.
  • העיקרון האלמותי של (DRY (Do Not Repeat Yourself הוא "No Brainer" בקוד, אבל הופך לנושא מורכב ולא חד-משמעי ברמת הארכיטקטורה.

בסופו של דבר – יש הרבה מאוד חפיפה בין עקרונות "ארכיטקטורה" לעקרונות "קוד", כך שאין כ"כ חדש ללמוד. הרעיונות חוזרים על עצמם בשינוי אדרת. חלק מהעקרונות (למשל KISS = Keep It Simple Stupid) הם פשוט אוניברסליים.

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

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

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

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

קושי מסוים בשימוש ב SLAP הוא שאין "מד רמת-הפשטה", כזה שנכוון אותו לשורה בקוד והוא יגיד לנו "רמה 6!" או "רמה 7!". זה הכל בראש שלנו כמפתחים ובני-אדם אינטליגנטים. לפעמים יהיו "סתירות" כך שיוחלט שפעולה X תהיה פעם אחת ברמה n ופעם אחרת ברמה n+1. אני אומר: זה אנושי. זהו עקרון חשוב – פשוט קחו אותו בפרופורציה ("We figured they were more actual guidelines").

סיכום

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

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

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

עשה זאת בעצמך: NoSQL

מעוניינים לשדרג משהו? לעתים זול יותר ופשוט יותר לבצע שיפוץ קטן בעצמכם, במקום להשתמש בבעל מקצוע. ייתכן והתוצאה תהיה מוצלחת לא-פחות.
בפוסט זה אני רוצה לשתף במימוש מוצלח של "טכניקת NoSQL BIG DATA" שביצענו על גבי מערכת קיימת, מבלי לשנות אותה באופן מהותי ומבלי להחליף את בסיס הנתונים הרלציוני הקיים.המסר המעניין מבחינתי, הוא שניתן ליישם ״בקטנה״ רעיונות של בסיסי הנתונים NoSql בעצמכם – ולהשיג תוצאות יפות.

הבעיה

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

בדיקות שעשינו למערכת הראו שבסביבות 30-אלף פרויקטים, המערכת מתחילה להראות סימנים של שבירת הלינאריות ב scalability. כלומר: עד נקודה זו – אם רצו לנהל עוד פרויקטים היה ניתן להוסיף עוד חומרה ביחס ישר לגדילה בכמות הפרויקטים / הפעילות. מעבר לנקודה זו היה צריך להוסיף x וקצת חומרה ל x פעילות נוספת, וככל שהמספר גדל – העלות השולית הלכה וגדלה.
הבנו שהמערכת תוכל לטפל במשהו כמו 50 אלף עד 100 אלף פרויקטים, תלוי בכמות החומרה שהלקוח יסכים להקצות. באופן מעשי זהו בערך גבול ה Scalability שלנו ולכן ההמלצה ללקוחות הייתה לא ליצור מעל 50 אלף פרויקטים.חשוב להבהיר שמדובר במערכת בת כ3 שנים – שעברה לאורך חייה לא מעט שיפורי performance ו scalability. בשלבים הראשונים של המערכת הצלחנו לבצע שיפור יחיד שהגדיל את ה Scalability ב 30% – אך ככל שהזמן עבר שיפרנו אלמנטים פחות משמעותיים (כיוון שהמשמעותיים כבר שופרו) והבנו שאנו מגיעים לקצה ה Scalability של הארכיטקטורה הקיימת.

פתרון אפשרי אחד היה לנסות לעבור לבסיס נתונים NoSQL, נוסח MongoDB או CouchDB – המתאימים יותר לשימוש הספציפי של המערכת, והיו יכולים בהחלט לשפר את המצב. הבעיה: שאר האלמנטים במערכת (מלבד הפרויקטים) התנהלו בצורה משביעת-רצון בבסיס הנתונים הרלציוני. מה עושים? עושים הסבה לכל הקוד לעבוד מול בסיס נתונים NoSql או דורשים מלקוחות לנהל 2 בסיסי-נתונים שונים במקביל?!

התוצאות

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

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

מקור הבעיה

ישנן סיבות שונות המצדיקות מעהר לNoSql Databases:

  • הרצון בסכמה גמישה, שלא דורשת migration בין גרסאות.
  • כמות נתונים (ב TB) שדורשת מעבר משרת אחת לכמה שרתים – מה שנקרא Scale Out.
  • בעיית Scalability מקומית. כלומר: מעל כמות נתונים מסוימת, זמן התגובה למשתמש הקצה הופך ללא-סביר.
    זו הבעיה איתה התמודדנו במערכת שלנו.

איך נוצרת בעיית Scalability?
בואו נביט על (הפשטה של) סכמת הנתונים של מערכת הפרויקטים:

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

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

בבסיס נתונים רלציוני ניתן לשמור תכונות כאלו ב-2 אופנים:

  • סכמה קשיחה: כעמודה (column) בטבלה. על כל תכונה אפשרית יוצרים עמודה חדשה.
    יתרונות: פשטות
    חסרונות: יש תכונות (כגון "deleted by admin") שמתרחשות לעתים נדירות – אך עדיין יש לשמור עבורן מקום בכל רשומה, הוספת שורה = הוספת סכמה.
  • סכמה גמישה: כטבלה נוספת, בתצורת master-detail, בה כל מפתח וערך של תכונה היא שורה נוספת.
    יתרונות: גמישות רבה ללא שינויי סכמה
    חסרונות: עוד טבלה לנהל, עוד קצת סיבוך.

המערכת הנ"ל השתמשה בסכמה גמישה.

נתונים לדוגמה, בסכמה גמישה של תכונות אובייקט (לחצו להגדלה)

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

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

חזרה לתיאוריה

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

הנה טבלה חשובה למדי:

מקור: גוגל

שתי נקודות שהייתי רוצה להדגיש:

  • קריאה מדיסק היא הפעולה היקרה ביותר ברשימה (נו, טוב – מלבד WAN בין-יבשתי), והיא יקרה משמעותית מפעולות מבוססות זיכרון (פי 100 עד פי 100-אלף, תלוי בתסריט).
    אם אנו מתבוננים על שכבת ה Persistence כקופסה שחורה, אזי כדאי לנו מאוד להפחית קריאות לדיסק, גם על חשבון הרבה פעולות בזיכרון. כלומר: להבין מתי בסיס הנתונים או שכבת ה ORM גורמות לקריאות לדיסק להתרחש – ולגרום לקריאות אלו לפחות ככל האפשר.
  • בניגוד לזיכרון, בו יש פער גדול בין גישה כלשהי (100ns) לקריאת 1MB של נתונים (250K ns, פי 2500), בקריאה מדיסק הפער הוא רק פי – 2. כלומר: להביא 1MB של נתונים רציפים לוקח כמו הבאה של 2 חתיכות של 4k.
    הסיבה לפער זה הוא שזמן גישה (seek time) בדיסק כוללת תנועה של זרועה מכנית וסיבוב הדיסק לנקודה הנכונה, משם קריאה רציפה היא כבר "לא סיפור".
    הערה: מגבלה זו השתפרה מאוד עם הצגת כונני ה SSD המודרניים. ניתן לקרוא עוד בנושא בפוסט מבט מפוכח על מהפכת ה SSD. עדיין, קריאה מדיסק ובמיוחד קריאה בלתי-רציפה, היא יקרה למדי. בפוסט הנ"ל ניתן לראות כונן SSD שקורא 180MB בשנייה באופן רציף, אך רק 18MB בשנייה כאשר המידע מפוזר. יחס קצב העברת-נתונים של פי 10-15 בין קריאה רציפה לקריאה אקראית הוא מאפיין שכיח בכונני SSD מודרניים. יחס זה הוא בערך פי 100-200 בכוננים קלאסיים – כך שמדובר בשיפור גדול.
כיצד נגרום לבסיס הנתונים לבצע משמעותית פחות קריאות לדיסק, מבלי לגעת בקוד של ה ORM או של בסיס הנתונים? כיצד נוכל לעשות זאת מבלי לשנות דרמטית את כל המערכת שלנו? התשובה בפסקה הבאה.

Aggregate-Based Data Storage

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

כשטוענים פרויקט:

  • נטענת רשומה מטבלת הפרויקטים
  • נטענת רשומה אחת מטבלת ה Topic (נושא אחד נפתח ב default)
  • נטענות כל רשומות הדיון מאותו ה topic (כדי להציג רשימת שמות) וכל הרשומות מהדיון הראשון, כולל כל ה comments וה attributes שלהם.

זה המידע שנדרש על מנת לספק את חווית השימוש הרצויה.

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

  • לפחות קריאה אחת עבור כל טבלה.
  • בעצם, יש אינדקסים שבהם ייתכן ויש להיעזר – כך שבפועל ייתכנו מספר קריאות לכל טבלה.
  • המידע מגיע מהדיסק בבלוקים של 4k או 16k. אם הרשומות בטבלה אינן "קרובות דיין" על מנת להיכנס לבלוק של 16k נתונים – ניאלץ "לדלג" (seek) שוב בתוך הטבלה.
    רשומות מסוג ה Comment יכולות להיכתב בהפרש זמנים ניכר אחת מהשנייה, שכן תגובות יכולות להגיע לאחר שבוע או חודש.
    רשומות מסוג ה Comment Attribute יכולות להיכתב בהפרש (כלומר, פיזור) נוסף, מאחר והן נוספות "רק ע"פ הצורך". לדוגמה: תכונת ה likesCount תיווצר רק בעת שנעשה ה Like הראשון ולא עם יצירת ה comment.
אין לי חישוב של מספר הפעולות בדיסק, אך יש לי בסיס להאמין שהוא יכול להסתיים בעשרות קריאות מהדיסק לכל discussion. השימוש ב ORM יכול להסתיר את העובדה שיצירת אובייקט "discussion" בזיכרון, רק בכדי לקחת את ה title ו lastUpdateDate – יכולה לגרום ליצירת אובייקטי ה comment והקריאה גם שלהן מהדיסק.
נקודה מעניינת, שמעצימה את הבעיה, היא שבמערכת עם הרבה מאוד פרויקטים ודיונים, הכמות הגבוהה של ה comments שמתווספים למערכת בשעה, יכולה לגרום לכך ש 2 תגובות במרחק של 5 דקות אחת מהשנייה – לא יהיו במרחק 16k בדיסק (מכיוון שנכתבו מאז הרבה comments אחרים ב discussions אחרים).
המצב הזה, בו המידע בדיסק מאורגן בניתוק מאופו השימוש בנתונים (קרי – ארצה לראות את כל ה comments מאותו דיון ביחד – ולא את כל ה comments שנכתבו בדקה מסוימת), הוא שורש הבעיה.

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

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

במקרים כאלו יש ייתרון ברור לאיגוד הנתונים ע"פ נקודת הייחוס המתאימה לשימוש הנפוץ במערכת, מה שנקרא document-based database או aggregate-database.

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

כיצד מממשים זאת?
פשוט מאוד: בוחרים נקודת ייחוס (למשל Topic, בכדי לצמצם מעט את גודל ה"קובץ" שנקרא בכל פעם), ומייצגים אותה ואת כל ההיררכיה של האובייקטים מתחתיה (למשל אובייקטי ה discussion) כרשומת JSON או XML אחת. את רשומה זו שומרים בבסיס הנתונים כ BLOB, כך שיהיה עדיין להינות משירותים של בסיס הנתונים (גיבוי, טרנזקציות וכו').

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

משמעויות נוספות
יש כמה משמעויות נוספות בגישה זו שכדאי להיות מודעים אליהן.

  • יש לממש לבד לוגיקה של קריאה / כתיבה של אובייקטים לתוך ה BLOB (מה שציינו למעלה).
  • אנו מאבדים את היכולת לעשות שאילתת SELECT על כל האובייקטים במערכת. למשל, למצוא את כל ה Comments שנכתבו בין 2 ל 4 בבוקר.
    אם אנו רוצים לבצע שאילתה שכזו – יהיה עלינו לקרוא את כל ה Topics מהדיסק, אחד אחרי השני, ולסרוק בעצמנו את המידע בתוך ה BLOB.
    אם אנו רוצים מהירות בסריקה (ויש לנו use-case ספציפי) אנו יכולים להשתמש במנועי indexing כגון Lucne.
  • השימוש בסכמה של "טקסט חופשי", כגון פורמט JSON, מאפשרת לנו לבצע שינויים לסכמת הנתונים בין הגרסאות של המוצר מבלי לבצע שינויים לסכמת בסיס הנתונים.
  • השימוש בסכמה של "טקסט חופשי" מאפשרת לנו לשלם על שדות "נדירים" רק כאשר משתמשים בהם (לדוגמה: isDeletedByAdmin) ועדיין להינות מביצועים נהדרים.

עדכון ספטמבר 2015:
הנה שני סיפורים דומים של חברות שבחרו ב"התאמה אישית" של בסיס נתונים רלציוני על פני מעבר ל NoSQL DB:

סיכום

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

כדי להינות מ BIG DATA, מספיק לעשות שינוי קצת שונה בבסיס הנתונים הרלציוני הקיים שלנו. ברור שבסיס נתונים NoSQL ייעודי יכול לתת יותר – אך לא תמיד הפער הזה מצדיק את המעבר.

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

אם אתם מעוניינים ללמוד קצת יותר על BIG DATA, אתם מוזמנים לקרוא את הפוסט מה הביג-דיל ב BIG DATA?