सागा पैटर्न: माइक्रोसर्विसेज आर्किटेक्चर में वितरित लेनदेन

सागा पैटर्न: माइक्रोसर्विसेज आर्किटेक्चर में वितरित लेनदेन

पारंपरिक अखंड अनुप्रयोगों में, कई संस्थाओं में डेटा स्थिरता बनाए रखना सीधा है। रिलेशनल डेटाबेस इंजन स्थानीय SQL लेनदेन के अंदर ACID (परमाणुता, संगति, अलगाव, स्थायित्व) गारंटी प्रदान करते हैं। यदि कोई ऑर्डर प्लेसमेंट, भुगतान कटौती, या इन्वेंट्री रिजर्व आधे रास्ते में विफल हो जाता है, तो ROLLBACK को कॉल करने से प्रत्येक डेटाबेस संशोधन तुरंत वापस आ जाता है।

हालाँकि, आधुनिक माइक्रोसर्विसेज आर्किटेक्चर में स्थानांतरित होने पर, डेटा प्रबंधन मौलिक रूप से बदल जाता है। डोमेन स्वायत्तता और स्वतंत्र स्केलेबिलिटी सुनिश्चित करने के लिए, प्रत्येक माइक्रोसर्विस के पास अपना निजी डेटाबेस होता है। एक एकल व्यवसाय संचालन - जैसे कि ई-कॉमर्स चेकआउट संसाधित करना - अब कई सेवा सीमाओं और डेटाबेस इंजनों (उदाहरण के लिए, ऑर्डर के लिए पोस्टग्रेएसक्यूएल, भुगतान के लिए डायनेमोडीबी, इन्वेंटरी के लिए रेडिस) तक फैला हुआ है।

क्योंकि वितरित माइक्रोसर्विसेज एकल डेटाबेस लेनदेन पर भरोसा नहीं कर सकते हैं, नेटवर्क सीमाओं के पार डेटा स्थिरता बनाए रखना वितरित सिस्टम इंजीनियरिंग में सबसे चुनौतीपूर्ण समस्याओं में से एक बन जाता है।

सिस्टम की उपलब्धता या प्रदर्शन से समझौता किए बिना इसे हल करने के लिए, सॉफ़्टवेयर आर्किटेक्ट सागा पैटर्न पर भरोसा करते हैं।

इस गहन गोता में, हम यह पता लगाएंगे कि पारंपरिक वितरित लेनदेन विफल क्यों होते हैं, सागा पैटर्न के मूल यांत्रिकी को तोड़ेंगे, कोरियोग्राफी बनाम ऑर्केस्ट्रेशन की तुलना करेंगे, अलगाव काउंटरमेशर्स का विश्लेषण करेंगे, गो और जावा में उत्पादन कोड कार्यान्वयन की जांच करेंगे, और सीखेंगे कि वास्तविक दुनिया विफलता रोलबैक को सुरक्षित रूप से कैसे संभालना है।


मूल समस्या: माइक्रोसर्विसेज में दो-चरणीय प्रतिबद्धता (2पीसी) विफल क्यों होती है

सागा पैटर्न को अपनाने से पहले, इंजीनियर अक्सर पूछते हैं: हम अपने माइक्रोसर्विसेज़ में पारंपरिक दो-चरण कमिट (2PC / XA) ​​का उपयोग क्यों नहीं कर सकते?

2पीसी की यांत्रिकी

दो-चरणीय कमिट दो चरणों में एक केंद्रीय लेनदेन प्रबंधक का उपयोग करके कई डेटाबेस नोड्स में वितरित लेनदेन का समन्वय करती है:

  1. तैयारी चरण: लेनदेन प्रबंधक सभी भाग लेने वाले डेटाबेस नोड्स को आवश्यक पंक्तियों को तैयार करने और लॉक करने के लिए कहता है। प्रतिभागी YES या NO वोट करते हैं।
  2. प्रतिबद्ध चरण: यदि सभी प्रतिभागियों ने YES को वोट दिया है, तो प्रबंधक सभी नोड्स को COMMIT कमांड जारी करता है। यदि किसी नोड ने NO वोट किया है या समय समाप्त हो गया है, तो यह ROLLBACK जारी करता है।
Client ----> Transaction Manager
                   |
     +-------------+-------------+
     | (Prepare)   | (Prepare)   | (Prepare)
     v             v             v
 Order DB     Payment DB    Inventory DB

क्यों 2पीसी माइक्रोसर्विसेज के लिए एक एंटी-पैटर्न है

जबकि 2PC मजबूत स्थिरता की गारंटी देता है, यह कई वास्तुशिल्प दोषों के कारण क्लाउड-नेटिव माइक्रोसर्विस वातावरण में टूट जाता है:

  1. ब्लॉकिंग और संसाधन विवाद: मल्टी-स्टेज नेटवर्क हैंडशेक के दौरान डेटाबेस पंक्तियाँ लॉक रहती हैं। यदि नेटवर्क विलंबता बढ़ जाती है या कोई सेवा धीमी हो जाती है, तो ताले खुले रह जाते हैं, जिससे कनेक्शन पूल तेजी से खत्म हो जाते हैं और कैस्केडिंग सिस्टम विफलता हो जाती है।
  2. उपलब्धता बाधा: 2PC में, सिस्टम उपलब्धता सभी भागीदार उपलब्धताओं के उत्पाद से बंधी होती है ($A_{कुल} = A_1 \times A_2 \times \dots \times A_n$)। यदि कोई एकल सेवा या डेटाबेस नोड तैयारी चरण के दौरान ऑफ़लाइन हो जाता है, तो संपूर्ण वैश्विक लेनदेन अनिश्चित काल के लिए अवरुद्ध हो जाता है।
  3. सेवा स्वायत्तता का नुकसान: 2PC सेवाओं को नेटवर्क एपीआई में डेटाबेस-स्तरीय XA प्रोटोकॉल को उजागर करने के लिए बाध्य करता है, जिससे उनके डेटाबेस इंजन सीधे जुड़ जाते हैं।
  4. ब्रोकर बाधाएं: अपाचे काफ्का या रैबिटएमक्यू जैसे उच्च-थ्रूपुट संदेश ब्रोकर रिलेशनल डेटाबेस में पारंपरिक XA 2PC लेनदेन में मूल रूप से भाग नहीं लेते हैं।

CAP प्रमेय के अनुसार, वितरित सिस्टम को नेटवर्क विभाजन (पी) के तहत मजबूत संगति (सी) और उच्च उपलब्धता (ए) के बीच चयन करना होगा। आधुनिक माइक्रोसर्विस सिस्टम उपलब्धता और विभाजन सहिष्णुता को प्राथमिकता देते हैं, अंतिम स्थिरता के लिए त्वरित स्थिरता का व्यापार करते हैं (आधार: मूल रूप से उपलब्ध, नरम-स्थिति, अंतिम स्थिरता)।


सागा पैटर्न क्या है?

सागा पैटर्न मूल रूप से 1987 में डेटाबेस प्रबंधन प्रणालियों में दीर्घकालिक लेनदेन को संभालने के लिए एक तंत्र के रूप में हेक्टर गार्सिया-मोलिना और केनेथ सलेम द्वारा प्रस्तावित किया गया था। आधुनिक माइक्रोसर्विसेज में, सागा अलग-अलग स्थानीय लेनदेन के अनुक्रम का प्रतिनिधित्व करता है।

संपूर्ण बहु-सेवा प्रवाह को एक वैश्विक लॉक के अंदर लपेटने के बजाय, एक सागा स्वतंत्र स्थानीय डेटाबेस लेनदेन ($T_1, T_2, \dots, T_n$) की एक श्रृंखला के रूप में एक व्यावसायिक प्रक्रिया को निष्पादित करता है।

प्रत्येक स्थानीय लेनदेन एकल माइक्रोसर्विस के डेटाबेस में डेटा अपडेट करता है और एक डोमेन ईवेंट या संदेश प्रकाशित करता है। यह ईवेंट डाउनस्ट्रीम सेवा में अगले स्थानीय लेनदेन ($T_{i+1}$) को ट्रिगर करता है।

[Order Service]         [Payment Service]       [Inventory Service]
  Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order)              (Process Payment)            (Reserve Stock)

फॉरवर्ड निष्पादन बनाम पिछड़ा मुआवजा

यदि सभी स्थानीय लेनदेन सफल हो जाते हैं, तो सागा सफलतापूर्वक पूरा हो जाता है (फॉरवर्ड निष्पादन)।

हालाँकि, यदि कोई स्थानीय लेनदेन आधे रास्ते में विफल हो जाता है (उदाहरण के लिए, $T_3$ विफल हो जाता है क्योंकि कोई आइटम स्टॉक से बाहर है), तो सागा पिछले चरणों ($T_1, T_2$) के लिए डेटाबेस ROLLBACK को कॉल नहीं कर सकता है क्योंकि वे स्थानीय लेनदेन पहले से ही अपने संबंधित डेटाबेस के लिए प्रतिबद्ध हैं।

पहले से प्रतिबद्ध स्थानीय लेनदेन को पूर्ववत करने के लिए, सागा को क्षतिपूर्ति लेनदेन ($C_{n-1}, \dots, C_1$) को विपरीत क्रम में निष्पादित करना होगा।

Forward Flow:    T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
                                                                       |
Backward Rollback:  C1 (Cancel Order) <--- C2 (Refund Card) <----------+

सागा लेनदेन का वर्गीकरण

एक मजबूत सागा वर्कफ़्लो को डिज़ाइन करने के लिए, लेनदेन अनुक्रम में प्रत्येक चरण को तीन संरचनात्मक प्रकारों में से एक में वर्गीकृत किया जाना चाहिए:

लेनदेन प्रकार विवरण निष्कामता एवं रोलबैक आवश्यकता
क्षतिपूर्ति योग्य लेनदेन बिना वापसी के बिंदु से पहले क्रियान्वित चरण। यदि डाउनस्ट्रीम चरण विफल हो जाता है तो उन्हें पूर्ववत या उलटा किया जा सकता है। संगत क्षतिपूर्ति लेनदेन ($C_i$) होना चाहिए.
पिवोट लेनदेन गाथा में निश्चित कदम. यदि धुरी सफल हो जाती है, तो गाथा समाप्त होने की गारंटी है। यदि यह विफल हो जाता है, तो गाथा वापस आ जाती है। न तो क्षतिपूर्तियोग्य और न ही पुनःप्रयासयोग्य; यह रोलबैक और फॉरवर्ड पूर्णता के बीच की सीमा को चिह्नित करता है।
पुनर्प्राप्ति योग्य लेनदेन पिवोट लेनदेन के बाद निष्पादित चरण। उन्हें अंततः सफल होने की गारंटी दी जाती है और उन्हें मुआवजे की आवश्यकता नहीं होती है। सख्ती से निष्क्रिय होना चाहिए, क्योंकि सफल होने तक उन्हें स्वचालित रूप से पुनः प्रयास किया जाएगा।

ई-कॉमर्स चेकआउट उदाहरण ब्रेकडाउन

चार चरणों वाले ई-कॉमर्स ऑर्डर चेकआउट पर विचार करें:

  1. $T_1$: लंबित ऑर्डर बनाएं (क्षतिपूर्ति योग्य) $\to$ $C_1$ के माध्यम से पूर्ववत करें: ऑर्डर रद्द करें
  2. $T_2$: अधिकृत भुगतान (क्षतिपूर्ति योग्य) $\to$ $C_2$ के माध्यम से पूर्ववत करें: रिफंड भुगतान
  3. $T_3$: रिजर्व इन्वेंटरी (पिवोट ट्रांजेक्शन) $\to$ यदि स्टॉक आवंटन सफल होता है, तो ऑर्डर को अंतिम रूप दिया जाता है। यदि यह विफल रहता है, तो $C_2$ और $C_1$ को ट्रिगर करें।
  4. $T_4$: प्रेषण शिपिंग अनुरोध (पुनर्प्राप्ति योग्य) $\to$ धुरी के बाद निष्पादित; वितरित होने तक पुनः प्रयास किया गया।

सागा वास्तुकला शैलियाँ: कोरियोग्राफी बनाम आर्केस्ट्रा

वितरित प्रणालियों में सागा पैटर्न को लागू करने के लिए दो प्राथमिक वास्तुशिल्प शैलियाँ हैं: कोरियोग्राफी (विकेंद्रीकृत) और ऑर्केस्ट्रेशन (केंद्रीकृत)।

सागा पैटर्न कार्यान्वयन शैलियाँ: कोरियोग्राफी बनाम ऑर्केस्ट्रेशन वास्तुकला आरेख

शैली 1: कोरियोग्राफी (घटना-संचालित विकेंद्रीकरण)

कोरियोग्राफी-आधारित गाथा में, कोई केंद्रीय नियंत्रक या समन्वयक नहीं होता है। इसके बजाय, माइक्रोसर्विसेज एक केंद्रीय इवेंट बस (जैसे अपाचे काफ्का, एनएटीएस, या रैबिटएमक्यू) में प्रकाशित डोमेन घटनाओं को सुनकर अतुल्यकालिक रूप से संचार करते हैं।

वर्कफ़्लो यांत्रिकी

  1. ऑर्डर सेवा $T_1$ निष्पादित करती है (लंबित ऑर्डर बनाती है) और काफ्का को एक OrderCreated ईवेंट भेजती है।
  2. भुगतान सेवा OrderCreated को सुनती है, $T_2$ निष्पादित करती है (क्रेडिट कार्ड से शुल्क लेती है), और एक PaymentCompleted ईवेंट (या PaymentFailed) उत्सर्जित करती है।
  3. इन्वेंटरी सेवा PaymentCompleted को सुनती है, $T_3$ (आइटम आरक्षित) निष्पादित करती है। यदि आइटम स्टॉक से बाहर हैं, तो यह InventoryReservationFailed उत्सर्जित करता है।
  4. भुगतान सेवा InventoryReservationFailed को सुनती है और $C_2$ निष्पादित करती है (रिफंड जारी करती है)।
  5. ऑर्डर सेवा PaymentRefunded को सुनती है और $C_1$ निष्पादित करती है (ऑर्डर को रद्द के रूप में चिह्नित करती है)।

कोरियोग्राफी के फायदे

  • ढीला युग्मन: सेवाएँ केवल ईवेंट विषयों की सदस्यता लेती हैं; उन्हें अन्य सेवा कार्यान्वयन के बारे में जानकारी नहीं है.
  • उच्च थ्रूपुट और विकेंद्रीकरण: डायरेक्ट इवेंट पब/सब केंद्रीय ऑर्केस्ट्रेटर बाधाओं को समाप्त करता है।
  • छोटे वर्कफ़्लो के लिए सरलता: सरल 2-3 चरण वाले वर्कफ़्लो के लिए सेट अप करना आसान है।

कोरियोग्राफी के नुकसान

  • चक्रीय निर्भरता जोखिम: सेवाएँ एक-दूसरे की घटनाओं को सुन सकती हैं, जिससे जटिल चक्रीय निर्भरताएँ पैदा हो सकती हैं।
  • कठिन कोड ट्रैसेबिलिटी: एंड-टू-एंड बिजनेस प्रक्रिया को समझने के लिए कई कोडबेस रिपॉजिटरी में तर्क का पता लगाने की आवश्यकता होती है।
  • इवेंट स्टॉर्मिंग और जटिलता: जैसे-जैसे चरणों की संख्या बढ़ती है (उदाहरण के लिए, 10+ सेवाएँ), त्रुटि किनारे के मामलों को प्रबंधित करने से इवेंट स्टेट विस्फोट होता है।

शैली 2: ऑर्केस्ट्रेशन (केंद्रीकृत वर्कफ़्लो प्रबंधन)

ऑर्केस्ट्रेशन-आधारित सागा में, सागा ऑर्केस्ट्रेटर के नाम से जाना जाने वाला एक समर्पित माइक्रोसर्विस वितरित लेनदेन के संपूर्ण जीवनचक्र को नियंत्रित करता है। ऑर्केस्ट्रेटर एक केंद्रीय समन्वयक के रूप में कार्य करता है, जो भाग लेने वाले माइक्रोसर्विसेज को स्पष्ट आदेश जारी करता है और उनकी प्रतिक्रिया घटनाओं को सुनता है।

वर्कफ़्लो यांत्रिकी

  1. ग्राहक सागा ऑर्केस्ट्रेटर को ऑर्डर अनुरोध भेजता है।
  2. ऑर्केस्ट्रेटर ऑर्डर सर्विस को CreateOrder कमांड भेजता है। ऑर्डर सेवा OrderCreated लौटाती है।
  3. ऑर्केस्ट्रेटर अपनी स्टेट मशीन को अपडेट करता है और भुगतान सेवा को ProcessPayment कमांड भेजता है। भुगतान सेवा PaymentSuccessful लौटाती है।
  4. ऑर्केस्ट्रेटर इन्वेंटरी सर्विस को एक ReserveInventory कमांड भेजता है। इन्वेंटरी सेवा InventoryFailed (Out of Stock) लौटाती है।
  5. ऑर्केस्ट्रेटर विफलता का पता लगाता है, मुआवजा प्रवाह शुरू करता है:
    • भुगतान सेवा को RefundPayment कमांड भेजता है।
    • ऑर्डर सर्विस को CancelOrder कमांड भेजता है।
  6. ऑर्केस्ट्रेटर सागा निष्पादन को FAILED के रूप में चिह्नित करता है।

आर्केस्ट्रा के लाभ

  • केंद्रीकृत व्यापार तर्क: वर्कफ़्लो स्थिति और व्यावसायिक तर्क एक एकल ऑर्केस्ट्रेटर सेवा या राज्य मशीन में स्थानीयकृत होते हैं।
  • कोई चक्रीय निर्भरता नहीं: माइक्रोसर्विसेज ऑर्केस्ट्रेटर के आदेशों का जवाब देते हैं; वे अन्य डाउनस्ट्रीम सेवाओं पर निर्भर नहीं हैं या उनके बारे में नहीं जानते हैं।
  • स्पष्ट मॉनिटरिंग और डिबगिंग: एंड-टू-एंड लेनदेन स्थिति स्पष्ट रूप से ऑर्केस्ट्रेटर के स्टेट स्टोर (उदाहरण के लिए, पोस्टग्रेएसक्यूएल या टेम्पोरल / कैमुंडा जैसे वर्कफ़्लो इंजन) में संग्रहीत की जाती है।
  • त्रुटि प्रबंधन में आसान: नए चरण जोड़ना या रोलबैक नियम बदलना पूरी तरह से ऑर्केस्ट्रेटर के अंदर प्रबंधित किया जाता है।

आर्केस्ट्रा के नुकसान

  • ऑर्केस्ट्रेटर जटिलता: ऑर्केस्ट्रेटर में बहुत अधिक डोमेन लॉजिक रखने का जोखिम, इसे “स्मार्ट ऑर्केस्ट्रेटर, डंब सर्विस” एंटी-पैटर्न में बदल देता है।
  • विफलता का संभावित एकल बिंदु: ऑर्केस्ट्रेटर को अत्यधिक उपलब्ध और स्टेटफुल बनाया जाना चाहिए।

तुलनात्मक मैट्रिक्स: कोरियोग्राफी बनाम ऑर्केस्ट्रेशन

फ़ीचर कोरियोग्राफी आर्केस्ट्रा
नियंत्रण संरचना विकेंद्रीकृत (इवेंट पब/उप) केंद्रीकृत (सागा समन्वयक/राज्य मशीन)
युग्मन बेहद कम (सेवाएँ घटनाओं का उपभोग करती हैं) माध्यम (सेवाएँ समन्वयक से आदेश स्वीकार करती हैं)
प्रक्रिया दृश्यता निम्न (लॉग फ़ाइलों में वितरित) उच्च (एकल राज्य स्टोर वर्कफ़्लो की कल्पना करता है)
के लिए सबसे उपयुक्त सरल कार्यप्रवाह (2 से 4 सेवा चरण) जटिल उद्यम वर्कफ़्लोज़ (5+ चरण, शाखा तर्क)
टूलिंग/फ्रेमवर्क काफ्का, रैबिटएमक्यू, एनएटीएस, एडब्ल्यूएस इवेंटब्रिज Temporal.io, AWS स्टेप फ़ंक्शंस, कैमुंडा, एक्सॉन

अलगाव की चुनौतियाँ और प्रतिउपाय (“एसिड माइनस I” से निपटना)

क्योंकि सागा में स्थानीय लेनदेन तुरंत उनके स्थानीय डेटाबेस के लिए प्रतिबद्ध होते हैं, सागा पैटर्न में पारंपरिक ACID गारंटी से अलगाव (I) का अभाव होता है।

यदि कोई क्लाइंट $T_1$ द्वारा संशोधित डेटाबेस पंक्ति को पढ़ता है, जबकि सागा अभी भी चल रहा है, तो वे अप्रतिबद्ध, मध्यवर्ती स्थिति पढ़ रहे हैं। यदि डाउनस्ट्रीम चरण विफल हो जाता है और मुआवजा ($C_1$) ट्रिगर हो जाता है, तो क्लाइंट ने डर्टी रीड किया है।

अलगाव की कमी के कारण होने वाली सामान्य विसंगतियाँ

  1. खोये हुए अपडेट: सागा ए एक रिकॉर्ड को अपडेट करता है। सागा ए के पूरा होने से पहले, सागा बी उसी रिकॉर्ड को अधिलेखित कर देता है। यदि सागा ए विफल हो जाता है और मुआवजा निष्पादित करता है, तो यह सागा बी के अपडेट को अधिलेखित कर देता है।
  2. डर्टी रीड्स: एक ग्राहक सागा ए ($T_1$) द्वारा अपडेट किए गए उपलब्ध स्टॉक को पढ़ता है। सागा ए डाउनस्ट्रीम ($T_3$) में विफल रहता है और स्टॉक ($C_1$) को पुनर्स्थापित करता है, लेकिन ग्राहक ने पहले ही पुराने डेटा के आधार पर ऑर्डर दे दिया है।
  3. नॉन-रिपीटेबल रीड्स: एक सेवा चरण $T_1$ पर डेटा पढ़ती है और चरण $T_3$ पर इसे फिर से पढ़ती है, लेकिन एक अन्य समवर्ती सागा ने बीच में डेटा को संशोधित किया।

प्रतिउपाय एवं शमन रणनीतियाँ

अलगाव की कमी के बावजूद डेटा अखंडता बनाए रखने के लिए, सॉफ्टवेयर आर्किटेक्ट विशिष्ट अलगाव डिजाइन पैटर्न लागू करते हैं:

1. सिमेंटिक लॉक (लंबित/ध्वजांकित स्थिति)

जब स्थानीय लेनदेन $T_1$ डेटाबेस रिकॉर्ड को अद्यतन करता है, तो यह एक स्थिति फ़ील्ड को PENDING या APPROVAL_REQUIRED पर सेट करता है (उदाहरण के लिए, ORDER_PENDING_PAYMENT)।

इस रिकॉर्ड को पढ़ने वाले अन्य समवर्ती सागा को सिमेंटिक लॉक फ़्लैग की जांच करनी चाहिए और अपने व्यवहार को तब तक ब्लॉक या बदलना चाहिए जब तक कि स्थिति COMMITTED या CANCELLED में न बदल जाए।

2. प्रतिबद्धता आदेश

स्थानीय लेनदेन के अनुक्रम को डिज़ाइन करें ताकि उच्च जोखिम या अपरिवर्तनीय संचालन सागा निष्पादन में देर से हो, जिससे भेद्यता की खिड़की कम से कम हो।

3. पुनः पढ़ें सत्यापन (आशावादी समवर्ती नियंत्रण)

किसी महत्वपूर्ण कदम या मुआवज़े को निष्पादित करने से पहले, लक्ष्य डेटाबेस रिकॉर्ड को दोबारा पढ़ें और संस्करण टाइमस्टैम्प (version_id) को सत्यापित करें ताकि यह सुनिश्चित हो सके कि कोई समवर्ती संशोधन नहीं हुआ है।

4. निराशावादी दृष्टिकोण

आर्थिक जोखिम को कम करने के लिए सागा के चरणों को फिर से व्यवस्थित करें (उदाहरण के लिए, भुगतान प्राधिकरण को यथासंभव धुरी लेनदेन के करीब रखें)।


उत्पादन अनुक्रम प्रवाह: क्षतिपूर्ति लेनदेन

आगे की निष्पादन विफलता और परिणामी पिछड़े मुआवजे के निष्पादन को दर्शाने वाला संपूर्ण अनुक्रम प्रवाह आरेख नीचे दिया गया है:

सागा पैटर्न अनुक्रम प्रवाह आरेख आगे निष्पादन विफलता और क्षतिपूर्ति रोलबैक दिखा रहा है

व्यावहारिक कोड कार्यान्वयन

आइए कोरियोग्राफी (गो में) और ऑर्केस्ट्रेशन (जावा स्प्रिंग बूट में) दोनों के लिए उत्पादन-तैयार कार्यान्वयन उदाहरण देखें।

कार्यान्वयन 1: गो में कोरियोग्राफी-आधारित गाथा

इस गो उदाहरण में, हम एक ऑर्डर सेवा को प्रदर्शित करते हैं जो ऑर्डर निर्माण को संभालती है और क्षतिपूर्ति रोलबैक तर्क को निष्पादित करने के लिए एक इवेंट ब्रोकर पर भुगतान विफलता की घटनाओं को सुनती है।

package saga

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// Event definitions
type OrderCreatedEvent struct {
	OrderID   string  `json:"order_id"`
	CustomerID string `json:"customer_id"`
	Amount    float64 `json:"amount"`
}

type PaymentFailedEvent struct {
	OrderID string `json:"order_id"`
	Reason  string `json:"reason"`
}

// OrderRepository handles local DB operations
type OrderRepository interface {
	CreateOrder(ctx context.Context, orderID string, amount float64) error
	UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}

// EventBus abstraction for message broker (e.g., Kafka / NATS)
type EventBus interface {
	Publish(topic string, payload []byte) error
	Subscribe(topic string, handler func(payload []byte)) error
}

type OrderSagaChoreographer struct {
	repo     OrderRepository
	eventBus EventBus
}

func NewOrderSagaChoreographer(repo OrderRepository, bus EventBus) *OrderSagaChoreographer {
	c := &OrderSagaChoreographer{repo: repo, eventBus: bus}
	c.registerSubscriptions()
	return c
}

// Step 1: Forward Transaction (T1)
func (s *OrderSagaChoreographer) StartOrderSaga(ctx context.Context, orderID, customerID string, amount float64) error {
	// Execute local database transaction
	err := s.repo.CreateOrder(ctx, orderID, amount)
	if err != nil {
		return fmt.Errorf("failed local DB transaction T1: %w", err)
	}

	// Emit domain event for downstream Payment Service
	event := OrderCreatedEvent{OrderID: orderID, CustomerID: customerID, Amount: amount}
	bytes, _ := json.Marshal(event)
	
	log.Printf("[SAGA][T1] Order %s created. Publishing OrderCreatedEvent...", orderID)
	return s.eventBus.Publish("orders.created", bytes)
}

// Register subscription for compensating events
func (s *OrderSagaChoreographer) registerSubscriptions() {
	_ = s.eventBus.Subscribe("payments.failed", func(payload []byte) {
		var event PaymentFailedEvent
		if err := json.Unmarshal(payload, &event); err != nil {
			log.Printf("[ERROR] Corrupt payment event: %v", err)
			return
		}

		// Execute Compensating Transaction (C1)
		s.handlePaymentFailed(context.Background(), event)
	})
}

// Step C1: Compensating Transaction
func (s *OrderSagaChoreographer) handlePaymentFailed(ctx context.Context, event PaymentFailedEvent) {
	log.Printf("[SAGA][C1] Payment failed for Order %s (Reason: %s). Rolling back local order...", event.OrderID, event.Reason)
	
	// Revert order status to CANCELLED in local DB
	err := s.repo.UpdateOrderStatus(ctx, event.OrderID, "CANCELLED_PAYMENT_FAILED")
	if err != nil {
		log.Printf("[CRITICAL] Failed to execute compensating transaction C1 for Order %s: %v", event.OrderID, err)
		// Trigger alert or write to Dead Letter Queue (DLQ)
		return
	}
	
	log.Printf("[SAGA][SUCCESS] Order %s successfully compensated and cancelled.", event.OrderID)
}

कार्यान्वयन 2: जावा में ऑर्केस्ट्रेशन-आधारित सागा (स्प्रिंग बूट)

इस जावा उदाहरण में, हम आगे के आदेशों को समन्वयित करने और डाउनस्ट्रीम चरण विफल होने पर क्षतिपूर्ति रोलबैक निष्पादित करने के लिए एक राज्य मशीन पैटर्न का उपयोग करके एक सागा ऑर्केस्ट्रेटर बनाते हैं।

package com.ghaznix.saga.orchestrator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.UUID;

public enum SagaState {
    STARTED,
    ORDER_CREATED,
    PAYMENT_PROCESSED,
    INVENTORY_RESERVED,
    COMPLETED,
    COMPENSATING_PAYMENT,
    COMPENSATING_ORDER,
    FAILED
}

@Service
public class OrderSagaOrchestrator {

    private static final Logger log = LoggerFactory.getLogger(OrderSagaOrchestrator.class);

    private final OrderServiceClient orderClient;
    private final PaymentServiceClient paymentClient;
    private final InventoryServiceClient inventoryClient;

    public OrderSagaOrchestrator(OrderServiceClient orderClient,
                                PaymentServiceClient paymentClient,
                                InventoryServiceClient inventoryClient) {
        this.orderClient = orderClient;
        this.paymentClient = paymentClient;
        this.inventoryClient = inventoryClient;
    }

    public boolean executeOrderSaga(String customerId, String productId, double amount, int quantity) {
        String sagaId = UUID.randomUUID().toString();
        log.info("[SAGA {}] Starting Order Saga Workflow...", sagaId);
        
        SagaState currentState = SagaState.STARTED;
        String orderId = null;
        String paymentId = null;

        try {
            // Step 1: Forward Local Tx T1 - Create Order
            log.info("[SAGA {}][T1] Sending CreateOrder command...", sagaId);
            orderId = orderClient.createOrder(customerId, productId, amount);
            currentState = SagaState.ORDER_CREATED;

            // Step 2: Forward Local Tx T2 - Process Payment
            log.info("[SAGA {}][T2] Sending ProcessPayment command for Order {}...", sagaId, orderId);
            paymentId = paymentClient.chargePayment(orderId, customerId, amount);
            currentState = SagaState.PAYMENT_PROCESSED;

            // Step 3: Forward Local Tx T3 (Pivot Step) - Reserve Inventory
            log.info("[SAGA {}][T3] Sending ReserveInventory command for Product {}...", sagaId, productId);
            boolean stockReserved = inventoryClient.reserveStock(productId, quantity);

            if (!stockReserved) {
                throw new InventoryAllocationException("Stock allocation failed: Item out of stock.");
            }

            currentState = SagaState.INVENTORY_RESERVED;
            log.info("[SAGA {}][SUCCESS] Saga completed successfully!", sagaId);
            return true;

        } catch (Exception ex) {
            log.error("[SAGA {}][FAILURE] Step failed during state {}. Triggering Compensation...", sagaId, currentState, ex);
            rollbackSaga(sagaId, currentState, orderId, paymentId);
            return false;
        }
    }

    private void rollbackSaga(String sagaId, SagaState failedState, String orderId, String paymentId) {
        log.info("[SAGA {}] Initiating backward compensating transactions from state: {}", sagaId, failedState);

        // Compensate Step 2 if payment was processed
        if (failedState == SagaState.PAYMENT_PROCESSED || failedState == SagaState.INVENTORY_RESERVED) {
            try {
                log.info("[SAGA {}][C2] Executing Payment Refund compensation for Payment {}...", sagaId, paymentId);
                paymentClient.refundPayment(paymentId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Payment refund C2 failed! Manual intervention or DLQ required.", sagaId, e);
            }
        }

        // Compensate Step 1 if order was created
        if (failedState != SagaState.STARTED && orderId != null) {
            try {
                log.info("[SAGA {}][C1] Executing Order Cancel compensation for Order {}...", sagaId, orderId);
                orderClient.cancelOrder(orderId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Order cancellation C1 failed!", sagaId, e);
            }
        }

        log.info("[SAGA {}] Saga compensation workflow finished. Final State: FAILED.", sagaId);
    }
}

उत्पादन अनिवार्यताएँ: सागा को ट्रांजेक्शनल आउटबॉक्स पैटर्न के साथ जोड़ना

कोरियोग्राफी और ऑर्केस्ट्रेशन दोनों में, स्थानीय लेनदेन ($T_i$) को निष्पादित करने के लिए नेटवर्क पर एक डोमेन इवेंट या कमांड प्रकाशित करने की आवश्यकता होती है।

यदि आपकी सेवा अपने SQL डेटाबेस को अद्यतन करती है और फिर काफ्का को एक संदेश प्रकाशित करती है, तो डेटाबेस प्रतिबद्ध होने के बाद एक नेटवर्क गड़बड़ी मूक घटना हानि का कारण बनती है। इसके विपरीत, डेटाबेस के प्रतिबद्ध होने से पहले संदेश प्रकाशित करने से फैंटम इवेंट प्रोसेसिंग होती है।

इसे हल करने के लिए, सागास को ट्रांजेक्शनल आउटबॉक्स पैटर्न के साथ जोड़ा जाना चाहिए:

[Service Database Transaction Boundary]
+-----------------------------------------------------+
| 1. INSERT INTO business_table (orders/payments)    |
| 2. INSERT INTO outbox_table (event_payload)        |
+-----------------------------------------------------+
                          |
             (CDC / Polling Message Relay)
                          |
                          v
               [Message Broker / Kafka]

इवेंट पेलोड को समान स्थानीय डेटाबेस लेनदेन के अंदर outbox तालिका में बनाए रखने से, परमाणुता की गारंटी होती है। एक अतुल्यकालिक पृष्ठभूमि प्रक्रिया (जैसे डेबेज़ियम या पोलिंग रिले) आउटबॉक्स तालिका से पढ़ती है और काफ्का में घटनाओं को विश्वसनीय रूप से प्रकाशित करती है।

इसके अलावा, प्रत्येक डाउनस्ट्रीम सेवा उपभोक्ता को इडेम्पोटेंसी (अद्वितीय idempotency_key या संदेश डिडुप्लीकेशन हेडर का उपयोग करके) लागू करना होगा ताकि पुन: प्रयास के दौरान डुप्लिकेट संदेश डिलीवरी डुप्लिकेट शुल्क या इन्वेंट्री आवंटन को ट्रिगर न करें।


वास्तु निर्णय चेकलिस्ट

माइक्रोसर्विस अनुप्रयोगों के लिए वितरित लेनदेन को डिज़ाइन करते समय इस व्यावहारिक निर्णय मैट्रिक्स का उपयोग करें:

                  Do you need cross-service data consistency?
                                     |
                    +----------------+----------------+
                    | No                              | Yes
                    v                                 v
         Standard Single Service            Can you accept Eventual
            Local Database                    Consistency (BASE)?
                                                      |
                                     +----------------+----------------+
                                     | No                              | Yes
                                     v                                 v
                          Use Monolithic Core            Adopt Saga Pattern
                          with Single ACID DB                 |
                                                              |
                                             How complex is the workflow?
                                                              |
                                            +-----------------+-----------------+
                                            | Simple (2-3 steps)                | Complex (4+ steps/branches)
                                            v                                   v
                                   Choreography Saga                   Orchestration Saga
                                   (Event-Driven Bus)                  (Temporal/Custom State Machine)

निष्कर्ष और मुख्य बातें

सागा पैटर्न संसाधनों को लॉक किए बिना या सिस्टम उपलब्धता का त्याग किए बिना माइक्रोसर्विस सीमाओं पर वितरित लेनदेन के प्रबंधन के लिए एक आवश्यक वास्तुशिल्प पैटर्न है।

सारांश चेकलिस्ट:

  1. क्लाउड-नेटिव माइक्रोसर्विसेज में 2PC/XA को त्यागें: दो-चरण की प्रतिबद्धता के कारण टाइट लॉकिंग, उच्च विलंबता और गंभीर उपलब्धता बाधाएँ आती हैं।
  2. लेन-देन को स्थानीय चरणों में विभाजित करें: वैश्विक परिचालन को स्थानीय लेनदेन में विभाजित करें ($T_1 \dots T_n$) को उलटे क्षतिपूर्ति वाले लेनदेन के साथ जोड़ा गया ($C_1 \dots C_{n-1}$)।
  3. सही वास्तुशिल्प शैली चुनें:
    • ढीले युग्मन के साथ सरल, 2-3 चरण वाले इवेंट-संचालित प्रवाह के लिए कोरियोग्राफी का उपयोग करें।
    • केंद्रीकृत दृश्यता, शाखाकरण और राज्य मशीन ट्रैकिंग की आवश्यकता वाले जटिल व्यावसायिक वर्कफ़्लो के लिए ऑर्केस्ट्रेशन का उपयोग करें।
  4. आइसोलेशन प्रतिउपाय लागू करें: सिमेंटिक लॉक्स (PENDING फ़्लैग्स) और री-रीड ऑप्टिमिस्टिक लॉकिंग का उपयोग करके गंदे रीड्स और खोए हुए अपडेट से बचाव करें।
  5. विश्वसनीय मैसेजिंग की गारंटी: हमेशा सागा कार्यान्वयन को ट्रांजैक्शनल आउटबॉक्स पैटर्न के साथ जोड़ें और निष्क्रिय उपभोक्ताओं को पुनः प्रयास को सुरक्षित रूप से संभालने के लिए लागू करें।

सागा पैटर्न को सोच-समझकर लागू करके, आप अत्यधिक उपलब्ध, स्केलेबल माइक्रोसर्विसेज का निर्माण कर सकते हैं जो नेटवर्क विफल होने पर भी लचीली और सुसंगत रहती हैं।