איך מעצבים מערכת התראות - מקרה בוחן¶
כמעט כל מוצר עם משתמשים צריך לשלוח להם התראות - הודעת פוש כשמישהו הגיב, מייל כשההזמנה נשלחה, SMS כשקוד אימות מוכן. זה נשמע כמו רכיב שולי, אבל מערכת התראות שנבנית נכון צריכה לשרת עשרות סוגי אירועים שונים, כמה ערוצי משלוח, וכמה ספקים חיצוניים - וזה הופך אותה למקרה בוחן מצוין להבנת הפרדת אחריות בעיצוב מערכות.
שלב 1 - הגישה הנאיבית ולמה היא נשברת¶
הגישה הכי מיידית היא לקרוא לשירות שליחת מייל או פוש ישירות מתוך הקוד שמייצר את האירוע - כשמשתמש מקבל תגובה, קוראים ל-send_push() באותו מקום בקוד. זה עובד לזמן קצר, אבל נשבר מהר - כל שינוי בספק ההתראות דורש שינוי בכל מקום שקורא לו, וקריאה סינכרונית לשירות חיצוני חוסמת את הבקשה המקורית עד שהוא עונה, גם אם המשתמש שיוצר את האירוע לא צריך לחכות לזה בכלל.
שלב 2 - הפרדה בין הפקה למשלוח¶
הפתרון הוא להפריד לגמרי בין שני שלבים - הפקת ההתראה, ומשלוח בפועל. השירות שמזהה שאירוע קרה - למשל שירות התגובות - רק שולח הודעה קטנה לתור עבודה, עם המידע הבסיסי - איזה משתמש, איזה סוג אירוע, ואילו נתונים רלוונטיים. שירות ההתראות הנפרד קורא מהתור, ומחליט מה לעשות איתו.
הפרדה כזאת נותנת שני יתרונות מיידיים - השירות שמייצר את האירוע לא חוסם ולא תלוי בזמינות של ספקי ההתראות, וכל הלוגיקה של "איך לשלוח" מרוכזת במקום אחד, ולא מפוזרת בעשרות מקומות בקוד.
שלב 3 - העדפות משתמש וניתוב לערוץ הנכון¶
לא כל אירוע צריך להישלח בכל הערוצים, ולא כל משתמש רוצה את אותו דבר. שירות ההתראות צריך לבדוק העדפות - האם המשתמש הזה כיבה התראות פוש על תגובות, האם הוא מעוניין במייל שיווקי. הבדיקה הזאת קורית לפני שנבחר ערוץ, כדי לא לשלוח משהו שהמשתמש כבר ביקש לא לקבל. חלק מהמערכות גם מיישמות עדיפות ערוצים - ננסה קודם פוש, ואם המשתמש לא פעיל באפליקציה כבר כמה ימים, ניפול חזרה למייל.
שלב 4 - עבודה מול ספקים חיצוניים לא אמינים¶
כאן נכנס החלק המעניין באמת. שליחת מייל בפועל עוברת דרך ספק חיצוני, שליחת SMS דרך ספק אחר, ושליחת פוש דרך שירות של Apple או Google. כל אחד מהם יכול להיות איטי, להחזיר שגיאה, או ליפול לגמרי. שירות ההתראות צריך להתמודד עם זה בלי לאבד הודעות - retry עם השהיה גדלה בין ניסיונות, ולפעמים גם מעבר לספק גיבוי אם הראשי לא זמין לאורך זמן. זה בדיוק המקום שבו דפוסים כמו Circuit Breaker נכנסים לתמונה - אם ספק מסוים נכשל שוב ושוב, עדיף להפסיק לנסות אותו זמנית ולעבור לגיבוי, במקום להמשיך לדפוק על דלת סגורה.
שלב 5 - עדכון סטטוס ומעקב¶
משתמשים ומפתחים כאחד רוצים לדעת אם הודעה נשלחה בהצלחה. שירות ההתראות שומר סטטוס לכל הודעה - ממתינה, נשלחה, נכשלה - ולרוב מקבל גם webhook חזרה מהספק החיצוני עם עדכון על מסירה בפועל, למשל אם משתמש פתח את המייל. זה מידע חשוב גם לצוות המוצר וגם לצורך דיבוג כשמישהו מתלונן שהוא לא קיבל התראה.
שלב 6 - בקרת קצב ומניעת הצפה¶
בלי בקרה, מערכת התראות יכולה בקלות להציף משתמש - עשרות התראות על אותו אירוע בטעות, או ריבוי התראות דומות תוך דקה. שכבת דה-דופליקציה ובקרת קצב לכל משתמש מונעת מצב שבו באג בשירות אחר גורם לשליחת אלף התראות לאותו אדם, מה שמוביל בדרך כלל להסרת האפליקציה מהר יותר מכל דבר אחר.
מקרה הבוחן הזה מדגים היטב איך תור הודעות ועיבוד אסינכרוני הופכים רכיב שנראה טכני לפתרון עמיד באמת. בקורס ארכיטקטורת תוכנה אנחנו בונים מקרי בוחן דומים ומראים איך העקרונות האלה חוזרים שוב ושוב במערכות שונות.
בניתם מערכת התראות והתמודדתם עם ספק חיצוני לא אמין? הצטרפו לקהילה בדיסקורד ותספרו איך פתרתם את זה.
לסיכום¶
מערכת התראות טובה מפרידה בין הפקת האירוע למשלוח בפועל דרך תור עבודה, מכבדת העדפות משתמש, ומתמודדת בעדינות עם ספקים חיצוניים שנופלים או מתעכבים. הערך המרכזי שלה הוא שהיא מרכזת לוגיקה מורכבת במקום אחד, במקום לפזר אותה בכל שירות שצריך לשלוח הודעה למשתמש.