יש באגים שמתפוצצים. השרת נופל, המסך מלבין, כולם יודעים תוך שלוש דקות.
ואז יש את הסוג השני. הכול עובד. אף שגיאה בלוגים, אף התראה, אף 500. פשוט מדי פעם מגיעה למטבח של סניף אחד הזמנה שמישהו ביצע בסניף אחר לגמרי. הצוות מכין אותה, כי למה שלא יכינו — היא הופיעה על המסך שלהם. הלקוח האמיתי ממתין במקום אחר. אף אחד לא יודע מאיפה להתחיל לחפש.
זה הבאג שנקרא אצלנו פנימית "דליפת סניפים", והוא לימד אותנו יותר על ארכיטקטורה מכל באג אחר שנתקלנו בו.
הרקע
המערכת מנהלת רשת מסעדות עם כמה סניפים. כל סניף הוא ישות עסקית נפרדת: תפריט משלו, מלאי משלו, צוות משלו, קופה משלו. מבחינת הלקוח זו חוויה אחת. מבחינת המערכת אלה כמה עסקים שרק במקרה חולקים אותו קוד.
השאלה הבסיסית ביותר שהמערכת צריכה לענות עליה היא: באיזה סניף המשתמש הזה נמצא עכשיו?
מסתבר שזו לא שאלה בסיסית בכלל.
שלושה מקורות אמת שלא הסכימו
כשפתחנו את הקוד, גילינו שלזהות הסניף היו שלושה מקורות נפרדים, וכל אחד מהם היה נכון חלק מהזמן:
הראשון היה ה-URL. הלקוח נכנס לכתובת שמכילה את מזהה הסניף, וזה נראה כמו מקור אמת סביר. הוא גם היה — עד שמישהו שיתף לינק בוואטסאפ, או חזר לאתר מהיסטוריית הדפדפן, או לחץ על תוצאה ישנה בגוגל.
השני היה ה-state בצד הלקוח. הסניף שנבחר נשמר בזיכרון האפליקציה ובאחסון המקומי של הדפדפן. מהיר, נוח, ומסונכרן עם ה-URL בערך שמונים אחוז מהזמן. השאר היה מרחב הבאגים.
השלישי היה מה שהשרת חשב בזמן יצירת ההזמנה. וכאן הגיע החלק הכואב.
בכל מקום שבו השרת לא הצליח לקבוע בוודאות מהו הסניף, היה בקוד ביטוי שנראה תמים לחלוטין:
const companyId = resolveCompany(req) || 14;ארבע-עשרה. סניף ברירת המחדל. מישהו כתב את זה פעם אחת, כנראה בשלב שבו היה סניף אחד וזה היה נכון, ואז זה התפשט. מצאנו את הביטוי הזה בכ-38 מקומות שונים בקוד.
חשוב להבין מה || 14 באמת אומר. הוא לא אומר "אני לא יודע". הוא אומר "אני לא יודע, אז אני אנחש, ואני אנחש בביטחון מלא ובלי להגיד לאף אחד". זו הסיבה שלא הייתה אף שגיאה בלוגים. המערכת מעולם לא נכשלה. היא הצליחה — בסניף הלא נכון.
למה זה נשמע כמו באג קטן, ולא היה
הפיתוי כאן הוא לתקן את התסמין. למצוא את המקרה שבו הסניף אבד, לסגור אותו, ולהמשיך הלאה.
אבל חשבו רגע מה || 14 אומר בשפה עסקית. הוא אומר שהמערכת מוכנה לזקוף הכנסה לסניף אחד על עבודה שסניף אחר עשה. בבעלות אחת אולי זו טעות סטטיסטית. ברשת שבה לכל סניף יש דוח רווח והפסד משלו, זו בעיה שנוגעת בכסף של אנשים.
ואז יש את הצד השני — מלאי. סניף שקיבל הזמנה שאינה שלו הוריד מהמלאי שלו. הספירה בסוף היום לא הסתדרה. מישהו במחסן חיפש בקבוקים שנעלמו.
ברגע שהבנו את זה, זו הפסיקה להיות שאלה של תיקון באג והפכה לשאלה של ארכיטקטורה.
ההחלטה: מקור אמת אחד, והוא לא אצל הלקוח
הכלל שקבענו היה קצר: הדפדפן מציע, השרת מחליט.
הלקוח יכול לבקש סניף. דרך ה-URL, דרך כפתור, דרך מה שירצה. אבל שום דבר שמגיע מהדפדפן לא נכנס להזמנה לפני שהשרת אישר אותו מול המקור שלו. פונקציה אחת הפכה לשער היחיד שדרכו נקבעת זהות הסניף בהזמנה. אין דרך אחרת להיכנס.
מתחת לזה הנחנו cookie מסוג httpOnly שמחזיק את הסניף הפעיל. הבחירה ב-httpOnly הייתה מכוונת: קוד JavaScript בצד הלקוח לא יכול לקרוא אותו או לשנות אותו. לא בגלל שחשדנו במשתמשים, אלא בגלל שערך שאפשר לשנות מהקונסולה הוא ערך שיום אחד ישתנה בטעות — על ידי הקוד שלנו, לא של אף אחד אחר.
העגלה עברה למפתחות נפרדים לכל סניף. עד אז הייתה עגלה אחת, ומעבר בין סניפים גרר מוצרים איתו — מוצרים שלפעמים בכלל לא היו קיימים בתפריט של הסניף השני.
ואז בא החלק הכי לא נעים, ובעצם הכי חשוב: הסרנו את כל 38 ה-fallbacks. לא החלפנו אותם בסניף ברירת מחדל חכם יותר. הסרנו אותם. אם המערכת לא יודעת באיזה סניף היא נמצאת, היא נעצרת ואומרת את זה בקול.
זה נשמע כמו רגרסיה. בפועל זה ההפך. כישלון רועש שקורה פעם ביום עדיף על הצלחה שקטה ושגויה שקורה פעם בשבוע — את הראשון מתקנים ביום שהוא מופיע, ואת השני מגלים בסוף החודש, כשמישהו מנסה לסגור דוח.
איך יודעים שזה באמת נגמר
בשלב הזה סוויטת הבדיקות מנתה שבע בדיקות. שבע בדיקות למערכת שמנהלת רשת שלמה היא בעצם אמירה שאנחנו סומכים על מזל.
סיימנו את התהליך עם 38.
המספר עצמו לא מרשים במיוחד. מה שחשוב הוא מה הבדיקות האלה מכסות: כל תרחיש שבו זהות סניף עלולה ללכת לאיבוד. כניסה מלינק ישן. מעבר בין סניפים עם עגלה מלאה. שתי לשוניות פתוחות על שני סניפים שונים במקביל. חזרה אחורה בדפדפן באמצע תהליך תשלום. רענון עמוד בין הוספת מוצר לבין סליקה.
כל אחד מהתרחישים האלה היה פעם באג פוטנציאלי שהיינו מגלים ממטבח מבולבל. עכשיו הם נכשלים ב-CI, לפני שהקוד בכלל עולה.
מאז — אפס דליפות.
מה שלקחנו מזה
כל || בקוד הוא החלטה עסקית שהתחפשה להחלטה טכנית. הביטוי || 14 נראה כמו הגנה מפני קריסה. בפועל הוא היה מדיניות — "כשאנחנו לא בטוחים, נזקוף לסניף הזה" — שאף אחד מעולם לא אישר.
מקור אמת אחד הוא לא קפדנות אקדמית. שלושה מקורות שמסכימים ברוב המקרים גרועים יותר ממקור אחד שלפעמים מודה שאינו יודע, כי במקרה הראשון אין לך דרך לדעת מתי אתה טועה.
הבדיקה הטובה ביותר לארכיטקטורה היא לשאול איפה המערכת שקרנית. לא איפה היא נופלת — איפה היא ממשיכה בביטחון בלי שיש לה בסיס לכך. שם יושבים הבאגים היקרים.
אם אתם מפעילים מערכת שמשרתת יותר מסניף אחד, יותר מלקוח אחד או יותר מארגון אחד — שווה לחפש בקוד את הביטוי || ליד מזהה ישות. יש סיכוי סביר שתמצאו שם החלטה עסקית שאף אחד לא קיבל במודע.
אם מצאתם משהו כזה ואתם לא בטוחים כמה עמוק זה הולך — דברו איתנו. שיחת אפיון קצרה בדרך כלל מספיקה כדי לדעת אם זה תיקון נקודתי או שאלה ארכיטקטונית.