• בלוג
  • מ Monolith ל Micro Services

מ Monolith ל Micro Services

15/07/2021

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

1. הגישה הקלאסית (Monolyth) לפיתוח יישומים

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

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

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

  3. בתוך האתר הציבורי יש מנגנון של סל קניות. משתמשים יכולים להוסיף לעצמם מוצרים לסל הקניות, לראות מה נמצא כרגע בסל, אולי לקבל הנחות (קנה 2 חולצות וקבל גרביים מתנה), להוציא מוצרים מהסל.

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

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

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

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

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

בתוך הקוד מודולים שונים (בדרך כלל class-ים) יטפלו בחלקים השונים של המערכת. כשמודול מסוים יצטרך מידע הוא יוכל לשלוף את המידע מבסיס הנתונים. כשמודול מסוים יצטרך להשתמש בפונקציונאליות של מודול אחר הוא יוכל פשוט להפעיל את הפונקציה.

2. מה טוב במונולית

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

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

  2. כשכל הקוד באותו פרויקט קל לבדוק תרחישים מורכבים באמצעות בדיקות End To End.

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

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

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

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

  7. קל לנהל גירסאות כשכל מה שיש לנו זה קוד אחד ענק וכל גירסה חדשה כוללת את הקוד כולו.

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

3. מה רע במונולית

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

  1. קשה לנו לגייס אנשים חדשים כי מתכנתים רוצים לעבוד בטכנולוגיה X ואצלנו המונולית כתוב בטכנולוגיה Y.

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

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

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

  5. קוד גדול מדי הוא גם חיסרון. כשמספר השורות במערכת עובר את המאות אלפים או אפילו מיליונים אין אף אחד בצוות שמכיר את כל חלקי המערכת ומתכנתים נדרשים לעבוד בסביבה אותה הם אינם מבינים עד הסוף.

4. איך Micro Services יכולים לעזור לנו

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

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

פיתוח בגישת Micro Services יכול לעזור לנו לפתור חלק מהאתגרים בפיתוח מונולית:

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

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

  3. בהעלאה לפרודקשן אפשר להעלות רק את הסרביס שהשתנה ולא צריך להעלות את המערכת כולה.

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

  5. קוד של מיקרו סרביס הוא הרבה יותר קטן מקוד של מערכת מלאה ויכול להסתכם במאות או אלפי שורות.

ב 25.3.2014 מרטין פולר וג'יימס לואיס פרסמו מאמר מפורסם בשם המופלא Microservices, בו הם מפרטים את הגישה, היתרונות והחסרונות שלה. אני ממליץ לכם לקרוא אותו.

5. אתגרים בפיתוח Micro Services

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

  1. ניהול זהות - כי עכשיו המידע על המשתמש מפוצל בין מספר בסיסי נתונים.

  2. ניטור - כדי לזהות התנהגות חריגה או עומס על סרביסים מסוימים

  3. התמודדות עם תקלות - כי פניה ל API היא מהלך הרבה פחות אמין מקריאת מידע ישירות מבסיס נתונים

  4. ניהול Log - כי כל סרביס רץ על מכונה נפרדת אבל עדיין צריך להיות מסוגלים לראות את כל מה שקרה במקום אחד

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

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

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

למרות האתגרים ארכיטקטורת Micro Services איתנו כדי להישאר. שירותי ענן כמו AWS ו Azure מספקים כלים מובנים לפיתוח בגישה זו וחברות גדולות רבות כבר עובדות כך. היכרות עם העולם, הכלים, האתגרים וההזדמנויות של Micro Services היא הכרחית למפתחי Full Stack היום.

בואו נמשיך לראות איך היינו ניגשים לבנות את מערכת החנות שתיארתי בהתחלה בגישת Micro Services.

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

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

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

  1. ניהול מוצרים

  2. ניהול מראה

  3. יצירת קמפיינים והנחות

  4. ניהול הזמנות

  5. ניהול משתמשים

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

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

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

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

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

פיתוח האתר הציבורי יהיה פרויקט Front End. הוא יכול להיות כתוב ב JavaScript או באיזשהו פריימוורק. הדבר החשוב כאן הוא שכל המידע מגיע מהשרת דרך קריאות API. לדפדפן לא אכפת אם הוא מדבר עכשיו עם סרביס X או סרביס Y, מאחר והוא פשוט פונה ל URL, מקבל את המידע ומציג אותו על המסך לגולש.

8. בתוך האתר הציבורי יש מנגנון של סל קניות.

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

סל הקניות יקבל בקשות מהדפדפן דרך ה API. כל גולש שמגיע לאתר יקבל מזהה ייחודי שיישמר אצלו ב Session, ואם הוא מבצע Login המזהה הזה גם יישמר בבסיס הנתונים במערכת המשתמשים (עוד סרביס) ויישלח אלינו לסל הקניות באמצעות HTTP Header. כשמשתמש רוצה להוסיף מוצר לסל הדפדפן ישלח בקשה לסל הקניות עם אותו מזהה ייחודי של הגולש ומזהה המוצר שהוא רוצה להוסיף לסל. סל הקניות לא צריך לשמור את פרטי המוצרים ומספיק לו להשתמש בבסיס נתונים קטן עם טבלה בודדת שמכילה מזהה משתמש, האם זה משתמש מאומת במערכת או אורח, ומזהה המוצר שהוא רוצה להוסיף.

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

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

כחלק מהסרביס, משימה מתוזמנת תרוץ פעם בשבוע כדי למחוק את סלי הקניות שלא מקושרים למשתמש רשום במערכת.

9. לקוחות החנות שרוצים לקנות צריכים להירשם לאתר

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

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

מנגנון של API Gateway יוודא שמשתמש מגיע לסרביס שדורש הרשמה רק אחרי שהוא נרשם והתחבר לאתר.

10. תשלום בקופה

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

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

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

11. הצעת מוצרים קשורים

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

דפדפן יוכל לפנות למערכת ההמלצות מתוך כל דף מוצר ולקבל רשימה של מוצרים קשורים להצגה.

משימה מתוזמנת תוכל לרוץ פעם ביום, לזהות מוצרים מומלצים ללקוחות מסוימים ולשלוח להם אימייל עם תקציר מוצרים אלה.

12. סיכום

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

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

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

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