תבנית Saga לעסקאות מבוזרות¶
במונוליט עם מסד נתונים אחד, טרנזקציה שחוצה כמה טבלאות פשוטה - BEGIN, כמה פעולות, COMMIT, ואם משהו נכשל באמצע, ROLLBACK מבטל הכל בבת אחת. במיקרוסרביסים, כשכל שירות מנהל מסד נתונים משלו, אין ROLLBACK גלובלי כזה. תבנית Saga קיימת בדיוק בשביל לפתור את הבעיה הזאת - איך מנהלים תהליך שחוצה כמה שירותים, ומבטיחים שהוא מסתיים בעקביות גם כשמשהו נכשל באמצע.
הבעיה - הזמנה שחוצה שלושה שירותים¶
נניח תהליך ביצוע הזמנה שחוצה שלושה שירותים - שירות מלאי שצריך לתפוס מוצר, שירות תשלומים שצריך לחייב, ושירות משלוחים שצריך ליצור משלוח. אם שלב התשלום נכשל אחרי ששלב המלאי כבר הצליח, המוצר כבר "נתפס" במלאי אבל אף אחד לא שילם עליו - צריך דרך מסודרת לבטל את תפיסת המלאי, לא רק להשאיר את המערכת במצב לא עקבי.
הפתרון - שרשרת פעולות עם פיצויים¶
Saga מגדירה את התהליך כשרשרת של פעולות מקומיות, כל אחת בשירות אחר, ולכל פעולה יש פעולת "פיצוי" (compensation) מוגדרת מראש שמבטלת אותה אם צריך.
1. שירות מלאי: תפוס מוצר → פיצוי: שחרר מוצר
2. שירות תשלומים: חייב כרטיס → פיצוי: זכה כרטיס
3. שירות משלוחים: צור משלוח → פיצוי: בטל משלוח
אם כל השלבים מצליחים, ה-Saga הושלמה בהצלחה. אם שלב כלשהו נכשל, המערכת מריצה את פעולות הפיצוי של כל השלבים שכבר הצליחו, בסדר הפוך, כדי להחזיר את המערכת למצב עקבי - לא זהה לפני שהתהליך התחיל בהכרח, אלא מצב "מפוצה" שמבטל את ההשפעות של השלבים שכבר קרו.
שתי דרכים למימוש - Choreography מול Orchestration¶
יש שתי גישות עיקריות למימוש Saga. בגישת Choreography, אין מתאם מרכזי - כל שירות מפרסם אירוע כשהוא מסיים שלב, והשירות הבא בשרשרת מאזין לאירוע ומתחיל את השלב שלו. זה דומה מאוד לארכיטקטורה מונעת אירועים - כל שירות עצמאי ולא צריך להכיר את כל השרשרת, רק את האירוע שהוא מקשיב לו.
בגישת Orchestration, יש שירות מתאם מרכזי (orchestrator) שמנהל את כל התהליך במפורש - שולח פקודה לכל שירות בתורו, ממתין לתשובה, ומחליט מה השלב הבא או אם צריך להריץ פיצויים. הגישה הזאת נותנת נראות ברורה יותר על כל התהליך במקום אחד, אבל יוצרת תלות בשירות המתאם עצמו.
Choreography מתאימה לתהליכים פשוטים יחסית עם מעט שלבים, כי היא נשארת מבוזרת ולא יוצרת נקודת כשל מרכזית. Orchestration מתאימה לתהליכים מורכבים עם הרבה שלבים ותנאים, כי הרבה יותר קל לעקוב אחרי הלוגיקה כשהיא כתובה במקום אחד, במקום מפוזרת בין אירועים בעשרה שירותים שונים.
עקביות סופית, לא עקביות מיידית¶
חשוב להבין ש-Saga לא נותנת את אותה הבטחה כמו טרנזקציה רגילה. במהלך הביצוע, יש חלון זמן שבו חלק מהשלבים כבר בוצעו וחלק לא - למשל, המלאי כבר נתפס אבל התשלום עוד לא בוצע. זו עקביות סופית (eventual consistency), לא עקביות מיידית. כל שירות שקורא נתונים באמצע Saga פעילה עלול לראות מצב ביניים זה, וצריך לקחת את זה בחשבון בעיצוב המערכת.
אידמפוטנטיות כתנאי הכרחי¶
בדיוק כמו במערכות תשלומים, כל שלב ב-Saga חייב להיות אידמפוטנטי - כי כשל רשת יכול לגרום לניסיון חוזר של אותו שלב, וגם פעולת הפיצוי יכולה להיקרא יותר מפעם אחת. בלי הבטחה הזאת, ניסיון חוזר עלול לגרום לתפיסת מלאי כפולה או חיוב כפול, בדיוק אותה בעיה שראינו בהקשרים אחרים - רק שכאן היא חוצה כמה שירותים בבת אחת.
בקורס ארכיטקטורת תוכנה שלנו עוברים על Saga לצד תבניות נוספות לניהול עסקאות מבוזרות במיקרוסרביסים.
בניתם תהליך שחוצה כמה שירותים והתלבטתם בין Choreography ל-Orchestration? הצטרפו לקהילה בדיסקורד ותספרו איך הכרעתם.
לסיכום¶
תבנית Saga מנהלת תהליך שחוצה כמה מיקרוסרביסים כשרשרת פעולות מקומיות, כל אחת עם פעולת פיצוי שמבטלת אותה אם שלב מאוחר יותר נכשל. Choreography מתאימה לתהליכים פשוטים ומבוזרים, Orchestration לתהליכים מורכבים שדורשים נראות ברורה במקום אחד - ובשני המקרים, כל שלב חייב להיות אידמפוטנטי כדי שהמערכת תישאר אמינה גם כשמשהו נכשל באמצע.