[ הרצאה · DOTB Meetup #2 · Frontend Night ]

Scaling the Codebase: Avoiding Monolithic Spaghetti

וידאו · 29 באפריל 2023 · 34:43 · גיל תייר

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

צפו בהרצאה · 34:43
תמונה ממוזערת של ההרצאה: Scaling the Codebase: Avoiding Monolithic Spaghetti
[ כתבת ההרצאה ]

הסיפור שמאחורי ההרצאה

[ תקציר מהיר ]
  • קוד גדל בלי להאט כשמחלקים אותו למודולים קטנים ועצמאיים.
  • מיקרו־שירותים מאפשרים להבין, לבדוק ולפרוס כל יחידה בלי להכיר את כולן.
  • טסטים לכל פקג' הם תנאי לשחרורים מהירים ובטוחים לפרודקשן.
  • מפרסמים קוד משותף כפקג'ים ומעדכנים תלות רק כשנכון לצרכן.
  • מונוריפו מנהל מאות פקג'ים; הוא לא מחייב לבנות את כולם יחד.

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

איך מגדילים קודבייס בלי להאט את הפיתוח

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

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

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

מודולריות במאקרו: הגבולות שמונעים ספגטי קוד

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

אבל גבול אמיתי נמדד בעצמאות תפעולית, לא בשם של תיקייה. לכל module צריך להיות package.json או קובץ dependency משלו, dependencies משלו, tests משלו ותהליך build משלו. אפשר לפתח אותו בלי לערוך module אחר, להריץ את הבדיקות שלו בלי להריץ את כולן, ולפרוס אותו בלי לפרוס את שאר המערכת. גיל מדגיש שלא מספיק שיהיה test end-to-end אי שם במערכת: אם package הוא יחידה עצמאית, הבדיקות צריכות לחיות איתו ולהגן עליו.

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

מונוריפו לניהול מאות פקג׳ים, לא לבנייה אחת גדולה

מונוריפו הוא כלי ניהול, לא סיבה לקשור את כל המערכת יחד. כשיש מאות packages, כפי שגיל מתאר מ-Round Forest, ניהול של repository נפרד לכל אחד מקשה על חיפוש, על גילוי שימושים ועל התפעול היומיומי. repository אחד נותן כתובת משותפת לקוד. זה היתרון.

הטעות מתחילה כשהמונוריפו הופך ל-unit של build, test ו-release. אם שינוי זעיר ב-utility מחייב לבדוק ולבנות את כל הארגון, הקוד אמנם נמצא במקום אחד אבל החיכוך נשאר. גיל מציע לחשוב על כל package כאילו היה יכול לגור ב-repository אחר. העובדה שהוא נמצא ליד האחרים היא נוחות אנושית ותפעולית, לא חוזה שמכריח אותם לזוז יחד.

גם common code לא חייב להפוך מיד לפרויקט מורכב. גיל מעדיף לדחות abstraction עד שמתגלה שימוש חוזר משמעותי, ורק אז ליצור library package שמפורסם ב-registry פרטי. הצרכן מקבל dependency מפורשת בגרסה מסוימת, בדומה ל-dependency על React או Lodash. שינוי ב-library לא כופה עדכון על כל מי שמשתמש בה. כל שירות יכול להתעדכן כשהוא מוכן ולבדוק את השדרוג בגבולות שלו.

למה Lerna ו-Nx לא מתאימים לכל מונוריפו

גיל אינו טוען ש-Lerna, Nx או Yarn Workspaces חסרי שימוש. מבחינתו הם יכולים להתאים לפרויקט עם מספר מצומצם של packages שצריכים להיבנות ולהתפרסם יחד, למשל אוסף plugins או design system שמכוון לשחרור אחד. במצב כזה התלות ההדוקה היא חלק מהדרישה.

אבל במונוריפו ארגוני גדול, עם עשרות רבות או מאות packages בלתי תלויים, אותם כלים עלולים לעודד את ההפך מעצמאות: workspace אחד, גרסה אחת זמינה לכל package, והרצה רוחבית אחרי כל שינוי. גיל מזהיר במיוחד מהמודל שבו עדכון package גורר ריצה ובדיקה של כל הצרכנים מיד. זה מכניס coupling תפעולי גם כשהקוד עצמו יכול היה להישאר loosely coupled.

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

לבנות, לבדוק ולפרוס כל מודול בנפרד

הדוגמה של גיל מתחילה בתיקון באג ב-math-utils. קודם מוסיפים test, אחר כך מריצים build ו-tests על אותו package, ורק כשאלה עוברים מפרסמים גרסה חדשה. לאחר מכן השירות tenants מעדכן את ה-dependency שלו לגרסה החדשה, מוסיף test אם צריך, נבנה, נבדק ומקבל artifact לפריסה, למשל Docker image. השירות לא צריך לחכות ל-release של כל המונוריפו כדי לקבל תיקון ממוקד.

לפי גיל, זו גם סיבה להריץ את התהליך קרוב למפתח. ב-Round Forest הוא מתאר build, tests ו-publish שמופעלים מקומית, ולא pipeline מרוחק שצריך לחכות לו שעות. ההמלצה הזו תלויה ביכולת לשחזר את סביבת הבדיקה ובאיכות ה-tests, ולכן היא אינה קיצור דרך. בלי tests טובים לכל package, הוא אומר, כל המודל קורס.

הבטיחות כאן היא תנאי למהירות. tests מאפשרים refactor, build קטן מאפשר משוב מהיר, ו-artifact עצמאי מאפשר deployment שאינו מערב שירותים אחרים. אפשר להתווכח על הפרטים, כולל על הסתמכות על production לאימות הסופי, אבל העיקרון נשאר: אם תהליך השחרור קצר, הוא חייב להיות מוגן היטב בבדיקות אוטומטיות.

דיפלויים קטנים שמקטינים סיכון ומחזירים מהירות

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

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

לכן frequency היא לא מדד vanity. היא בונה שליטה. משחררים יחידה קטנה, רואים שהיא מתנהגת נכון, וממשיכים. ואם טעינו, ה-blast radius קטן יותר כי מדובר בשירות אחד ובגרסה אחת שאפשר להחזיר.

למי ההרצאה הזאת

ההרצאה מתאימה למפתחים, Tech Leads וארכיטקטים שמרגישים שהקודבייס שלהם גדל מהר יותר מהיכולת לשנות אותו. היא שימושית במיוחד לצוותים עם כמה services או packages, ולמי ששוקל מונוריפו ומנסה להבין מה הוא פותר ומה הוא לא פותר. מי שיצפה יקבל מסגרת מעשית לבחינת גבולות מודולים, ownership של tests, versioning של libraries וקצב הדיפלויים - גם אם לא מאמצים את כל העמדות הנחרצות של גיל.

השורה התחתונה

  • מדדו סקיילינג לפי מהירות שינוי, לא לפי מספר השירותים או גודל ה-repository.
  • Module עצמאי אפשר לפתח, לבדוק, לבנות ולפרוס בלי לגרור modules אחרים.
  • מונוריפו נועד לנהל הרבה packages במקום אחד, לא לחייב build אחד גדול.
  • Common code צריך להיות dependency עם גרסה, לא צינור שמכריח את כל הצרכנים להתעדכן.
  • דיפלויים קטנים ותכופים מצמצמים את טווח החשד כשמשהו נשבר.
  • אין קיצור דרך: עצמאות מהירה נשענת על tests טובים בכל package.

המסר של גיל תייר אינו להעמיס עוד process על צוות שכבר איטי. להפך: מצאו את התלויות שמכריחות אתכם לחשוב רחב, הריצו פחות דברים עבור כל שינוי, והפכו כל שחרור לקטן ובטוח מספיק כדי שלא תפחדו ממנו. לצפייה בהרצאה המלאה: https://www.youtube.com/watch?v=f9a43B9VJmw.

"מודול עצמאי זה אומר שאפשר לקודד אותו בצורה עצמאית לגמרי,"

- גיל תייר

"כי אם הוא לא יהיה בטוח הוא לא יהיה מהיר, זה אחד וחלק."

- גיל תייר

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

- גיל תייר
[ פרקי ההרצאה ]
  1. 00:00:45
    כשהקוד מאט את הפיתוח
    גיל מציג את הבעיה: ככל שבסיס הקוד גדל, מהירות הפיתוח נוטה לרדת.
  2. 00:04:51
    שלושת עמודי התווך
    הגישה נשענת על ארכיטקטורת קוד מודולרית, הסרת חיכוך בתהליך העבודה ומונוריפו.
  3. 00:06:00
    דמו תיקון באג
    דרך דוגמת מיקרו־שירות, גיל מדגים תיקון באג שנמצא בפקג' משותף.
  4. 00:09:01
    בדיקה ופרסום מקומיים
    הפקג' נבדק ומפורסם מהמכונה המקומית, ואז הצרכן מעדכן לגרסה החדשה שלו.
  5. 00:13:05
    למה בלי סביבת פיתוח
    כשיש טסטים מהירים ואמינים, גיל מעדיף לבדוק ולשחרר ישירות לפרודקשן.
  6. 00:15:07
    מודולריות ברמת המאקרו
    הפתרון לספגטי בקודבייס גדול אינו עוד שכבות פנימיות, אלא חלוקה למודולים ברורים.
  7. 00:17:56
    מיקרו־שירותים לסקייל קוד
    מיקרו־שירות קטן מאפשר להבין, לדבג, לבדוק ולפרוס רק את החלק הרלוונטי.
  8. 00:19:20
    מודול עצמאי באמת
    כל מודול צריך להיות ניתן לקידוד, בדיקה, בנייה ופריסה באופן עצמאי לחלוטין.
  9. 00:22:22
    קוד משותף כפקג'
    קוד משותף הופך לספרייה עם גרסאות, כדי שתלות בין מודולים תישאר רופפת.
  10. 00:24:00
    פיתוח עם פחות חיכוך
    עבודה ביחידות קטנות מעניקה פוקוס, שליטה ומהירות בלי להמתין לצינור CI ארוך.
  11. 00:27:20
    שחרורים קטנים ותכופים
    פריסות של עשרות שורות מקלות לאתר תקלות ומקטינות משמעותית את הסיכון.
  12. 00:29:39
    מונוריפו למאות פקג'ים
    מונוריפו מרכז מאות פקג'ים לחיפוש ולניהול, בלי לחבר את מחזורי הבנייה שלהם.
[ נושאים ]

מה כוסה בהרצאה

#מתודולוגיה #בקאנד וענן #Architecture #JavaScript
[ שאלות נפוצות ]

שאלות מההרצאה

איך עושים סקיילינג לקוד בלי לפגוע במהירות הפיתוח?

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

מה הופך מודול בקוד לעצמאי?

מודול עצמאי מחזיק dependencies משלו, tests משלו ותהליך build משלו. אפשר לשנות אותו בלי לעבוד במקביל על מודולים אחרים, להריץ את הבדיקות שלו בלי test suite רוחבי, ולפרוס אותו בנפרד אם הוא שירות. עבור גיל, עצמאות כזאת היא המדד החשוב יותר מהחלוקה הפנימית למחלקות ושכבות.

מתי מונוריפו מתאים לארגון גדול?

מונוריפו מתאים כשצריך לנהל הרבה packages במקום אחד: לחפש שימושים, לנווט בקוד ולמנוע עומס תפעולי של מאות repositories. אבל הוא לא אמור להפוך את כולם ליחידה אחת של build או release. אם packages באמת צריכים להיבנות ולהתפרסם יחד, למשל בפרויקט קטן של plugins, orchestration משותף יכול להתאים.

למה דיפלויים קטנים ותכופים בטוחים יותר?

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

[ קהילה ]

רוצה לדבר על ההרצאה?

הצטרף לקהילת המפתחים שלנו ב-WhatsApp.