در دنیای برنامههای تکتکه (Monolithic)، حفظ یکپارچگی دادهها ساده است. یک تراکنش پایگاهداده واحد تضمین میکند که یا تمام مراحل یک فرآیند تجاری موفق میشوند یا هیچکدام. با این حال، با مهاجرت به معماری میکروسرویس، این سادگی از بین میرود. هر سرویس مالک پایگاه داده خود است و این موضوع انجام تراکنشهای ACID سنتی را در مرزهای سرویسها غیرممکن میسازد. در اینجا است که الگوی ساگا (Saga Pattern) به عنوان یک ابزار ضروری برای معماران نرمافزار مدرن ظاهر میشود.
مشکل: یکپارچگی دادههای توزیعشده
هنگامی که یک فرآیند تجاری چندین سرویس را در بر میگیرد، مانند جریان سفارش در تجارت الکترونیک که شامل سرویس موجودی، سرویس پرداخت و سرویس حملونقل است، ما با چالش تراکنشهای توزیعشده روبرو میشویم. به دلیل تأخیر بالا، وابستگی شدید و احتمال ایجاد نقاط شکست تکی، نمیتوانیم از پروتکل کامیت دو مرحلهای (2PC) استاندارد در بسیاری از محیطهای مدرن استفاده کنیم. در عوض، به الگویی نیاز داریم که به تراکنشهای طولانیمدت اجازه دهد یکپارچگی را از طریق دنبالهای از تراکنشهای محلی حفظ کنند، که هر کدام دارای یک اقدام جبرانی (Compensating Action) متناظر هستند.
نحوه عملکرد الگوی ساگا
یک ساگا دنبالهای از تراکنشهای محلی است. هر تراکنش محلی پایگاه داده را بهروزرسانی کرده و رویداد یا پیامی را برای راهاندازی تراکنش محلی بعدی در ساگا منتشر میکند. اگر یک مرحله شکست بخورد، ساگا سری از تراکنشهای جبرانی را اجرا میکند تا تغییرات ایجاد شده توسط مراحل قبلی را لغو کند. دو روش اصلی برای پیادهسازی ساگا وجود دارد:
- رقص گروهی (Choreography): سرویسها از طریق رویدادها بدون هماهنگکننده مرکزی با یکدیگر ارتباط برقرار میکنند.
- هماهنگسازی (Orchestration): یک هماهنگکننده مرکزی جریان ساگا را هدایت میکند.
رقص گروهی در برابر هماهنگسازی
در حالی که رقص گروهی برای سیستمهای کوچک سادهتر است، با افزایش پیچیدگی میتواند ردیابی و عیبیابی آن دشوار شود. هماهنگسازی، با استفاده از یک هماهنگکننده مانند AWS Step Functions یا Temporal، دید و کنترل بهتری ارائه میدهد، اما اگر به دقت طراحی نشود، یک نقطه شکست جدید ایجاد میکند.
مثال کد عملی: رویکرد رقص گروهی
بیایید یک مثال سادهشده Node.js را برای یک ساگا مبتنی بر رقص گروهی در فرآیند "ثبت سفارش" بررسی کنیم. در اینجا فرض میکنیم که از یک بوس رویداد (Event Bus) مانند RabbitMQ یا Kafka استفاده میشود.
// Step 1: Create Order (Local Transaction)
async function handleOrderCreated(event) {
try {
// Reserve inventory locally
await inventoryService.reserve(itemIds, quantity);
// Publish event to trigger payment
eventBus.publish('ItemsReservedEvent', { orderId: event.orderId });
} catch (error) {
// Compensation: If reservation fails, notify order cancellation
eventBus.publish('OrderFailedEvent', { orderId: event.orderId });
}
}
// Step 2: Process Payment (Triggered by ItemsReservedEvent)
async function handleItemsReserved(event) {
try {
await paymentService.charge(event.orderId, amount);
// Success: Proceed to shipping
eventBus.publish('PaymentCompletedEvent', { orderId: event.orderId });
} catch (error) {
// Compensation: Cancel the order (which will trigger inventory release)
eventBus.publish('OrderFailedEvent', { orderId: event.orderId });
}
}
// Step 3: Cancel Order (Compensating Action)
async function handleOrderFailed(event) {
// Release inventory
await inventoryService.release(event.orderId);
// Refund payment if already charged
await paymentService.refund(event.orderId);
}
چالشهای کلیدی و ملاحظات
پیادهسازی ساگا بدون چالش نیست. توسعهدهندگان باید به دقت جابهجاییناپذیری (Idempotency) را مدیریت کنند، زیرا تلاش مجدد شبکه ممکن است باعث اجرای چندباره یک مرحله شود. علاوه بر این، قابلیت مشاهده (Observability) حیاتی است؛ بدون ردیابی توزیعشده، عیبیابی یک ساگای شکستخورده در سراسر پنج سرویس میتواند کابوس باشد. در نهایت، مهلتهای زمانی (Timeouts) را در نظر بگیرید؛ اگر یک سرویس پاسخگو نباشد، ساگا باید بتواند این موضوع را تشخیص داده و اقدام جبرانی را آغاز کند، نه اینکه برای همیشه در حالت تعلیق بماند.
نتیجهگیری
الگوی ساگا یک راهحل قدرتمند برای مدیریت تراکنشهای توزیعشده در معماریهای میکروسرویس است. با جایگزینی تراکنشهای سختگیرانه ACID با جریانهای کاری جبرانی انعطافپذیر، این الگو به سیستمها اجازه میدهد تا در عین حفظ یکپارچگی دادهها، به صورت شلبسته (Loosely Coupled) باقی بمانند. چه برای سادگی رقص گروهی را انتخاب کنید و چه برای کنترل هماهنگسازی را، درک الگوی ساگا برای هر توسعهدهندهای که سیستمهای توزیعشده مقیاسپذیر و مقاوم میسازد، ضروری است.