ساگا پیٹرن: مائیکرو سروسز آرکیٹیکچر میں تقسیم شدہ لین دین
روایتی یک سنگی ایپلی کیشنز میں، متعدد اداروں میں ڈیٹا کی مستقل مزاجی کو برقرار رکھنا سیدھا ہے۔ متعلقہ ڈیٹا بیس انجن ACID (جوہری، مستقل مزاجی، تنہائی، استحکام) کی ضمانت فراہم کرتے ہیں جو مقامی ایس کیو ایل لین دین کے اندر لپیٹے ہوئے ہیں۔ اگر آرڈر کی جگہ، ادائیگی کی کٹوتی، یا انوینٹری ریزرو آدھے راستے میں ناکام ہو جاتی ہے، تو ROLLBACK کو کال کرنے سے ڈیٹا بیس میں ہونے والی ہر ترمیم کو فوری طور پر واپس کر دیا جاتا ہے۔
تاہم، جب جدید مائیکرو سروسز آرکیٹیکچر میں منتقل ہوتے ہیں، ڈیٹا مینجمنٹ بنیادی طور پر بدل جاتا ہے۔ ڈومین کی خود مختاری اور آزاد اسکیل ایبلٹی کو یقینی بنانے کے لیے، ہر مائیکرو سروس اپنے نجی ڈیٹا بیس کا مالک ہے۔ ایک ہی کاروباری آپریشن — جیسے کہ ای کامرس چیک آؤٹ پر کارروائی کرنا — اب متعدد سروس باؤنڈریز اور ڈیٹا بیس انجنوں پر محیط ہے (جیسے، آرڈرز کے لیے PostgreSQL، ادائیگیوں کے لیے DynamoDB، انوینٹری کے لیے Redis)۔
چونکہ تقسیم شدہ مائیکرو سروسز کسی ایک ڈیٹا بیس کے لین دین پر انحصار نہیں کر سکتیں، اس لیے نیٹ ورک کی حدود میں ڈیٹا کی مستقل مزاجی کو برقرار رکھنا تقسیم شدہ نظاموں کی انجینئرنگ میں سب سے مشکل مسائل میں سے ایک بن جاتا ہے۔
سسٹم کی دستیابی یا کارکردگی کو قربان کیے بغیر اسے حل کرنے کے لیے، سافٹ ویئر آرکیٹیکٹس ساگا پیٹرن پر انحصار کرتے ہیں۔
اس گہرے غوطے میں، ہم دریافت کریں گے کہ روایتی تقسیم شدہ لین دین کیوں ناکام ہوتا ہے، ساگا پیٹرن کے بنیادی میکانکس کو توڑیں گے، کوریوگرافی بمقابلہ آرکیسٹریشن کا موازنہ کریں گے، تنہائی کے انسداد کا تجزیہ کریں گے، Go اور Java میں پروڈکشن کوڈ کے نفاذ کا جائزہ لیں گے، اور حقیقی دنیا کی ناکامی کو محفوظ طریقے سے ہینڈل کرنے کا طریقہ سیکھیں گے۔
بنیادی مسئلہ: مائیکرو سروسز میں دو فیز کمٹ (2PC) کیوں ناکام ہوتا ہے
ساگا پیٹرن کو اپنانے سے پہلے، انجینئر اکثر پوچھتے ہیں: ہم اپنی مائیکرو سروسز میں روایتی دو فیز کمٹ (2PC/XA) کا استعمال کیوں نہیں کر سکتے؟
2PC کی میکینکس
دو فیز کمٹ دو مرحلوں میں مرکزی ٹرانزیکشن مینیجر کا استعمال کرتے ہوئے متعدد ڈیٹا بیس نوڈس میں تقسیم شدہ لین دین کو مربوط کرتا ہے:
- مرحلہ تیار کریں: ٹرانزیکشن مینیجر تمام حصہ لینے والے ڈیٹا بیس نوڈس سے مطلوبہ قطاریں تیار کرنے اور لاک کرنے کو کہتا ہے۔ شرکاء ووٹ دیتے ہیں
YESیاNO۔ - کمٹ فیز: اگر تمام شرکاء نے
YESکو ووٹ دیا تو مینیجر تمام نوڈس کوCOMMITکمانڈ جاری کرتا ہے۔ اگر کسی نوڈ نےNOکو ووٹ دیا یا وقت ختم ہو گیا، تو یہROLLBACKجاری کرتا ہے۔
Client ----> Transaction Manager
|
+-------------+-------------+
| (Prepare) | (Prepare) | (Prepare)
v v v
Order DB Payment DB Inventory DB
کیوں 2PC مائیکرو سروسز کے لیے ایک اینٹی پیٹرن ہے۔
جبکہ 2PC مضبوط مستقل مزاجی کی ضمانت دیتا ہے، یہ کئی تعمیراتی خامیوں کی وجہ سے کلاؤڈ-نیٹیو مائیکرو سروس ماحول میں ٹوٹ جاتا ہے:
- بلاکنگ اور ریسورس کنٹینشن: ڈیٹا بیس کی قطاریں ملٹی اسٹیج نیٹ ورک ہینڈ شیک کے دوران مقفل رہتی ہیں۔ اگر نیٹ ورک لیٹنسی بڑھ جاتی ہے یا سروس سست ہو جاتی ہے، تو تالے کھلے رکھے جاتے ہیں، تیزی سے کنکشن پولز کو استعمال کرتے ہیں اور کاسکیڈنگ سسٹم کی خرابی کا باعث بنتے ہیں۔
- Availability Bottleneck: 2PC میں، سسٹم کی دستیابی تمام شرکاء کی دستیابی ($A_{total} = A_1 \times A_2 \times \dots \times A_n$) کے مصنوعہ کی پابند ہے۔ اگر تیاری کے مرحلے کے دوران کوئی ایک سروس یا ڈیٹابیس نوڈ آف لائن ہو جاتا ہے، تو پوری عالمی لین دین غیر معینہ مدت کے لیے بند ہو جاتی ہے۔
- سروس کی خودمختاری کا نقصان: 2PC سروسز کو ڈیٹا بیس لیول کے XA پروٹوکول کو نیٹ ورک APIs میں ظاہر کرنے پر مجبور کرتا ہے، ان کے ڈیٹا بیس انجن کو براہ راست جوڑتا ہے۔
- بروکر کی رکاوٹیں: اپاچی کافکا یا RabbitMQ جیسے ہائی تھرو پٹ میسج بروکرز متعلقہ ڈیٹا بیس میں روایتی XA 2PC لین دین میں مقامی طور پر حصہ نہیں لیتے ہیں۔
CAP تھیوریم کے مطابق، تقسیم شدہ نظاموں کو نیٹ ورک پارٹیشنز (P) کے تحت مضبوط مستقل مزاجی (C) اور اعلی دستیابی (A) کے درمیان انتخاب کرنا چاہیے۔ جدید مائیکرو سروس سسٹمز دستیابیت اور تقسیم رواداری کو ترجیح دیتے ہیں، ایونچوئل کنسسٹینسی کے لیے فوری مستقل مزاجی کی تجارت کرتے ہیں (بیس: بنیادی طور پر دستیاب، نرم حالت، حتمی مستقل مزاجی)۔
ساگا پیٹرن کیا ہے؟
ساگا پیٹرن اصل میں ہیکٹر گارسیا-مولینا اور کینتھ سیلم نے 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$ ناکام ہوجاتا ہے کیونکہ ایک آئٹم اسٹاک سے باہر ہے)، Saga صرف پچھلے مراحل ($T_1, T_2$) کے لیے ڈیٹا بیس ROLLBACK کو کال نہیں کرسکتا ہے کیونکہ وہ مقامی ٹرانزیکشنز پہلے ہی اپنے متعلقہ ڈیٹا بیس کے لیے کمٹمنٹ کر چکے ہیں۔
پہلے سے ارتکاب شدہ مقامی لین دین کو کالعدم کرنے کے لیے، Saga کو الٹی ترتیب میں معاوضہ دینے والے لین دین ($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) <----------+
ساگا لین دین کی درجہ بندی
ایک مضبوط ساگا ورک فلو کو ڈیزائن کرنے کے لیے، لین دین کی ترتیب میں ہر قدم کو تین ساختی اقسام میں سے ایک میں درجہ بندی کرنا ضروری ہے:
| لین دین کی قسم | تفصیل | Idempotency اور رول بیک کی ضرورت |
|---|---|---|
| معاوضہ لین دین | پوائنٹ آف نو ریٹرن سے پہلے انجام دیئے گئے اقدامات۔ اگر کوئی نیچے کی طرف قدم ناکام ہو جاتا ہے تو انہیں کالعدم یا تبدیل کیا جا سکتا ہے۔ | ایک متعلقہ معاوضہ دینے والا لین دین ہونا چاہیے ($C_i$)۔ |
| پیوٹ ٹرانزیکشن | ساگا میں حتمی قدم۔ اگر محور کامیاب ہو جاتا ہے، تو ساگا کے ختم ہونے کی ضمانت ہے۔ اگر یہ ناکام ہو جاتا ہے تو، ساگا واپس چلا جاتا ہے۔ | نہ تو قابل تلافی ہے اور نہ ہی قابل واپسی؛ یہ رول بیک اور آگے کی تکمیل کے درمیان حد کو نشان زد کرتا ہے۔ |
| قابل بازیافت لین دین | پیوٹ ٹرانزیکشن کے بعد عمل میں لائے گئے اقدامات۔ وہ بالآخر کامیاب ہونے کی ضمانت دی جاتی ہے اور انہیں معاوضے کی ضرورت نہیں ہوتی ہے۔ | سختی سے کمزور ہونا ضروری ہے، کیونکہ کامیاب ہونے تک ان کی دوبارہ کوشش کی جائے گی۔ |
ای کامرس چیک آؤٹ کی مثال کی خرابی۔
چار مراحل پر مشتمل ای کامرس آرڈر چیک آؤٹ پر غور کریں:
- $T_1$: زیر التواء آرڈر بنائیں (معاوضہ) $\to$ $C_1$ کے ذریعے کالعدم کریں: آرڈر کینسل۔
- $T_2$: ادائیگی کی اجازت دیں (معاوضہ) $\to$ واپس کریں بذریعہ $C_2$: رقم کی واپسی۔
- $T_3$: ریزرو انوینٹری (پیوٹ ٹرانزیکشن) $\to$ اگر اسٹاک ایلوکیشن کامیاب ہوجاتا ہے، تو آرڈر کو حتمی شکل دی جاتی ہے۔ اگر یہ ناکام ہوجاتا ہے، تو $C_2$ اور $C_1$ کو متحرک کریں۔
- $T_4$: ڈسپیچ شپنگ کی درخواست (قابل بازیافت) $\to$ محور کے بعد عمل میں آیا؛ ڈیلیور ہونے تک دوبارہ کوشش کی۔
ساگا آرکیٹیکچرل اسٹائلز: کوریوگرافی بمقابلہ آرکیسٹریشن
تقسیم شدہ نظاموں میں ساگا پیٹرن کو نافذ کرنے کے لیے دو بنیادی آرکیٹیکچرل طرزیں ہیں: کوریوگرافی (ڈی سینٹرلائزڈ) اور آرکیسٹریشن (مرکزی)۔
انداز 1: کوریوگرافی (ایونٹ پر مبنی وکندریقرت)
کوریوگرافی پر مبنی ساگا میں، کوئی مرکزی کنٹرولر یا کوآرڈینیٹر نہیں ہوتا ہے۔ اس کے بجائے، مائیکرو سروسز سنٹرل ایونٹ بس (جیسے اپاچی کافکا، NATS، یا RabbitMQ) میں شائع ہونے والے ڈومین ایونٹس کو سن کر متضاد طور پر بات چیت کرتی ہیں۔
ورک فلو میکینکس
- آرڈر سروس $T_1$ کو عمل میں لاتی ہے (پینڈنگ آرڈر بناتی ہے) اور کافکا کو ایک
OrderCreatedایونٹ کا اخراج کرتی ہے۔ - ادائیگی کی خدمت
OrderCreatedکو سنتی ہے، $T_2$ (کریڈٹ کارڈ پر چارج کرتی ہے) اور ایکPaymentCompletedایونٹ (یاPaymentFailed) کو خارج کرتی ہے۔ - انوینٹری سروس
PaymentCompletedکو سنتی ہے، $T_3$ پر عملدرآمد کرتی ہے (آئٹمز محفوظ رکھتی ہے)۔ اگر آئٹمز اسٹاک سے باہر ہیں، تو یہInventoryReservationFailedخارج کرتا ہے۔ - ادائیگی کی خدمت
InventoryReservationFailedکو سنتی ہے اور $C_2$ (ریفنڈ کے مسائل) پر عملدرآمد کرتی ہے۔ - آرڈر سروس
PaymentRefundedکو سنتی ہے اور $C_1$ پر عمل کرتی ہے (آرڈر کو منسوخ کے طور پر نشان زد کرتا ہے)۔
کوریوگرافی کے فوائد
- لوز کپلنگ: سروسز صرف ایونٹ کے عنوانات پر سبسکرائب کرتی ہیں۔ وہ دیگر خدمات کے نفاذ کے بارے میں نہیں جانتے ہیں۔
- ہائی تھرو پٹ اور ڈی سینٹرلائزیشن: براہ راست ایونٹ پب/سب مرکزی آرکیسٹریٹر کی رکاوٹوں کو ختم کرتا ہے۔
- مختصر ورک فلوز کے لیے سادگی: سادہ 2–3 قدمی ورک فلوز کے لیے سیٹ اپ کرنا آسان ہے۔
کوریوگرافی کے نقصانات
- سائیکلک انحصار کا خطرہ: سروسز ایک دوسرے کے واقعات کو سن کر پیچیدہ سائیکلک انحصار پیدا کر سکتی ہیں۔
- مشکل کوڈ ٹریس ایبلٹی: اختتام سے آخر تک کاروباری عمل کو سمجھنے کے لیے متعدد کوڈ بیس ریپوزٹریوں میں ٹریسنگ منطق کی ضرورت ہوتی ہے۔
- واقعہ کا طوفان اور پیچیدگی: جیسے جیسے قدموں کی تعداد میں اضافہ ہوتا ہے (مثلاً، 10+ سروسز)، خرابی کے کنارے کے معاملات کو منظم کرنے سے واقعہ کی حالت میں دھماکہ ہوتا ہے۔
انداز 2: آرکیسٹریشن (مرکزی ورک فلو مینجمنٹ)
ایک آرکیسٹریشن پر مبنی Saga میں، ساگا آرکیسٹریٹر کے نام سے معروف مائیکرو سروس تقسیم شدہ لین دین کے پورے لائف سائیکل کو کنٹرول کرتی ہے۔ آرکیسٹریٹر ایک مرکزی کوآرڈینیٹر کے طور پر کام کرتا ہے، شرکت کرنے والی مائیکرو سروسز کو واضح احکامات جاری کرتا ہے اور ان کے ردعمل کے واقعات سنتا ہے۔
ورک فلو میکینکس
- کلائنٹ ساگا آرکیسٹریٹر کو آرڈر کی درخواست بھیجتا ہے۔
- آرکیسٹریٹر آرڈر سروس کو
CreateOrderکمانڈ بھیجتا ہے۔ آرڈر سروسOrderCreatedواپس کرتا ہے۔ - آرکیسٹریٹر اپنی ریاستی مشین کو اپ ڈیٹ کرتا ہے اور ادائیگی سروس کو
ProcessPaymentکمانڈ بھیجتا ہے۔ ادائیگی کی خدمتPaymentSuccessfulواپس کرتی ہے۔ - آرکیسٹریٹر انوینٹری سروس کو ایک
ReserveInventoryکمانڈ بھیجتا ہے۔ انوینٹری سروسInventoryFailed (Out of Stock)واپس کرتی ہے۔ - آرکیسٹریٹر ناکامی کا پتہ لگاتا ہے، معاوضے کا بہاؤ شروع کرتا ہے:
- پیمنٹ سروس کو
RefundPaymentکمانڈ بھیجتا ہے۔ - آرڈر سروس کو
CancelOrderکمانڈ بھیجتا ہے۔
- پیمنٹ سروس کو
- آرکیسٹریٹر نے ساگا کے عمل کو
FAILEDکے بطور نشان زد کیا۔
آرکیسٹریشن کے فوائد
- سنٹرلائزڈ بزنس لاجک: ورک فلو اسٹیٹ اور بزنس لاجک کو ایک سنگل آرکیسٹریٹر سروس یا اسٹیٹ مشین میں لوکلائز کیا جاتا ہے۔
- کوئی سائیکلک انحصار نہیں: مائیکرو سروسز آرکیسٹریٹر کے حکموں کا جواب دیتی ہیں۔ وہ دیگر ڈاؤن اسٹریم سروسز پر انحصار نہیں کرتے یا ان کے بارے میں نہیں جانتے ہیں۔
- کلیئر مانیٹرنگ اور ڈیبگنگ: اینڈ ٹو اینڈ ٹرانزیکشن اسٹیٹ واضح طور پر آرکیسٹریٹر کے اسٹیٹ اسٹور میں اسٹور کیا جاتا ہے (جیسے، PostgreSQL یا ورک فلو انجن جیسے Temporal / Camunda)۔
- آسان خرابی سے نمٹنا: نئے مراحل کو شامل کرنا یا رول بیک رولز کو تبدیل کرنا مکمل طور پر آرکیسٹریٹر کے اندر منظم کیا جاتا ہے۔
آرکیسٹریشن کے نقصانات
- آرکیسٹریٹر کی پیچیدگی: آرکیسٹریٹر میں بہت زیادہ ڈومین منطق ڈالنے کا خطرہ، اسے “سمارٹ آرکیسٹریٹر، گونگا سروس” مخالف پیٹرن میں تبدیل کرنا۔
- ممکنہ واحد نکتہ آف ناکامی: آرکیسٹریٹر کو انتہائی دستیاب اور ریاستی ہونا چاہیے۔
تقابلی میٹرکس: کوریوگرافی بمقابلہ آرکیسٹریشن
| خصوصیت | کوریوگرافی | آرکیسٹریشن |
|---|---|---|
| کنٹرول کا ڈھانچہ | وکندریقرت (ایونٹ پب/سب) | سنٹرلائزڈ (ساگا کوآرڈینیٹر / اسٹیٹ مشین) |
| جوڑا | انتہائی کم (سروسز ایونٹس استعمال کرتی ہیں) | میڈیم (سروسز کوآرڈینیٹر سے حکم قبول کرتی ہیں) |
| عمل کی مرئیت | کم (لاگ فائلوں میں تقسیم) | ہائی (سنگل اسٹیٹ اسٹور ورک فلو کو تصور کرتا ہے) |
| ** کے لیے بہترین ** | سادہ ورک فلو (2 سے 4 سروس کے مراحل) | پیچیدہ انٹرپرائز ورک فلو (5+ مراحل، برانچنگ منطق) |
| ٹولنگ / فریم ورک | Kafka, RabbitMQ, NATS, AWS EventBridge | Temporal.io, AWS Step Functions, Camunda, Axon |
تنہائی کے چیلنجز اور انسدادی اقدامات (“ACID مائنس I” کو سنبھالنا)
چونکہ ساگا میں مقامی لین دین اپنے مقامی ڈیٹا بیس سے فوری طور پر کمٹمنٹ کرتے ہیں، اس لیے ساگا پیٹرن میں روایتی ACID ضمانتوں سے Isolation (I) کا فقدان ہے۔
اگر کوئی کلائنٹ ڈیٹابیس کی قطار کو پڑھتا ہے جس میں $T_1$ کی طرف سے ترمیم کی گئی ہے جب کہ Saga ابھی بھی چل رہا ہے، تو وہ غیر کمٹڈ، انٹرمیڈیٹ اسٹیٹ پڑھ رہے ہیں۔ اگر کوئی نیچے کا مرحلہ ناکام ہو جاتا ہے اور معاوضے کو متحرک کرتا ہے ($C_1$)، کلائنٹ نے Dirty Read انجام دیا ہے۔
تنہائی کی کمی کی وجہ سے عام بے ضابطگیاں
- لاسٹ اپ ڈیٹس: Saga A ایک ریکارڈ کو اپ ڈیٹ کرتا ہے۔ Saga A کے مکمل ہونے سے پہلے، Saga B اسی ریکارڈ کو اوور رائٹ کر دیتا ہے۔ اگر Saga A ناکام ہوجاتا ہے اور معاوضہ ادا کرتا ہے، تو یہ Saga B کی تازہ کاری کو اوور رائٹ کر دیتا ہے۔
- ڈرٹی ریڈز: ایک گاہک Saga A ($T_1$) کے ذریعے اپ ڈیٹ شدہ دستیاب اسٹاک کو پڑھتا ہے۔ Saga A ڈاون اسٹریم ($T_3$) میں ناکام ہوجاتا ہے اور اسٹاک ($C_1$) کو بحال کرتا ہے، لیکن کسٹمر نے پہلے سے ہی باسی ڈیٹا کی بنیاد پر آرڈر دیا ہے۔
- غیر دہرائی جانے والی ریڈز: ایک سروس ڈیٹا کو $T_1$ پر پڑھتی ہے اور اسے دوبارہ $T_3$ پر پڑھتی ہے، لیکن ایک اور سمورتی ساگا نے درمیان میں ڈیٹا میں ترمیم کی۔
انسدادی اقدامات اور تخفیف کی حکمت عملی
تنہائی کی کمی کے باوجود ڈیٹا کی سالمیت کو برقرار رکھنے کے لیے، سافٹ ویئر آرکیٹیکٹس مخصوص تنہائی کے ڈیزائن کے نمونوں کو نافذ کرتے ہیں:
1. سیمنٹک لاک (زیر التواء / پرچم والی حالت)
جب مقامی ٹرانزیکشن $T_1$ ڈیٹا بیس ریکارڈ کو اپ ڈیٹ کرتا ہے، تو یہ اسٹیٹس فیلڈ کو PENDING یا APPROVAL_REQUIRED (جیسے، ORDER_PENDING_PAYMENT) پر سیٹ کرتا ہے۔
اس ریکارڈ کو پڑھنے والے دوسرے ہم آہنگ ساگاس کو لازمی طور پر سیمنٹک لاک فلیگ کو چیک کرنا چاہیے اور اس وقت تک اپنے طرز عمل کو روکنا یا تبدیل کرنا چاہیے جب تک کہ ریاست COMMITTED یا CANCELLED میں تبدیل نہ ہوجائے۔
2. آرڈر کرنا
مقامی لین دین کی ترتیب کو ڈیزائن کریں تاکہ ساگا کے عمل میں دیر سے زیادہ خطرہ یا ناقابل واپسی آپریشنز ہوں، خطرے کی کھڑکی کو کم سے کم کریں۔
3. دوبارہ پڑھیں توثیق (پرامید کنکرنسی کنٹرول)
کسی اہم قدم یا معاوضے کو انجام دینے سے پہلے، ہدف ڈیٹا بیس ریکارڈ کو دوبارہ پڑھیں اور ورژن کے ٹائم اسٹیمپ (version_id) کی تصدیق کریں تاکہ یہ یقینی بنایا جا سکے کہ کوئی ہم آہنگی ترمیم نہیں ہوئی ہے۔
4. مایوسی کا نظریہ
معاشی نمائش کو کم سے کم کرنے کے لیے ساگا کے اقدامات کو دوبارہ ترتیب دیں (مثلاً ادائیگی کی اجازت کو پیوٹ ٹرانزیکشن کے زیادہ سے زیادہ قریب رکھیں)۔
پیداوار کی ترتیب کا بہاؤ: معاوضہ دینے والا لین دین
ذیل میں مکمل تسلسل کے بہاؤ کا خاکہ ہے جو آگے بڑھنے کی ناکامی اور اس کے نتیجے میں پسماندہ معاوضے پر عمل درآمد کو ظاہر کرتا ہے:
ہینڈ آن کوڈ پر عمل درآمد
آئیے کوریوگرافی (گو میں) اور آرکیسٹریشن (جاوا اسپرنگ بوٹ میں) دونوں کے لیے پروڈکشن کے لیے تیار نفاذ کی مثالیں دریافت کریں۔
نفاذ 1: کوریوگرافی پر مبنی ساگا ان گو
اس Go کی مثال میں، ہم ایک آرڈر سروس کا مظاہرہ کرتے ہیں جو آرڈر کی تخلیق کو سنبھالتی ہے اور کسی ایونٹ بروکر پر ادائیگی کی ناکامی کے واقعات کو سننے کے لیے معاوضہ دینے والی رول بیک منطق کو انجام دیتی ہے۔
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$) کو انجام دینے کے لیے نیٹ ورک پر ڈومین ایونٹ یا کمانڈ شائع کرنے کی ضرورت ہوتی ہے۔
اگر آپ کی سروس اپنے ایس کیو ایل ڈیٹا بیس کو اپ ڈیٹ کرتی ہے اور پھر کافکا کو ایک پیغام شائع کرتی ہے، ڈیٹا بیس کے کمٹ کے بعد نیٹ ورک کی خرابی **خاموش واقعہ کے نقصان کا سبب بنتی ہے۔ اس کے برعکس، ڈیٹا بیس کمٹ سے پہلے پیغام کو شائع کرنے سے فینٹم ایونٹ پروسیسنگ ہوتی ہے۔
اس کو حل کرنے کے لیے، Sagas کو ٹرانزیکشنل آؤٹ باکس پیٹرن کے ساتھ جوڑنا ضروری ہے:
[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 (منفرد 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)
نتیجہ اور اہم نکات
ساگا پیٹرن وسائل کو لاک کیے بغیر یا سسٹم کی دستیابی کو قربان کیے بغیر مائیکرو سروس کی حدود میں تقسیم شدہ لین دین کا انتظام کرنے کے لیے ایک ضروری آرکیٹیکچرل پیٹرن ہے۔
خلاصہ چیک لسٹ:
- کلاؤڈ-نیٹیو مائیکرو سروسز میں 2PC/XA کو چھوڑ دیں: دو فیز کمٹ سخت لاکنگ، زیادہ تاخیر، اور دستیابی میں شدید رکاوٹوں کا سبب بنتا ہے۔
- مقامی مراحل میں ٹرانزیکشنز کو توڑیں: عالمی آپریشنز کو مقامی لین دین میں تقسیم کریں ($T_1 \dots T_n$) معکوس معاوضہ دینے والے لین دین کے ساتھ جوڑا ($C_1 \dots C_{n-1}$)۔
- صحیح آرکیٹیکچرل اسٹائل کا انتخاب کریں:
- کوریوگرافی کا استعمال کریں سادہ، 2-3 قدمی ایونٹ سے چلنے والے بہاؤ کے ساتھ ڈھیلے جوڑے۔
- پیچیدہ کاروباری ورک فلو کے لیے آرکیسٹریشن کا استعمال کریں جس میں مرکزی مرئیت، برانچنگ، اور ریاستی مشین سے باخبر رہنے کی ضرورت ہوتی ہے۔
- تنہائی کے انسداد کے اقدامات کو نافذ کریں: سیمنٹک لاک (
PENDINGپرچم) اور دوبارہ پڑھیں آپٹیمسٹک لاکنگ کا استعمال کرکے گندے پڑھنے اور گمشدہ اپ ڈیٹس سے بچائیں۔ - معتبر پیغام رسانی کی ضمانت: ہمیشہ ساگا کے نفاذ کو ٹرانزیکشنل آؤٹ باکس پیٹرن کے ساتھ جوڑیں اور دوبارہ کوششوں کو محفوظ طریقے سے ہینڈل کرنے کے لیے بے باک صارفین کو نافذ کریں۔
ساگا پیٹرن کو سوچ سمجھ کر لاگو کر کے، آپ انتہائی دستیاب، توسیع پذیر مائیکرو سروسز بنا سکتے ہیں جو نیٹ ورک کے ناکام ہونے پر بھی لچکدار اور مستقل رہتی ہیں۔