ושוב אני חוזר לסדרת הפוסטים בנושא שדרוג מערכת, והפעם: דרך שלישית לשדרוג המערכת, המנסה להתגבר על חסרונות אפשריים בשתי הגישות הקודמות.
אפשרות שלישית: המערכת החדשה תספק פרוקסי למערכת הישנה
לפעמים, לא נוכל להריץ את שתי המערכות במקביל, כמו שהצעתי כאן. למשל: במקרה בו חייב להיות ממשק אחד לתפעול כל המוצר. יחד עם זאת, להעביר מודולים ישנים למערכת החדשה באמצעות adapters, כמו שהצעתי כאן, יהיה בלתי אפשרי לא פחות. למשל: במקרה בו ישנם יותר מדי קשרים בין מודולים, הקשורים קשר הדוק לארכיטקטורה הישנה. במקרה כזה, שני הפתרונות הקודמים אינם ישימים. כדי להתגבר על כך, אנו יכולים לבנות את הארכיטקטורה בצורה היברידית: המערכת החדשה היא זו שמולה מתממשקים, אך במקביל, רצה "מאחוריה" המערכת הישנה. המערכת החדשה תשמש סוג של פרוקסי עבור כל הרכיבים הנדרשים מן המערכת הישנה.
גישה זו דומה לגישה הראשונה, בכך שלמעשה, שתי המערכות רצות במקביל, והמערכת הראשונה ממשיכה לספק את השירותים שלה כל עוד הם לא הוטמעו בחדשה. מן הצד השני, היא דומה לגישה השניה בכך שהיא אינה חושפת את ממשקי המערכת הישנה, אלא ניגשת אליה מאחורי הקלעים. ההבדל הוא, שבמקרה הזה, לא מדובר ב-adapter למודול מסויים, אלא בגישה למערכת שלמה שנמצאת מאחור.
כיצד זה עובד, שלב אחר שלב
כך, בשלב הראשון, נבנה את השלד של המערכת החדשה, נבדוק אותו ונוודא כי הוא בשל מספיק. לאחר מכן, נספק את הפרוקסי. החוכמה בשיטת עבודה זו, והיתרון (האפשרי) שלה על פני שימוש ב-adapters, הוא שהיא אינה מתחברת לכל מודול אלא משמשת פרוקסי למערכת שלמה. למשל, אם ה-API שלנו כולל מספר גרסא – נוכל בקלות להחליט שכל API בגרסא נמוכה מ-X.yz יופנה ישירות למערכת הקודמת. אם ה-API אינו כולל מספר גרסא, נוכל להחזיק רשימה של כל גישות ה-API של המערכת הישנה ולהפנות אותן לשם. לחילופין, נוכל תמיד להפנות כל פניה ל-API שלא קיבלה מענה מן המערכת החדשה אל המערכת הישנה. הדרך הספציפית תהיה זו שתתאים למערכת המסויימת שלנו.
חשוב לשים לב לסוג הערכים החוזרים מן המערכת הישנה. כך, למשל, אם היא מחזירה ערך כלשהו, ייתכן שלא נרצה לגעת בו כלל. אם היא מחזירה סט ערכים, שאחד מהם הוא לינק למערכת (למשל, כתובת ומספר פורט), אולי נצטרך לבצע על כל ערך חוזר מניפולציה, שמספקת את הלינק לממשק המערכת ולא למערכת הישנה, שאינה חשופה. אם היא מחזירה דף אינטרנט לדפדפן, אולי נרצה לשנות בו כמה פרטים, כמו הפניות ל-CSS, כך שה-Look and Feel שלהם יתאים לגרסא החדשה של המוצר.
כעת נוכל לוודא שהמערכת החדשה מספקת פרוקסי נכון למוצר, אשר פועל כראוי ומאפשר את כל התכונות והיכולות שסיפקה המערכת המקורית (בהתאמות הנדרשות, אם ישנן). בשלב זה נוכל כבר לצאת ל-Production עם המוצר החדש. היכולות שלו אמורות להיות לא פחותות מהמערכת הקודמת. היציבות שלו עשויה להיות טובה פחות – כיוון שמלבד יציבות המערכת המקורית נוספה גם יציבות המערכת החדשה. לאחר שיש לנו מוצר שמספק את "כל מה שהיה שם קודם", נוכל להתקדם הלאה ולכתוב פיצ'רים למערכת החדשה, או להעביר פיצ'רים מהמערכת הישנה לחדשה.
צעד אחד קדימה: שיפור המערכת הישנה בעזרת הפרוקסי
למרות שכאמור, הרצה של שתי המערכות זו מאחורי זו, עלולה לגרום ירידה ביציבות או בביצועים, לעיתים, נוכל להשיג דווקא שיפור בתכונות אלו וגם באחרות. יכולת זו תתקבל אם במקום פרוקסי "טיפש", שרק מעביר קריאות לשני הכיוונים, נכניס לפרוקסי לוגיקה הפותרת בעיות קיימות. הנה כמה דוגמאות קלאסיות:
פתרון בעיות תזמון. נחשוב על מקרים בהם המערכת הישנה היתה פגיעה בשל תזמוני I/O (למשל, מצבי race condition שלא טופלו היטב). ייתכן ומערכת תזמונים פשוטה בפרוקסי יכולה למנוע את התקלות. למשל, סידור של פניות ממספר רב של clients דרך message-queue או מערכת דומה. מבחינת המערכת הישנה, לא היה כל שינוי. ובכל זאת, כבר בשלב הראשון, ועוד לפני שכתבנו פיצ'ר אחד חדש על גבי המערכת החדשה, פתרנו בעיה רצינית.
פתרון בעיות משאבים. נחשוב על מקרים בהם המערכת הישנה לא היתה מסוגלת לעמוד בביצועים, אך היתה בלתי ניתנת לחלוקה ולביזור בשל המבנה שלה. אולי נוכל (זה כמובן לא מתאים לכל מערכת) להרים יותר מ-Instance אחד של המערכת הישנה בצורה מבוזרת – בתהליכים נפרדים, על ליבות נפרדות או על מחשבים נפרדים. כעת, נוסיף במערכת החדשה מנגנון של Load Balancing, שאמנם מעביר כל בקשה כמות-שהיא למערכת הישנה – אבל מפזר את הקריאות בין מספר מערכות כאלו. שוב, מבלי לשנות ולו שורת קוד אחת במערכת הישנה, קיבלנו מערכת הרבה יותר חזקה.
פתרון בעיות במצבי קצה. אם המערכת הישנה שלנו היתה פחות חסינה לקלט מסוגים שונים, נוכל להפעיל בפרוקסי לוגיקה שמאפשרת אך ורק מעבר לקלט חוקי. נקבל את המערכת המקורית, אבל חסינה בהרבה ועם הרבה פחות תקלות.
ואולי אפילו – שני צעדים קדימה?
אולי זה נשמע כאילו יהיה צורך להשקיע הרבה עבודה בממשקים מול המערכת הישנה, במקום "להתקדם" בכיוון של הארכיטקטורה החדשה. בפועל, מרבית הסיכויים הם שמדובר בצעדים באותם כיוונים בדיוק. סביר להניח שהארכיטקטורה החדשה נוצרה, לפחות בין היתר, על מנת לפתור בדיוק את הבעיות הללו של המערכת הישנה. לפיכך, היא כנראה תכיל את המנגנונים המתאימים בכל מקרה – בין אם מדובר ב-FIFO לתזמון פעולות, ב-Load Balancer או במנגנונים לוידוא נכונות קלט. כל מה שצריך יהיה לעשות זה "לחווט" את המנגנונים האלו לכיוון המערכת הישנה.
יותר מזה: בחלק מהמקרים, ייתכן שהקמה של "פרוקסי חכם" מסוג זה, תייתר את הצורך בארכיטקטורה חדשה. במקרים כאלו, נוכל לוותר על התקורה העצומה (אם כי – המהנה מאוד, יש להודות) של בניית מערכת חדשה לחלוטין. במקום זאת, נוכל להסתפק בשכבת ארכיטקטורה נוספת, שמשתמשת במערכת הישנה ללא שינוי, אך פותרת את בעיותיה העיקריות.
איך זה עובד? דוגמת זיהוי השירים
כרגיל, נדגים את המעבר עבור ה"מערכת לזיהוי שירים" אותה תיארנו בפוסטים הקודמים. במערכת זו, נוכל לחשוב על השלבים הבאים:
- בניית שלד הארכיטקטורה החדשה
- הוספת פרוקסי למערכת הישנה, כך שכל קריאת API תחת "/System/Api/Songs" תופנה למערכת הישנה
- הוספת רכיב "Count Checker" לפרוקסי, המוודא שאין יותר ממספר מסויים של קריאות בכל רגע נתון, ואם הגיעה בקשה נוספת, הוא מחזיר הודעת שגיאה
- הוספת הפיצ'רים החדשים (פיצ'רים לסרטים, תחת "/System/Api/Movies") במערכת החדשה
- העברה של הפיצ'רים הישנים למערכת החדשה
- הורדה של המערכת הישנה

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