בפוסט זה אנסה לשפוך אור על נושא שיש לגביו בלבול רב - דפוסי ארכיטקטורה. בפשט: דפוסי ארכיטקטורה (כלומר: Architectural Patterns) הם דפוסים חוזרים בארכיטקטורות של מוצרים.
אנשים שאינם ארכיטקטים לעתים מדמיינים את תהליך הגדרת הארכיטקטורה של המערכת כ"תורה סודית" כלשהי. הארכיטקטים נכנסים לחדר, סוגרים בקפידה את דלת העץ הכבדה, ובוחרים בטקס סודי את "הארכיטקטורה". הארכיטקטורה הזו היא כ"כ מורכבת, ומאתגרת שכלית - שרק מי שלמד אותה במשך שנים יכול להבין אותה באופן מלא. הארכיטקטים אז מנסים להעביר אותה אל שאר הצוות, ולפעמים... נכון - העיסוק במדע המטורף של הארכיטקטורה עשה לארכיטקטים "משהו לא טוב בראש" - ומאז הם לא מתקשרים כ"כ טוב. הם בטח מבינים איזה משהו מאוד מתוחכם - אבל מתקשים להסביר אותו ל"פשוטי העם".
פלא שמסקרן להבין מהם דפוסי הארכיטקטורה האלה?
יותר גרוע: יש ארכיטקטים שנכנסים לחדר (ואולי זו בכלל דלת עץ זולה שלא נסגרת היטב), וגם הם מאמינים באגדת "הארכיטקטורה האחת" שאותה מוצאים הארכיטקטים מתוך המאגר הסודי של דפוסי הארכיטקטורה. הם מקדישים כמה דקות בכדי לסגוד לדפוס הארכיטקטורה החביב עליהם ואז מותחים את מהות המוצר להיות בדמותו של דפוס הארכיטקטורה שנבחר. אאוץ'!
אתן לכם מטאפורה לדפוסי ארכיטקטורה:
דפוסי ארכיטקטורה הם פתרון מהיר לבעיה שדורשת מיומנות לפתור - אבל המתכון הוא לא בהכרח הפתרון האופטימלי.
אם אתם לא ממש מומחים בבישול - שימוש בדפוס קיים יכול מאוד לעזור. ניסיון "להמציא מתכון מהראש" יכול להסתיים בחוסר נעימות.
מצד שני, המתכון הוא לא האידאל, ושף מוכשר יוכל להרכיב מתכון טוב בהרבה בהתחשב בחומרי הגלם הזמינים, הסועדים, או מצב הרוח. למתכון מוכן אין את ההקשר למצב הייחודי שלכם [א]. המתכון גם פעמים רבות הופשט מהאידאל, בכדי שהיה אפשר להעביר אותו לרבים, ועל גבי תקשורת מוגבלת (זה לא שאתם עוברים Mentoring של כמה ימים על המתכון).
דפוסי ארכיטקטורה לא שואבים אנרגיה קוסמית מהיקום סביבנו - בכדי להבטיח הצלחה נצחית למוצר.
דפוסי ארכיטקטורה לא מכילים "קודים מסתוריים" שבן תמותה רגיל לא מסוגל להבין.
דפוסי ארכיטקטורה הם אפילו, כהגדרה, פתרון תת-אופטימלי, מכיוון שהם הוגדרו ללא ידע על ה Context בו אנו פועלים.
מצד שני נסו לדמיין מצב בו ידע שנצבר בבישול לא היה מועבר בין אנשים? כל אחד היה אוכל רק מה מה שהצליח להמציא בעצמו. איך היו נראות הארוחות שלנו ללא מתכנונים כלשהם?
- מצבנו הקולינרי היה עגום כמו זה של תושבי אנגליה!! (חס וחלילה)
דפוסים נפוצים אחרים שלא נעבור עליהם הם, אך שווה להזכיר:
אני מניח שקראתם את הפוסט שלי מיוני "דפוסי עיצוב: ימים יפים, ימים קשים", או שלפחות אתם יודעים כל מה שנאמר בו. לא אחזור על אותם רעיונות, שרלוונטיים בערך באותה מידה, לדפוסים ארכיטקטוניים.
בגדול אזכיר ש:
בואו נצא לדרך!
השם ה(לא) מחמיא הזה כמובן ניתן ל Anti-Pattern. כדור גדול של בוץ הוא מצב בו כל המחלקות במערכת שלנו נמצאות ב Pool אחד משותף, ללא חלוקה ברורה ומסודרת.
מצב זה אולי נוח כאשר יש 10 מחלקות, אך מאוד לא נוח כאשר יש כ 1000 מחלקות במערכת.
אפשר בהחלט להתחיל במערכת עם "כדור גדול של בוץ", לגלות את התכונות והמבנה והרצוי של המערכת - ואז לחלק. ככלל אצבע הייתי מצביע על כמה עשרות מחלקות, לכל היותר, כנקודה בה כדאי מאוד שהיה כבר מבנה מוגדר.
מדוע כדאי לנו לחלק את המערכת לחלקים? בגלל:
אוקיי: הבנו שיש חשיבות לסדר בסיסי במערכת. בואו ניקח בחור מסודר, שיקבע סדר.
האם משנה בדיוק מהו הסדר?
הדר בוחר בסדר פשוט:
נשרטט קווים, ונחלק את המערכת ל3 אזורים:
כל מפתח יודע היכן לשים את המחלקה שהוא כותב - הכללים ברורים.
אולי שיפרנו טיפה את בעיית ההתמצאות ("חישוב האלגוריתם הוא מסובך מאוד, אחפש באזור המחלקות הגדולות") ויצרנו תחושה (דיי מפוברקת) של מסגרת - אך לא טיפלנו בכלל ב Development Scalability.
ברור שכלל של גודל המחלקה הוא לא כלל טוב: אי אפשר לנחש אם פונקציונליות כתובה במחלקה אחת ענקית, או 10 קטנות.
נמשיך. הדר הבין שעליו לתקן ולכן מצא חלוקה חדשה: חלוקה "עסקית". אם המערכת היא GMAIL אז נחלק את המערכת ל:
החלוקה הזו נשמעת יותר הגיונית, אבל גם בה יש בעיות עקרוניות. למשל: הקבוצה "אחר" יכולה להכיל לא-מעט מחלקות שלא מתקשרות ישירות לפונקציה "עסקית" (persistence, helpers, פקדי UI שבשימוש חוזר, וכו'). יצרנו שם "כדור בינוני של בוץ" בפני עצמו. ההתמצאות עצמה היא בעיקר ברמת האזורים ("שכונות בעיר") אך לא מכסה רזולוציה קטנה יותר ("רחובות").
דבר נוסף הוא שכדי להשיג Developer Scalability (היכולת של הרבה אנשים לעבוד על המערכת במקביל) אנו נרצה להטיל מגבלות על היכולת של החלקים השונים של המערכת לתקשר זה עם זה.
את קוד ה Parser אנו מחלקים ל2 חלקים. מדוע?
אנשים שאינם ארכיטקטים לעתים מדמיינים את תהליך הגדרת הארכיטקטורה של המערכת כ"תורה סודית" כלשהי. הארכיטקטים נכנסים לחדר, סוגרים בקפידה את דלת העץ הכבדה, ובוחרים בטקס סודי את "הארכיטקטורה". הארכיטקטורה הזו היא כ"כ מורכבת, ומאתגרת שכלית - שרק מי שלמד אותה במשך שנים יכול להבין אותה באופן מלא. הארכיטקטים אז מנסים להעביר אותה אל שאר הצוות, ולפעמים... נכון - העיסוק במדע המטורף של הארכיטקטורה עשה לארכיטקטים "משהו לא טוב בראש" - ומאז הם לא מתקשרים כ"כ טוב. הם בטח מבינים איזה משהו מאוד מתוחכם - אבל מתקשים להסביר אותו ל"פשוטי העם".
פלא שמסקרן להבין מהם דפוסי הארכיטקטורה האלה?
יותר גרוע: יש ארכיטקטים שנכנסים לחדר (ואולי זו בכלל דלת עץ זולה שלא נסגרת היטב), וגם הם מאמינים באגדת "הארכיטקטורה האחת" שאותה מוצאים הארכיטקטים מתוך המאגר הסודי של דפוסי הארכיטקטורה. הם מקדישים כמה דקות בכדי לסגוד לדפוס הארכיטקטורה החביב עליהם ואז מותחים את מהות המוצר להיות בדמותו של דפוס הארכיטקטורה שנבחר. אאוץ'!
אתן לכם מטאפורה לדפוסי ארכיטקטורה:
דפוסי ארכיטקטורה הם כמו מתכון למרק
דפוסי ארכיטקטורה הם פתרון מהיר לבעיה שדורשת מיומנות לפתור - אבל המתכון הוא לא בהכרח הפתרון האופטימלי.
אם אתם לא ממש מומחים בבישול - שימוש בדפוס קיים יכול מאוד לעזור. ניסיון "להמציא מתכון מהראש" יכול להסתיים בחוסר נעימות.
מצד שני, המתכון הוא לא האידאל, ושף מוכשר יוכל להרכיב מתכון טוב בהרבה בהתחשב בחומרי הגלם הזמינים, הסועדים, או מצב הרוח. למתכון מוכן אין את ההקשר למצב הייחודי שלכם [א]. המתכון גם פעמים רבות הופשט מהאידאל, בכדי שהיה אפשר להעביר אותו לרבים, ועל גבי תקשורת מוגבלת (זה לא שאתם עוברים Mentoring של כמה ימים על המתכון).
דפוסי ארכיטקטורה לא שואבים אנרגיה קוסמית מהיקום סביבנו - בכדי להבטיח הצלחה נצחית למוצר.
דפוסי ארכיטקטורה לא מכילים "קודים מסתוריים" שבן תמותה רגיל לא מסוגל להבין.
דפוסי ארכיטקטורה הם אפילו, כהגדרה, פתרון תת-אופטימלי, מכיוון שהם הוגדרו ללא ידע על ה Context בו אנו פועלים.
מצד שני נסו לדמיין מצב בו ידע שנצבר בבישול לא היה מועבר בין אנשים? כל אחד היה אוכל רק מה מה שהצליח להמציא בעצמו. איך היו נראות הארוחות שלנו ללא מתכנונים כלשהם?
- מצבנו הקולינרי היה עגום כמו זה של תושבי אנגליה!! (חס וחלילה)
הדפוסים עליהם נעבור בפוסט
- כדור גדול של בוץ (Big Ball of Mud)
- "הדר המסודר" (Hadar Amesudar)
- "שכבות עוגה" (Layered Architecture)
- MVC (קיצור של Model-View-Controller)
- "דודות מרכלות" (Event-Driven Architecture)
- Command-Query Separation (בקיצור: CQRS או CQS)
- "גרעין קטן" (MicroKernel) ודרכו נזכיר גם Plug-in Architecture
- "מיקרו-שירותים" (Micro-Services Architecture, בקיצור: MSA)
דפוסים נפוצים אחרים שלא נעבור עליהם הם, אך שווה להזכיר:
- צינורות ומסננים (Pipes & Filters) - אותו כיסיתי בפירוט בפוסט משלו.
- BlackBoard (רלוונטי בעולם של בינה מלאכותית)
- MVP, MVVM או Presentation-Abstraction-Control (דומים למדי ל MVC)
- Broker (רלוונטי במערכות מבוזרות)
- Model-Driven Architecture (מן "רעיון גדול" שלא ממש "תפס") או Domain-Specific Languages (בקיצור DSL - רעיון שהצליח חלקית).
- וכו'
אני מניח שקראתם את הפוסט שלי מיוני "דפוסי עיצוב: ימים יפים, ימים קשים", או שלפחות אתם יודעים כל מה שנאמר בו. לא אחזור על אותם רעיונות, שרלוונטיים בערך באותה מידה, לדפוסים ארכיטקטוניים.
בגדול אזכיר ש:
- לדפוסים יש ערך רב כשפה משותפת וכמקור השראה.
- הצמדות לדפוסים "כפי שהוגדרו" - יכולה להיות הרסנית.
- כדאי ליצור וריאציות שלכם של הדפוסים שיתאימו לכם יותר, או לשלב רעיונות (Mix and Match) מדפוסים שונים.
בואו נצא לדרך!
כדור גדול של בוץ (Big Ball of Mud)
השם ה(לא) מחמיא הזה כמובן ניתן ל Anti-Pattern. כדור גדול של בוץ הוא מצב בו כל המחלקות במערכת שלנו נמצאות ב Pool אחד משותף, ללא חלוקה ברורה ומסודרת.
מצב זה אולי נוח כאשר יש 10 מחלקות, אך מאוד לא נוח כאשר יש כ 1000 מחלקות במערכת.
אפשר בהחלט להתחיל במערכת עם "כדור גדול של בוץ", לגלות את התכונות והמבנה והרצוי של המערכת - ואז לחלק. ככלל אצבע הייתי מצביע על כמה עשרות מחלקות, לכל היותר, כנקודה בה כדאי מאוד שהיה כבר מבנה מוגדר.
מדוע כדאי לנו לחלק את המערכת לחלקים? בגלל:
- בעיית ההתמצאות (Orientation)
- בעיית ה Development Scalability
- יצירת מסגרת מנטלית של סדר וחוקיות במערכת. זו לא בדיחה: כמה כללים פשוטים מה "נכון" ומה "לא נכון" עוזרים לאנשים בפרויקט להרגיש בטוחים יותר במה שהם עושים. אי קיום הכללים הפשוטים הללו / חוסר סדר בסיסי כלשהו בפרויקט - משפיע על אנשים רבים (לא כולם) בצורה שלילית וברורה
קשה להבין את ההשפעה עד שלא רואים את המעבר של פרויקט ממצב של "אי-סדר", לאפילו מצב של "סדר בסיסי".
את 2 הבעיות הראשונות תיארתי בפירוט בפוסט מדוע אנו זקוקים ל Software Design?
"הדר המסודר"
אוקיי: הבנו שיש חשיבות לסדר בסיסי במערכת. בואו ניקח בחור מסודר, שיקבע סדר.
האם משנה בדיוק מהו הסדר?
הדר בוחר בסדר פשוט:
נשרטט קווים, ונחלק את המערכת ל3 אזורים:
- מחלקות עד 1000 שורות קוד
- מחלקות מתחת ל 1000 שורות קוד
- קבועים, enums, או data objects (שהם בד"כ מאוד קטנים)
כל מפתח יודע היכן לשים את המחלקה שהוא כותב - הכללים ברורים.
אולי שיפרנו טיפה את בעיית ההתמצאות ("חישוב האלגוריתם הוא מסובך מאוד, אחפש באזור המחלקות הגדולות") ויצרנו תחושה (דיי מפוברקת) של מסגרת - אך לא טיפלנו בכלל ב Development Scalability.
ברור שכלל של גודל המחלקה הוא לא כלל טוב: אי אפשר לנחש אם פונקציונליות כתובה במחלקה אחת ענקית, או 10 קטנות.
נמשיך. הדר הבין שעליו לתקן ולכן מצא חלוקה חדשה: חלוקה "עסקית". אם המערכת היא GMAIL אז נחלק את המערכת ל:
- דואר נכנס
- דואר יוצא
- אנשי קשר
- קונפיגורציה
- אחר
החלוקה הזו נשמעת יותר הגיונית, אבל גם בה יש בעיות עקרוניות. למשל: הקבוצה "אחר" יכולה להכיל לא-מעט מחלקות שלא מתקשרות ישירות לפונקציה "עסקית" (persistence, helpers, פקדי UI שבשימוש חוזר, וכו'). יצרנו שם "כדור בינוני של בוץ" בפני עצמו. ההתמצאות עצמה היא בעיקר ברמת האזורים ("שכונות בעיר") אך לא מכסה רזולוציה קטנה יותר ("רחובות").
דבר נוסף הוא שכדי להשיג Developer Scalability (היכולת של הרבה אנשים לעבוד על המערכת במקביל) אנו נרצה להטיל מגבלות על היכולת של החלקים השונים של המערכת לתקשר זה עם זה.
מדוע אנו מעוניינים בסדר וניהול תלויות?
נמשיל את העניין לבעיה פשוטה מאוד: הפרדה של קוד בין 2 מחלקות:את קוד ה Parser אנו מחלקים ל2 חלקים. מדוע?
- כי יותר קל לנהל קוד בחתיכות קטנות ופשוטות, מחתיכה גדולה ומורכבת יותר.
- כי אז ניתן לבצע שינוי באזור אחד, עם סבירות גבוהה* שלא יהיה צורך לשנות אזור אחר. פחות בדיקות רגרסיה, פחות "פחד" לשבור דברים, והיכולת של 2 מפתחים לעבוד במקביל בצורה יותר יעילה.
* כל עוד החלוקה היא אפקטיבית
לא מספיק שחילקנו את ה Parser ל 2 מחלקות, אנו רוצים להטיל מגבלות על התלויות ביניהן:
- הכמסה (encapsulation) - מגבלה מאוד מקובלת בעולם ה OO, שמפרידה בין חלקים ציבוריים (בהם ניתן ליצור תלות) ופרטיים (בהם לא ניתן ליצור תלות). עצם כך שצמצמנו את התלויות - הגברנו את הסיכוי ששינוי בקוד (באזור הפרטי) לא ידרוש שינויים או ישפיע על מחלקות שתלויות במחלקה זו - שוב: Developer Scalability, תחזוקה קל היותר, ויכולת לבצע שינויים בצורה פשוטה יחסית (באזורים הפרטיים).
- ניהול תלויות (dependency management) - מגבלה שאומרת שמחלקת ה Document Parser רשאית לקרוא ל Paragraph Parse - אך לא להיפך. צמצמנו כך את התלויות עוד יותר.
כמו כן החכמה בניהול תלויות היא לא רק לקבוע כלל מגביל, אלא להגדיר מראש מבנה בו יהיה קל ליישם את הכללים הללו. לדוגמה, אם היינו מחלקים את ה Parser בצורה הבאה:
לא היה סביר שהתלות החד-כיוונית שהוגדרה בין המחלקות, תשמר לאורך זמן (בהנחה שיש תלויות קונספטואליות בין פענוח אלמנטים מסוג A ו B). צוות עם עצבי ברזל אולי יגיע לשימוש בפונקציות עם הרבה פרמטרים והרבה Callbacks - אך זה גם לא מצב רצוי. סה"כ זו דוגמה לחלוקה לא מוצלחת.
כללים מגבילים בין מחלקות / צמצום התלויות גם עוזר לשמור על הסדר שהוגדר: ברגע שהממשקים הם מצומצמים יש בד"כ פחות "דו-משמעות" היכן קוד צריך להיות: מחלקה A או מחלקה B. כך נשמר התפקיד הייחודי של כל מחלקה, ומכאן יכולת התמצאות בקוד, יכולת לעשות שינויים מבודדים וכו'.
הכללים המגבילים, מציבים בפנינו שקלול-תמורות מסוים:
טוב, גם הדר, למרות שהוא כל-כך מסודר - עדיין לא ממש עזר לנו.
זה הזמן להתנסות במתכון ותיק ומוכח, וללמוד ממנו כמה דברים.
דפוס השכבות הוא הדפוס הארכיטקטוני בשימוש הנרחב ביותר.
הוא נפוץ במיוחד במערכות מידע / Web Applications / Enterprise Applications (מבחינה ארכיטקטונית, הן בערך אותו הדבר) - והוא כנראה גם הדפוס השולט גם ב domains רבים אחרים. חשבו: כמה מקובל להציג ארכיטקטורה כקוביות בזו מעל זו, ומה צורה זו מתאר.
באופן לא מפתיע, עם הפופולריות מגיעה גם כמות יפה של חוזר-הבנה, ושימוש לא נכון.
כללי החלוקה של דפוס השכבות הוא כדלהלן:
האם היא טובה?
בעיה נפוצה היא הצמדות למתכון המוכר והמוערך - גם כשהוא לא מתאים.
במערכות ווב רבות, המתכנתים בוחרים במתכון זה - למרות שהוא לא מבטא את צרכי המודולריזציה של המערכת. כאשר כל פי'צר שמוסיפים למערכת דורש גם שינוי ב UI, גם ב Business Logic, גם בגישה לבסיס נתונים, וגם בטבלאות של בסיס הנתונים, וכאשר חלק גדול מהעבודה הוא הוספת פיצ'רים חדשים - אז מה הטעם בשכבות?
יצרנו ואכפנו מגבלות שימוש, אולם בפועל, אם הקשר שלנו בין השכבות היא תדיר יותר מהקשר בתוך השכבה - אזי השכבות לא מבודדות לנו אזורים בקוד, והערך שקיבלנו מהשימוש בדפוס הארכיטקטורה - הוא נמוך.
בפועל, קיבלנו סוג של "הדר המסודר" - יש סדר ואורנטציה במערכת, אבל מעט מאוד ערך מוסף. אנו משקיעים בהתמודדות עם המגבלות שיצרנו - אבל הן לא מוסיפות לנו ערך ממשי לתהליך הפיתוח / התחזוקה של המוצר.
דפוס ארכיטקטוני שעשוי להתאים במקרים בהם כל פיצ'ר / פיתוח קטן נוגע בכל (או רוב) השכבות הוא דפוס ה microservices - שזוכה לפופולריות רבה לאחרונה. האם גם אותו אפשר "לפספס"? - כמובן!
אציין, שבמערכות Enterprise כדוגמת ERP של סאפ - יש ערך ממשי בחלוקה הנ"ך לשכבות: מערכות אלו מבוססות במידה רבה על מידול (modeling) של נתונים, ואז באמת יש פיתוחים ב Business Layer שלא צריכים לעדכן את ה UI (כי הוא נבנה מתוך מודל הנתונים שלא השתנה), ולא צריכים לעדכן כמעט את הטבלאות בבסיס הנתונים. שכבת ה Business Layer היא משמעותית ומשתנה תדיר - ויש ערך רב בהפרדה זו. כמו כן ניתן לשנות את ה UI (כיצד מרנדרים את המודל) - מבלי לשנות את שכבת ה Business Logic בכלל, וכו'.
עוד בעיה נפוצה היא פרשנות של המודל ע"פ פונקציה אותה ממלאת המחלקה / מודול ולא ע"פ רמת הפשטה, כך שלמשל, כל פעולת גישה לדיסק נכנסת לשכבת ה Persistence באופן אוטומטי - "כי דיסק הוא Persistence" ואף אחד לא שוקל מהי רמת ההפשטה של הפעולה. למשל: קריאה של קובץ מהדיסק ששקול ל Input מהמשתמש - נכון יותר, כנראה, שהיה בשכבת ה UI. זו אותה רמה הפשטה. שכבת ה Persistence הוגדרה מכיוון שיש הרבה מאוד פעולות דיסק שהם ברמה נמוכה - אך לא כולן הן כאלה.
התעלמות מהעיקרון של רמות ההפשטה השונות - גורם ל"פספוס" ביישום של מודל השכבות.
תאוריה בצד. בפועל: יש לו בעיה מהותית - שמונעת ממנו לעתים "להמריא".
דפוס MVC בא לחלק אפליקציית UI לחלקים מוגדרים היטב - ולהגדיר תלויות ביניהן.
המקור של MVC הוא ב framework של שפת Smalltalk-80 משנת 1980 בשם "Model-View-Controller Editor".
ה Framework הזה היה מיועד לאפליקציות command line (שיא ה UI באותה תקופה), והחלוקה הייתה בין:
כל מחלקה (או פונקציה) נופלת באחד מהאזורים - וחייבת לציית לכליל הנראות. בת'כלס ה Controller רק יוצר את ה View ולא משתמש בו אח"כ, ושניהם מכירים את המודל. בין מחלקה של View למחלקה של Controller יש בד"כ קשר של 1:1 - והם מתארים "מסך" או "תפריט" באפליקציה.
הרעיון העיקרי מאחורי דפוס ארכיטקטוני זה היה הפרדה בין המודל (ה state של האפליקציה) לקלט והפלט של התפריטים השונים. הדפוס הפך להצלחה (אולי כאחד הדפוסים היחידים שמנסה לפתור בעיות של UI) - ועד היום אנו משתמשים בו... או שלא.
אם כולם היו מכירים את MVC ומשתמשים בו באותו האופן - לדפוס זה היה ערך רב יותר. אולם, יש 2 בעיות:
הפרדה זו יוצרת מעט overhead על הפיתוח, אך היא מאפשרת להחליף את ה presentation (כלומר: אלמנטים ב UI / כל ה UI) בצורה קלה יחסית, מבלי "לאבד" או לשנות את ה Presentation Logic שהוא יותר יציב, או לחלופין להוסיף presentation נוסף (נאמר: mobile version), מבלי לשכפל את הקוד לגמרי.
החלפת / רענון שכבת ה UI - היא פעולה תדירה מאוד במערכות ווב. ארגונים גדולים עושים זאת כל 2-3 שנים, ואתרים קטנים מבצעים שינויים לפעמים על בסיס חודשי.
חשוב לזכור שדפוס ארכיטקטוני הוא דפוס שחוזר על עצמו כי הוא מבטא כמה רעיונות שימושיים מאוד - אבל זה לא אומר שיש דרך יחידה להשתמש בו (נאמר - הגרסה שתועדה היא לא בהכרח הטובה ביותר), ויש עניין של התאמה להקשר מסוים: מה שטוב מאוד במערכת אחת, עשוי להיות פחות מתאים במערכת אחרת.
בפוסט המשך - אספק סקירה קצרה על ארבעת הדפוסים הנוספים שברשימה שלנו.
שיהיה בהצלחה!
---
[א] אם אין שום דבר ייחודי בתוכנה שאתם מפתחים, חבל על הזמן שלכם: מצאו תוכנה קיימת שעונה על צורך זה - והשתמשו בה.
[ב] בסיסי נתונים הם חבילת תוכנה ניתנת-לשימוש חוזר - שחסכה המון פיתוח, לאורך שנים, בקנה מידה עולמי. בסיסי הנתונים נהיו עשירים יותר ביכולות - וכך חסכו יותר עבודה למפתחים (ויצרו תלות גדולה יותר בחברות שעשו מכך הרבה כסף).
אמהמה, בסיסי הנתונים הוסיפו יכולות שנראו טובות מנקודת המבט שלהם, ובמקום היחידי בו הם יכלו להשפיע (בתוך בסיס הנתונים) - אך הן ממש לא שייכים לשם ע"פ מודל השכבות (שכ"כ נפוץ בקרב משתמשים של בסיסי-נתונים רלציוניים).
Stored Procedures הם דוגמה מצוינת ליכולת שכזו, שכמעט תמיד משתמשים בה בצורה שסותרת בצורה בוטה את העקרונות של מודל השכבות: לוגיקה עסקית (רמת הפשטה גבוהה) מנוהלת בתוך בסיס הנתונים (שכבת הפשטה הכי נמוכה) - ויוצרת תלות קשה בתחזוקה, ביכולת להחליף מימוש של שכבות, וכו'.
---
לינקים מעניינים
הדוד בוב בביקורת עוקצנית על MVC (וידאו): "MVC is not an Architecture"
כללים מגבילים בין מחלקות / צמצום התלויות גם עוזר לשמור על הסדר שהוגדר: ברגע שהממשקים הם מצומצמים יש בד"כ פחות "דו-משמעות" היכן קוד צריך להיות: מחלקה A או מחלקה B. כך נשמר התפקיד הייחודי של כל מחלקה, ומכאן יכולת התמצאות בקוד, יכולת לעשות שינויים מבודדים וכו'.
- פחות מדי מגבלות: כולם מדברים עם כולם במערכת - ונפגעת מאוד תכונת ה Development Scalability.
- יותר מדי מגבלות: מקשות על ה productivity של המפתחים.
ככל אצבע, ארכיטקטורה טובה לא כוללת כמה שיותר מגבלות: היא מתאימה את כמות המגבלות לגודל הפרויקט, לשלב ההתפתחות שלו (יותר מגבלות ככל שמתקדמים + כמה עולה לפיתוח להוסיף מגבלות בשלב זה?) ולארגון הספציפי (יש מתכנתים שיסתדרו טוב יותר עם מעט מגבלות, יש כאלו שלא).
זה הזמן להתנסות במתכון ותיק ומוכח, וללמוד ממנו כמה דברים.
Layered Architecture
דפוס השכבות הוא הדפוס הארכיטקטוני בשימוש הנרחב ביותר.
הוא נפוץ במיוחד במערכות מידע / Web Applications / Enterprise Applications (מבחינה ארכיטקטונית, הן בערך אותו הדבר) - והוא כנראה גם הדפוס השולט גם ב domains רבים אחרים. חשבו: כמה מקובל להציג ארכיטקטורה כקוביות בזו מעל זו, ומה צורה זו מתאר.
באופן לא מפתיע, עם הפופולריות מגיעה גם כמות יפה של חוזר-הבנה, ושימוש לא נכון.
כללי החלוקה של דפוס השכבות הוא כדלהלן:
- נפריד את מאגר המחלקות במערכת שלנו לכמה שכבות: שכבות עליונות לטיפול בעניינים ברמת הפשטה גבוהה, כאשר רמת ההפשטה יורדת ככל שיורדים בשכבות.
- לוגיקה בשכבה גבוהה (n) תהיה מורכבת מהפעלה של לוגיקות (או פונקציות) בשכבה שמתחתיה (n-1). כמו כן, לוגיקה בשכבה גבוהה (n) תעזור להרכיב את הפונקציות בשכבה הגבוהה ממנה (n+1).
- בד"כ השכבה הגבוהה ביותר היא זו שמתקשרת עם העולם החיצון: משתמש או מערכות אחרות.
- החלוקה של המחלקות לשכבות היא שלב אחד, ובד"כ יש חלוקות נוספות בתוך השכבה למודולים / תתי-מערכות (sub-systems).
- בד"כ בכל שכבה יש תת-שכבת API (או "Facade") מנוהלת - בה, ורק בה השכבה העליונה יכולה להשתמש. לא נרצה שכל ה APIs שזמינים בתוך השכבה יהיו זמינים לשכבה מעל - אחרת, איזו משמעות יש להפרדה שהגדרנו?!
פרשנות נפוצה של מודל השכבות
במערכות ווב, מערכות מידע, או Enterprise Systems יש מן מתכון מקובל של לחלוקה המערכת לשכבות.
אין לכם כח לקבוע רמות הפשטה שונות ושימושיות של המערכת? - השתמשו במתכון מוכן (מתכון של מתכון):
במערכות ווב, מערכות מידע, או Enterprise Systems יש מן מתכון מקובל של לחלוקה המערכת לשכבות.
אין לכם כח לקבוע רמות הפשטה שונות ושימושיות של המערכת? - השתמשו במתכון מוכן (מתכון של מתכון):
- UI - ממשק המשתמש
- Business Logic - הפונקציה העיקרית שהמערכת ממלאה (ניהול ספקים, רכש, מלאי, וכו')
- Data Access - בשכבה זו פעם היה הרבה קוד מתוחכם, אך היא הוחלפה חלקית ע"י מערכות ORM - ומה שנשאר בה הם האובייקטים של המערכת שמתארים את הנתונים (Entities, Collections, וכו').
- Persistency - שכבה זו מתייחסת לבסיס הנתונים (בהנחה שיש כזה, בד"כ רלציוני) - סקריפטים ליצירת הטבלאות (DDL) ולעתים גם קוד שרץ בתוך בסיס הנתונים (stored procedures) [ב].
האם היא טובה?
בעיה נפוצה היא הצמדות למתכון המוכר והמוערך - גם כשהוא לא מתאים.
במערכות ווב רבות, המתכנתים בוחרים במתכון זה - למרות שהוא לא מבטא את צרכי המודולריזציה של המערכת. כאשר כל פי'צר שמוסיפים למערכת דורש גם שינוי ב UI, גם ב Business Logic, גם בגישה לבסיס נתונים, וגם בטבלאות של בסיס הנתונים, וכאשר חלק גדול מהעבודה הוא הוספת פיצ'רים חדשים - אז מה הטעם בשכבות?
יצרנו ואכפנו מגבלות שימוש, אולם בפועל, אם הקשר שלנו בין השכבות היא תדיר יותר מהקשר בתוך השכבה - אזי השכבות לא מבודדות לנו אזורים בקוד, והערך שקיבלנו מהשימוש בדפוס הארכיטקטורה - הוא נמוך.
בפועל, קיבלנו סוג של "הדר המסודר" - יש סדר ואורנטציה במערכת, אבל מעט מאוד ערך מוסף. אנו משקיעים בהתמודדות עם המגבלות שיצרנו - אבל הן לא מוסיפות לנו ערך ממשי לתהליך הפיתוח / התחזוקה של המוצר.
דפוס ארכיטקטוני שעשוי להתאים במקרים בהם כל פיצ'ר / פיתוח קטן נוגע בכל (או רוב) השכבות הוא דפוס ה microservices - שזוכה לפופולריות רבה לאחרונה. האם גם אותו אפשר "לפספס"? - כמובן!
אציין, שבמערכות Enterprise כדוגמת ERP של סאפ - יש ערך ממשי בחלוקה הנ"ך לשכבות: מערכות אלו מבוססות במידה רבה על מידול (modeling) של נתונים, ואז באמת יש פיתוחים ב Business Layer שלא צריכים לעדכן את ה UI (כי הוא נבנה מתוך מודל הנתונים שלא השתנה), ולא צריכים לעדכן כמעט את הטבלאות בבסיס הנתונים. שכבת ה Business Layer היא משמעותית ומשתנה תדיר - ויש ערך רב בהפרדה זו. כמו כן ניתן לשנות את ה UI (כיצד מרנדרים את המודל) - מבלי לשנות את שכבת ה Business Logic בכלל, וכו'.
עוד בעיה נפוצה היא פרשנות של המודל ע"פ פונקציה אותה ממלאת המחלקה / מודול ולא ע"פ רמת הפשטה, כך שלמשל, כל פעולת גישה לדיסק נכנסת לשכבת ה Persistence באופן אוטומטי - "כי דיסק הוא Persistence" ואף אחד לא שוקל מהי רמת ההפשטה של הפעולה. למשל: קריאה של קובץ מהדיסק ששקול ל Input מהמשתמש - נכון יותר, כנראה, שהיה בשכבת ה UI. זו אותה רמה הפשטה. שכבת ה Persistence הוגדרה מכיוון שיש הרבה מאוד פעולות דיסק שהם ברמה נמוכה - אך לא כולן הן כאלה.
התעלמות מהעיקרון של רמות ההפשטה השונות - גורם ל"פספוס" ביישום של מודל השכבות.
Model-View-Controller
בתיאוריה, MVC הוא דפוס נהדר לחלוקה של שכבת ה UI.תאוריה בצד. בפועל: יש לו בעיה מהותית - שמונעת ממנו לעתים "להמריא".
דפוס MVC בא לחלק אפליקציית UI לחלקים מוגדרים היטב - ולהגדיר תלויות ביניהן.
המקור של MVC הוא ב framework של שפת Smalltalk-80 משנת 1980 בשם "Model-View-Controller Editor".
ה Framework הזה היה מיועד לאפליקציות command line (שיא ה UI באותה תקופה), והחלוקה הייתה בין:
- Model - הנתונים של האפליקציה / מבני נתונים
- View - תצוגת הפלט, מה לרשום על המסך
- Control - קבל ה Input מהמשתמש וטיפול בקלט הזה - בעצם ה Business Logic.
MVC "קלאסי" / "המקורי" |
כל מחלקה (או פונקציה) נופלת באחד מהאזורים - וחייבת לציית לכליל הנראות. בת'כלס ה Controller רק יוצר את ה View ולא משתמש בו אח"כ, ושניהם מכירים את המודל. בין מחלקה של View למחלקה של Controller יש בד"כ קשר של 1:1 - והם מתארים "מסך" או "תפריט" באפליקציה.
הרעיון העיקרי מאחורי דפוס ארכיטקטוני זה היה הפרדה בין המודל (ה state של האפליקציה) לקלט והפלט של התפריטים השונים. הדפוס הפך להצלחה (אולי כאחד הדפוסים היחידים שמנסה לפתור בעיות של UI) - ועד היום אנו משתמשים בו... או שלא.
אם כולם היו מכירים את MVC ומשתמשים בו באותו האופן - לדפוס זה היה ערך רב יותר. אולם, יש 2 בעיות:
- MVC המקורי לא כ"כ מתאים לאפליקציות Desktop, Web או כל מה שאינו command line.
- יש המון וריאציות של MVC: חלקן קיבלו שמות נבדלים (יחסית) כמו MVP, MVVM או PAC - אבל רוב הווריאציות פשוט משתמש בשם "MVC", תוך כדי ביצוע שינויים במודל "המקורי".
התוצאה המידית היא בלבול וחוסר תקשורת - FUD. אם דיברנו על כך שדפוסים משמשים כשפה משותפת, אזי MVC משמש לרוב כאנטי-שפה-משותפת: מתכנת אחד אומר "Controller" והשני שומע "Kotroller" (כלומר: מה שהוא מכיר כ Controller). שני המתכנתים רבים כמה ימים, אולי שבועות - עד שהם מצליחים להסכים שבעצם השני לא אידיוט/מפגר/לא מבין - שניהם פשוט מכירים שתי וריאציות שונות של MVC.
בפועל, אוותר על הניסיון לתאר ולאפיין את כל הווריאציות התאורטיות של MVC (להזכיר: יש כאלו שהם Client-Side, כאלו שהם MVC צד שרת, כאלו שהם מערבים צד-לקוח וצד-שרת, וכאלו שמתאימים לאפליקציות Desktop או Native Mobile).
אני ממליץ לוותר על ההגדרות, ופשוט להיצמד לספרייה הרלוונטית שבה אתם משתמשים - והכללים שהיא מכתיבה: בסוף, התועלת העיקרית היא מכללים ברורים שכולם מצייתים להם - קצת פחות אם כלל מסוים מנוסח בווריאציה 2א' או בווריאציה 3ג'.
יש ספריות יש רבות הממשות (וריאציה של) דפוס זה: Struts, Play!, Grails, Ruby on Rails, Django, ASP.NET MVC , Angular.js, Backbone.js ועוד....
מבנה של MVC צד-לקוח מודרני |
העיקרון המשמעותי ביותר, והמוסכם ביותר בקרב ספריות "MVC" מודרניות הוא ההפרדה בין UI (כיצד משהו נראה על המסך) ל Presentation Logic (כיצד להחליט מה יש להציג על המסך). למשל (דוגמה סופר-פשוטה):
במקום לקבוע כלל ש:
- אדם עם +500 חברים מקבל icon מיוחד.
מחלקים את המימוש ל2 אזורים:
- Presentation Logic - אדם עם 500+ מתויג כ "אדם פופולרי במיוחד" (What)
- UI - אדם המתויג כ "אדם פופולרי במיוחד" מקבל icon מיוחד.
הפרדה זו יוצרת מעט overhead על הפיתוח, אך היא מאפשרת להחליף את ה presentation (כלומר: אלמנטים ב UI / כל ה UI) בצורה קלה יחסית, מבלי "לאבד" או לשנות את ה Presentation Logic שהוא יותר יציב, או לחלופין להוסיף presentation נוסף (נאמר: mobile version), מבלי לשכפל את הקוד לגמרי.
החלפת / רענון שכבת ה UI - היא פעולה תדירה מאוד במערכות ווב. ארגונים גדולים עושים זאת כל 2-3 שנים, ואתרים קטנים מבצעים שינויים לפעמים על בסיס חודשי.
סיכום
סקרנו ארבעה דפוסי ארכיטקטורה, ועסקנו בעקרונות - מה הערך של דפוס ארכיטקטוני.חשוב לזכור שדפוס ארכיטקטוני הוא דפוס שחוזר על עצמו כי הוא מבטא כמה רעיונות שימושיים מאוד - אבל זה לא אומר שיש דרך יחידה להשתמש בו (נאמר - הגרסה שתועדה היא לא בהכרח הטובה ביותר), ויש עניין של התאמה להקשר מסוים: מה שטוב מאוד במערכת אחת, עשוי להיות פחות מתאים במערכת אחרת.
בפוסט המשך - אספק סקירה קצרה על ארבעת הדפוסים הנוספים שברשימה שלנו.
שיהיה בהצלחה!
---
[א] אם אין שום דבר ייחודי בתוכנה שאתם מפתחים, חבל על הזמן שלכם: מצאו תוכנה קיימת שעונה על צורך זה - והשתמשו בה.
[ב] בסיסי נתונים הם חבילת תוכנה ניתנת-לשימוש חוזר - שחסכה המון פיתוח, לאורך שנים, בקנה מידה עולמי. בסיסי הנתונים נהיו עשירים יותר ביכולות - וכך חסכו יותר עבודה למפתחים (ויצרו תלות גדולה יותר בחברות שעשו מכך הרבה כסף).
אמהמה, בסיסי הנתונים הוסיפו יכולות שנראו טובות מנקודת המבט שלהם, ובמקום היחידי בו הם יכלו להשפיע (בתוך בסיס הנתונים) - אך הן ממש לא שייכים לשם ע"פ מודל השכבות (שכ"כ נפוץ בקרב משתמשים של בסיסי-נתונים רלציוניים).
Stored Procedures הם דוגמה מצוינת ליכולת שכזו, שכמעט תמיד משתמשים בה בצורה שסותרת בצורה בוטה את העקרונות של מודל השכבות: לוגיקה עסקית (רמת הפשטה גבוהה) מנוהלת בתוך בסיס הנתונים (שכבת הפשטה הכי נמוכה) - ויוצרת תלות קשה בתחזוקה, ביכולת להחליף מימוש של שכבות, וכו'.
---
לינקים מעניינים
הדוד בוב בביקורת עוקצנית על MVC (וידאו): "MVC is not an Architecture"
פוסט מצוין, נהניתי ממש לקרוא!
השבמחקרק 2 הערות קטנטנות ;)
טוב, גם הדר, למרות שהוא כך-כך מסודר - התכוונת לכתוב כל כך?
ארגונים גדולים עושים זאת כל 2-3 שונים - שנים ולא שונים.
ממש תודה!
שוב תודה לך.
מחקליאור
תודה ליאור, הפוסט מאוד מעניין וכתוב בצורה מקצועית, ממש כיף לקרוא וללמוד!
השבמחק