מה זה Idempotency ב-API¶
משתמש לוחץ על כפתור "בצע תשלום". הבקשה יוצאת לשרת, אבל לפני שהתשובה חוזרת, החיבור נופל. המשתמש, שלא רואה תשובה, לוחץ שוב על הכפתור. אם ה-API לא בנוי נכון, זה יכול לגרום לחיוב כפול על אותה עסקה. Idempotency הוא בדיוק המושג שפותר את הבעיה הזאת, והוא אחד הרעיונות החשובים ביותר בעיצוב API אמין.
אז מה זה בעצם Idempotency¶
פעולה נקראת אידמפוטנטית אם ביצוע שלה פעם אחת נותן בדיוק את אותה תוצאה כמו ביצוע שלה כמה פעמים ברצף. במילים אחרות - אפשר לקרוא לפעולה שוב ושוב, והמצב הסופי של המערכת יישאר זהה, כאילו היא בוצעה פעם אחת בלבד.
דוגמה פשוטה מהחיים - להדליק מתג אור זו פעולה אידמפוטנטית. לחצתם על המתג פעם אחת או שלוש פעמים, האור דלוק. לעומת זאת, להוסיף פריט לעגלת קניות זו לא פעולה אידמפוטנטית - כל לחיצה מוסיפה פריט נוסף, ושלוש לחיצות נותנות שלושה פריטים בעגלה במקום אחד.
אילו שיטות HTTP אידמפוטנטיות מטבען¶
לפי המפרט של HTTP, כמה שיטות אמורות להיות אידמפוטנטיות מעצם הגדרתן. GET אידמפוטנטי - קריאת מידע לא אמורה לשנות כלום, לא משנה כמה פעמים קוראים אותה. PUT אידמפוטנטי - אם אתם מעדכנים משאב לערך מסוים, לעדכן אותו שוב לאותו ערך נותן בדיוק אותה תוצאה. DELETE נחשב אידמפוטנטי גם הוא - מחיקת משאב שכבר נמחק פשוט לא משנה שום דבר נוסף.
POST, לעומת זאת, לא אידמפוטנטי מטבעו. POST משמש בדרך כלל ליצירת משאב חדש, וקריאה חוזרת יוצרת עוד משאב חדש - בדיוק כמו הוספת פריט לעגלה. וזו בדיוק הבעיה - כל פעולה שיוצרת משהו, כמו יצירת הזמנה או ביצוע תשלום, בדרך כלל מגיעה דרך POST, ולכן חשופה בטבעה לבעיית קריאה כפולה.
הפתרון - Idempotency Key¶
הדפוס הנפוץ לפתור את זה הוא Idempotency Key - מזהה ייחודי שהקליינט מייצר ומצרף לבקשה, לרוב ב-header. השרת שומר את המזהה הזה יחד עם התוצאה של הבקשה הראשונה. אם מגיעה בקשה נוספת עם אותו מזהה, השרת לא מבצע את הפעולה שוב - הוא פשוט מחזיר את התוצאה השמורה מהפעם הראשונה, כאילו הבקשה בוצעה עכשיו, בלי לגרום לתופעת לוואי כפולה.
כך, גם אם הקליינט שולח את אותה בקשה כמה פעמים בגלל ניתוק רשת או לחיצה כפולה, השרת מבצע את הפעולה בפועל רק פעם אחת. שירותי תשלומים גדולים מיישמים את הדפוס הזה בדיוק בגלל זה - הם דורשים מפתח idempotency לכל בקשת חיוב, ומבטיחים שגם אם הבקשה נשלחת כמה פעמים, החיוב יתבצע רק פעם אחת.
למה זה קריטי דווקא כשעובדים עם רשת¶
רשתות לא אמינות ב-100 אחוז. חיבור יכול ליפול באמצע בקשה, תשובה יכולה ללכת לאיבוד גם אם הבקשה בכל זאת הצליחה בצד השרת, ולקוחות רבים - אפליקציות מובייל בפרט - מיישמים לוגיקת retry אוטומטית כשמשהו נכשל. בלי idempotency, כל retry כזה על פעולה שיוצרת נתונים חדשים הוא סיכון אמיתי לכפילות. ברגע שה-API אידמפוטנטי, retry הופך לבטוח - אפשר לנסות שוב בלי חשש.
איך זה בא לידי ביטוי בעיצוב מסד הנתונים¶
מימוש idempotency לא נגמר בקוד ה-API בלבד. לרוב נדרש שדה ייעודי במסד הנתונים ששומר את מפתחות ה-idempotency שכבר טופלו, לצד אינדקס ייחודי שמונע רישום כפול גם אם שתי בקשות מגיעות ממש באותו רגע. זה מקום נוסף שבו עיצוב נכון של מסד הנתונים משתלב עם עיצוב API אמין.
איך לומדים ליישם את זה נכון¶
הרעיון של idempotency קל להבנה, אבל ליישם אותו נכון תחת עומס אמיתי, כולל טיפול במקרי קצה כמו שתי בקשות שמגיעות בו זמנית - זה נלמד הכי טוב תוך כדי בניית API אמיתי. בקורס צד-שרת בונים endpoints רגישים כמו תשלומים בצורה נכונה ובטוחה מפני קריאה כפולה.
בונים פעולה רגישה כמו תשלום או הזמנה ולא בטוחים איך להגן עליה מכפילות? זה בדיוק סוג השאלה שכדאי לשאול בקהילה שלנו.
לסיכום¶
Idempotency הוא ההבטחה שקריאה חוזרת לאותה פעולה לא תגרום נזק כפול, וזה קריטי בדיוק כי רשתות לא אמינות וקליינטים מנסים שוב כשמשהו נכשל. עבור כל פעולה שיוצרת או משנה נתונים בצורה בלתי הפיכה - ובמיוחד תשלומים - מפתח idempotency הוא לא תוספת נחמדה, הוא הגנה בסיסית שכל API רציני צריך.