זהו פוסט שלישי בסדרת "המחלף והצומת", סדרת הפוסטים המתארים אפשרויות שונות למעבר חלק ככל האפשר בין ארכיטקטורות.
אפשרות שניה: ארכיטקטורה חדשה עם שימוש ברכיבי לוגיקה קיימים
כמו בגישה הקודמת, גם בגישה זו נבנה את הארכיטקטורה החדשה "מסקראץ'", כך שיהיה לנו שלד של מערכת שניתן להריץ באופן עצמאי ומנותק מן המערכת הקודמת. בשונה מן הגישה הקודמת, כאן לא ירוצו שתי המערכות זו לצד זו, אלא הארכיטקטורה החדשה בלבד. עם זאת, כיוון ששכתוב פיצ'ר קיים כך שיתאים לארכיטקטורה החדשה יכול לארוך זמן רב, נעביר כל פיצ'ר בשני שלבים. בשלב הראשון, ניקח את הקוד הקיים כמות שהוא, ככל האפשר, ונייצר adapter המתאים אותו למערכת החדשה. כך, בעזרת כתיבת שכבה דקה יחסית, נוכל לספק את מרבית הפיצ'רים הקיימים עבור המערכת החדשה בזמן קצר. לאחר מכן, בתהליך ארוך ומסודר יותר, נשכתב את הפיצ'רים כך שייכתבו בהתאם לפילוסופיה של הארכיטקטורה החדשה.
איך עושים את זה? שלב אחרי שלב
כדי לתאר את רצף השינויים, נשתמש שוב בדוגמת ה"מערכת לזיהוי דמיון" אשר הוצגה בפוסט הקודם. נזכיר שוב – עיקר השינוי במקרה זה נובע ממעבר לבסיס נתונים שונה, וממעבר מארכיטקטורה מונוליטית למערכת מבוססת מיקרו שירותים. במערכת המקורית, היו לנו כבר מספר שירותים פעילים, אלא שהם היוו מודולים, או פונקציות, בתוך שכבת קוד אחת. על מנת להפוך את המערכת החדשה שלנו למבצעית בהקדם האפשרי, נשתדל להשתמש ככל האפשר בלוגיקה אפליקטיבית קיימת, כך שלא נצטרך להשקיע זמן בכתיבה ודיבוג של לוגיקה חדשה אלא רק בהתאמות.
אם נוכל, נשתדל ליצור שכבת אדפטציה גנרית. למשל: אם השתמשנו במערכת ORM המתאימה לבסיס נתונים מסויים, וכעת אנו יכולים לכתוב שכבה שממפה את מרבית הקריאות הללו לקריאות ב-ORM המתאים לבסיס הנתונים החדש – נוכל לבצע ביתר קלות אדפטציה של הקוד הקיים. אם היינו קוראים לקוד בצורה פונקציונלית, וכעת מדובר בתהליך הקורא תור-מסרים כדי לדעת מה לבצע ומה להחזיר – נוכל לכתוב שכבה הקוראת מסרים, מפעילה פונקציה בהתאם למסר ומחזירה תשובה בדרך דומה. כמובן, בדרך כלל שכבה גנרית אינה פותרת את כל השינויים הנדרשים, אולם היא יכולה בהחלט לפתור את רובם.
נקודה חשובה למחשבה בצורת שינוי זו היא, עד כמה הלוגיקה הישנה זקוקה לשינוי באופן אינהרנטי (זאת אומרת, על מנת לעמוד בדרישות – ולא רק על מנת להיות כתובה בהתאם לגישה החדשה). לצורך כך חשוב להבין לעומק את הסיבות האמיתיות לשינוי הארכיטקטוני. למשל: יכולת סקאלאביליות, או בעיות ביצועים. מדוע זה חשוב? כי כך נוכל להחליט עד כמה חשוב להיכנס לעומק הלוגיקה הקיימת. בדוגמת ה"מערכת לזיהוי דמיון", הסברנו כי אנו מעוניינים להוסיף שירותים נוספים אשר המערכת הקודמת אינה מסוגלת להריץ כראוי. אולם, האם השירותים הקיימים נתקלים בקשיים גם הם? התשובה לכך תגזור את עומק השינוי שנבצע בשלב זה, שיכול להשתנות ממקרה למקרה:
שינוי ממשקים בלבד
ניקח לדוגמא את "פיצ'ר השירים #1". פיצ'ר זה עשוי להיות מורכב מכמה חלקים, שבהתאם לפילוסופיה של מיקרו-שירותים, היה ראוי לחלק לכמה שירותים שונים. אולם, אם נוכל להריץ אותו כשירות יחיד ועדיין לא לחוות כל בעיה, נשמור אותו בשלב זה כשירות יחיד.
התאמת-ארכיטקטורה מינימלית נדרשת
"פיצ'ר השירים #2", לעומת זאת, עשוי להראות כבר בעיות ביצועים, שהארכיטקטורה החדשה פותרת בזכות התקשורת הא-סינכרונית. אם כן, במקרה זה, לא ניקח את הלוגיקה כמקשה אחת, אלא נפרק אותה למספר שירותים. ועדיין, אין הכרח לפרק אותה למלוא הרזולוציה שאליה אנו מבקשים להגיע. נבצע רק את אותו פירוק שיספיק כדי לפתור את בעיית הביצועים. אם, למשל, בעיית הביצועים נובעת משלב ספציפי באלגוריתם הלוקח זמן חישוב ארוך וניתן לבצע אותו במקביל באופן א-סינכרוני, נוציא רק את החלק הזה כמיקרו-שירות נוסף, ואת כל השאר נשאיר כמות שהוא. כך נקבל שני מיקרו-שירותים המחליפים את המודול הבודד המקורי, ופותרים את הבעיה המיידית – למרות שמבחינה לוגית, ראוי היה לחלק אותו למספר רב יותר של מיקרו שירותים.
התאמה פנימית מינימלית
"פיצ'ר השירים #3" אינו סובל כלל מבעיית ביצועים, ולכן אנו יכולים בשלב זה להשאיר אותו כמות שהוא ללא התאמה מחדש לפילוסופית הארכיטקטורה מלבד הממשקים, כמו "פיצ'ר השירים #1". עם זאת, ייתכן שהלוגיקה שלו פשוט לא מתאימה יותר בכמה נקודות. למשל – ייתכן שתכונות מסויימות של בסיס הנתונים שבהן השתמש פיצ'ר זה באופן ייחודי, אינן קיימות בבסיס הנתונים החדש, ולכן נצטרך לשכתב מספר שלבים באלגוריתם. עדיין, שינוי כזה, בתוך לוגיקה כללית שנשארת ללא שינוי, יהיה מהיר ונקי בהרבה מאשר לשכתב את כל הפיצ'ר מחדש.
שלבי המעבר
כך, אם נמשיך באותה הדוגמא, נוכל לבצע את המיגרציה בין המערכות, למשל, בסדר הבא:
- כתיבת שלד הארכיטקטורה החדשה.
- כתיבת adapter המקבל קריאות שנכתבו ל-ORM הישן ומתרגם אותם לקריאות ל-ORM ולבסיס הנתונים החדש.
- כתיבת "מיקרו-שירות" גנרי, המאפשר ממשקי message-queue למודולים שנכתבו בצורה הפונקציונלית של המערכת הישנה.
- כתיבת מיקרו-שירות חדש, המשתמש ב-adapter, במיקרו-שירות הגנרי ובמודול הישן של "פיצ'ר שירים #1" כדי לקבל את הפיצ'ר בהתאם לארכיטקטורה החדשה.
- פירוק "פיצ'ר שירים #2" לשני חלקים, ושימוש ב-adapter ובמיקרו-שירות הגנרי כדי לאפשר לשני מיקרו-שירותים חדשים אלו לתת את אותו הפיצ'ר בהתאם לארכיטקטורה החדשה ובדרך שתמנע בעיות ביצועים.
- כתיבת מיקרו-שירות חדש, המשתמש ב-adapter, במיקרו-שירות הגנרי ובמודול הישן של "פיצ'ר שירים #3" לאחר ששוכתבו בו הקטעים שאין להם חלופה דומה ב-ORM החדש, כדי לקבל את הפיצ'ר בהתאם לארכיטקטורה החדשה.
- רק בשלב זה ניתן להעלות את המערכת, במקומה של המערכת הישנה, ולאפשר את אותם השירותים שניתנו בעבר – ייתכן שבצורה טובה יותר (למשל: עם פחות בעיות ביצועים בפיצ'ר #2) אך ייתכן שעם באגים חדשים.
- כתיבת שירותים חדשים (למשל: פיצ'רים הנוגעים לסרטים) בהתאם לפילוסופיה של הארכיטקטורה החדשה.
- לאורך זמן – החלפה של הפיצ'רים הישנים בפיצ'רים שנכתבו מלכתחילה בהתאמה לארכיטקטורה החדשה, למשל – מחולקים נכון למיקרו-שירותים.
הסכמה הבאה מתארת את שלבי המעבר השונים בין המערכת הישנה לחדשה, על פי השלבים שתוארו כאן.
מתי נכון להשתמש בגישה זו לשינוי ארכיטקטורה?
כמו בכל תחום בתוכנה, גם לשיטה זו יתרונות וחסרונות. בחלק מן המקרים היא תתאים בדיוק, בחלק אחר – לא ניתן יהיה להשתמש בה כלל, ובמקרים שונים ניתן יהיה לגזור ממנה כיוונים שונים המתאימים למקרה הספציפי.
יתרונות
- אין צורך לתחזק שתי מערכות במקביל
- המערכת החדשה תגיע בצורה מהירה יחסית לאותן יכולות של המערכת אותה היא מחליפה
- הלוגיקה הפנימית של כל פיצ'ר נשארת בשלב ראשון כמעט ללא שינוי, למרות המעבר למערכת חדשה
- תהליך השכתוב מחדש לפיצ'רים נעשה בצורה מסודרת, לאורך זמן ולפי תעדוף מתאים
- פיצ'רים פשוטים ויציבים יחסית שאינם דורשים תחזוקה (תיקונים/שיפורים) יכולים להישאר מאחורי adapter ולחסוך לנו זמן פיתוח יקר
חסרונות
- המערכת החדשה מחליפה לחלוטין את הישנה; ייתכנו לפיכך בעיות יציבות ובאגים שכבר נפתרו בעבר
- נבזבז זמן על כתיבת adapters זמניים שבסופו של דבר לא יהיה בהם צורך
- זמן העליה הראשוני של המערכת יהיה רק לאחר המרת כל הפיצ'רים הישנים, בניגוד לגישה המקבילית למשל
- "אין קבוע מן הזמני" – ללא תכנית מסודרת, עלולים חלק מהפיצ'רים להישאר במצב הביניים שלהם לזמן רב (זה לא בהכרח רע, ועשוי לחסוך זמן פיתוח; השאלה היא אם זה נעשה במתוכנן או מתכנון לקוי)
- השימוש בקוד שנכתב בגישה מסויימת בתוך מערכת שנכתבה עם פילוסופיה אחרת עלול להיות לא יעיל – למשל במשאבים, בשכפול מידע/חישוב, ביכולות מופחתות (היכולות הן קבוצת החיתוך של יכולות המערכת החדשה והישנה…)
מתי זה יתאים
- כאשר לא ניתן להריץ שתי מערכות במקביל
- כאשר חשוב שהמערכת החדשה תעלה במהירות יחסית עם הפיצ'רים הקודמים
- כאשר הפיצ'רים הקיימים ניתנים לשימוש בצורה סגורה יחסית מאחורי adapters עצמאיים
- כאשר ניתן להכיל ירידה מסויימת בביצועים/יציבות לטובת הקמה מהירה של המערכת החדשה
בפוסטים הבאים נמשיך ונתאר גישות אפשריות נוספות למעבר בין ארכיטקטורות.
[…] מודולים ישנים למערכת החדשה באמצעות adapters, כמו שהצעתי כאן, יהיה בלתי אפשרי לא פחות. למשל: במקרה בו ישנם יותר מדי […]