Site icon בלוג ארכיטקטורת תוכנה

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 Shortener בחברה שלנו, שלוקח URL ארוך, למשל https://softwarearchiblog.co.il/wp-admin/post.php?post=3658&action=edit ומקצר אותו ל URL קצקצר כמו https://short.com/xyz1234. איך היית מתכנן שירות כזה? בוא תתחיל/י לתאר בבקשה"

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

מה דעתם?

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

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

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

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

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

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

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

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

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

איטרציה שניה

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

בסיס נתונים רלציוני יכול לנהל 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? יש פה כמה אלטרנטיבות שעולות מיד:

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

שוב, אגב, אני מתבסס על ידע (הבנה כיצד פונקציות 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

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

משהו נשמע כאן מוזר? אי אפשר להעביר את רוב תווי ה 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. לאחר שנבין ונאפיין בעיה שקורת בפועל בשטח – נוכל להתאים לה פתרון:

סיכום

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

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

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

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



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

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

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

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

Exit mobile version