طلبية مكتملة، دفع فاشل: كيف أنقذني نمط ‘الساجا’ (Saga Pattern) من فوضى البيانات

يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.

قبل كم سنة، كنا في خضم حماس الانتقال من تطبيقنا المونوليثي (Monolith) الضخم إلى معمارية الخدمات المصغرة (Microservices). الكل كان مبسوط، الفريق متحمس للتقنيات الجديدة، والسرعة، والمرونة اللي وعدتنا فيها هالمعمارية. أطلقنا أول أجزاء النظام الجديد، وكان نظام الطلبات في متجرنا الإلكتروني.

في ليلة من الليالي، وأنا قاعد بشرب كاسة الشاي بالنعناع وبراقب لوحات المراقبة (Dashboards)، رن جوالي. كان صوت مدير قسم خدمة العملاء على الطرف الثاني متوتر: “أبو عمر، الحقنا! في عنا طلبية مكتملة، والبضاعة انخصمت من المستودع، بس الفلوس ما وصلت! والزبون معصّب وبحكي انه الدفع فشل عنده”.

قلبي وقتها “نقزني نقزة”. كيف يعني الطلب مكتمل والدفع فاشل؟ فتحت سجلات النظام (Logs) بسرعة، ولقيت الكارثة اللي كنت خايف منها. القصة كانت كالتالي:

  1. خدمة الطلبات (Order Service) استلمت الطلب بنجاح وسجلته في قاعدة بياناتها.
  2. خدمة الطلبات أرسلت أمر لخدمة المستودع (Inventory Service) عشان تخصم القطع المطلوبة. ونجحت العملية.
  3. خدمة الطلبات أرسلت أمر لخدمة الدفع (Payment Service) عشان تسحب المبلغ من بطاقة العميل. وهنا، “علّق النظام”! خدمة الدفع فشلت في إتمام العملية بسبب مشكلة مؤقتة في بوابة الدفع الخارجية.

النتيجة؟ خدمة الطلبات والمستودع بتعتقد إن الطلب ناجح، بينما خدمة الدفع (والعميل) بيعرفوا إنه فاشل. بياناتنا صارت في حالة فوضى وغير متناسقة. قضينا ساعات طويلة في هذاك اليوم نصلّح البيانات يدوياً ونتواصل مع العميل عشان نعتذر. وقتها قلت لحالي: “لهون وبس، لازم نلاقي حل جذري. شغل الترقيع هاد ما بزبط”. ومن هنا بدأت رحلتي الحقيقية مع نمط تصميم اسمه “الساجا” (Saga Pattern).

لماذا فشلت الطريقة التقليدية؟ (مشكلة المعاملات الموزعة)

في عالم التطبيقات المونوليثية القديمة، كانت الحياة أسهل. كل شيء موجود في مكان واحد وقاعدة بيانات واحدة. لما كنا نعمل عملية زي عملية الطلب، كنا نستخدم إشي اسمه “المعاملات” أو Transactions (وتحديداً ACID Transactions).

ببساطة، المعاملة بتضمن إنه مجموعة من العمليات يا إما بتنجح كلها مع بعض، أو بتفشل كلها مع بعض. لو فشلت خطوة واحدة، النظام تلقائياً بعمل “Rollback” أو تراجع عن كل الخطوات السابقة. زي كأنك بتقول للنظام: “يا كل إشي بتم، يا ولا إشي بتم”.

لكن في عالم الخدمات المصغرة، كل خدمة إلها قاعدة بياناتها الخاصة. خدمة الطلبات إلها قاعدة بياناتها، وخدمة الدفع إلها قاعدة بياناتها، وهلم جرا. محاولة تطبيق معاملة واحدة (Transaction) تشمل كل قواعد البيانات هاي هو كابوس تقني بنسميه “المعاملات الموزعة” (Distributed Transactions) باستخدام بروتوكول مثل (Two-Phase Commit – 2PC).

هاي الطريقة معقدة جداً، وبتبطئ النظام، والأهم من هيك، بتخلق اعتمادية (Coupling) قوية بين الخدمات، وهذا بعكس الهدف الأساسي من الخدمات المصغرة اللي هو الاستقلالية. باختصار، هالطريقة مش عملية بالمرة في الأنظمة الحديثة.

الحل السحري: نمط الساجا (Saga Pattern)

هنا يأتي دور نمط الساجا. الساجا مش معاملة (Transaction) بالمعنى التقليدي، بل هي طريقة لإدارة تناسق البيانات عبر خدمات متعددة بدون الحاجة لقفل قواعد البيانات.

الفكرة الأساسية للساجا هي: “سلسلة من المعاملات المحلية (Local Transactions)”.

كل خطوة في عمليتنا الموزعة (مثل إنشاء طلب، معالجة الدفع، تحديث المخزون) هي معاملة محلية بتصير داخل خدمة واحدة فقط. لما تنجح هاي المعاملة المحلية، الخدمة بتطلق حدث (Event) أو أمر (Command) عشان تبدأ المعاملة المحلية التالية في الخدمة اللي بعدها.

السر الأهم في الساجا: لكل إجراء (Action) تقوم به، يجب أن يكون هناك “إجراء تعويضي” (Compensating Action) مقابل له.

فإذا فشلت أي خطوة في منتصف السلسلة، يقوم نمط الساجا بتشغيل الإجراءات التعويضية بالترتيب العكسي لكل الخطوات السابقة التي نجحت. هذا الإجراء التعويضي وظيفته “إلغاء” أثر الإجراء الأصلي. خلينا نطبق هاد الحكي على مثالنا:

  • الإجراء: خدمة المستودع تخصم قطعة من المخزون.
  • الإجراء التعويضي: خدمة المستودع تضيف قطعة إلى المخزون (إلغاء الخصم).
  • الإجراء: خدمة الدفع تسحب المبلغ.
  • الإجراء التعويضي: خدمة الدفع تعيد المبلغ (Refund).

بهذه الطريقة، إذا فشل الدفع، ستقوم الساجا تلقائياً بتشغيل الإجراء التعويضي لإلغاء حجز القطعة في المستودع، وإلغاء الطلب في خدمة الطلبات، وبالتالي تعود حالة النظام إلى وضع متناسق. هذا ما يسمى بـ “التناسق النهائي” (Eventual Consistency).

أنواع الساجا: كيف ننسق الأوركسترا؟

هناك طريقتان أساسيتان لتطبيق نمط الساجا، وكل طريقة إلها حسناتها وسيئاتها.

1. التنسيق الكوريغرافي (Choreography)

في هذا النوع، لا يوجد منسق مركزي. كل خدمة بتعرف شو لازم تعمل لما تسمع حدث معين من خدمة أخرى. الأمر أشبه برقصة جماعية مدرب عليها جيداً، كل راقص بيعرف حركته التالية لما يشوف الراقص اللي قبله أنهى حركته.

كيف يعمل:

  1. خدمة الطلبات: تنشئ الطلب بحالة “قيد الإنشاء”، ثم تنشر حدث OrderCreatedEvent.
  2. خدمة الدفع: تستمع لهذا الحدث. عند استلامه، تحاول معالجة الدفع.
    • إذا نجح الدفع، تنشر حدث PaymentProcessedEvent.
    • إذا فشل الدفع، تنشر حدث PaymentFailedEvent.
  3. خدمة المستودع: تستمع لحدث PaymentProcessedEvent. عند استلامه، تخصم المخزون وتنشر حدث InventoryUpdatedEvent.
  4. خدمة الطلبات: تستمع لحدث InventoryUpdatedEvent لتغيير حالة الطلب إلى “مكتمل”، وتستمع أيضاً لحدث PaymentFailedEvent لتغيير حالة الطلب إلى “ملغي”.

الميزات: بسيطة في العمليات القصيرة، لا توجد نقطة فشل مركزية (No Single Point of Failure)، الخدمات منفصلة تماماً عن بعضها.

العيوب: يصعب تتبع سير العملية، خصوصاً مع زيادة عدد الخدمات. ممكن تصير فوضى “مين بيسمع لمين؟”. تصبح الصورة الكبيرة للعملية موزعة ومخفية داخل كل خدمة.

2. التنسيق الأوركسترالي (Orchestration)

في هذا النوع، يوجد “مايسترو” أو منسق (Orchestrator). هذا المنسق هو خدمة أو مكون مسؤول عن إدارة سير عمل الساجا بأكمله. هو الذي يخبر كل خدمة ماذا تفعل ومتى.

كيف يعمل:

لدينا خدمة جديدة اسمها OrderOrchestrator.

  1. العميل يرسل طلب إنشاء طلبية إلى المنسق.
  2. المنسق: يطلب من خدمة الطلبات إنشاء طلب.
  3. إذا نجح، المنسق: يطلب من خدمة الدفع معالجة الدفع.
  4. إذا نجح، المنسق: يطلب من خدمة المستودع تحديث المخزون.
  5. إذا نجح، المنسق: يطلب من خدمة الطلبات تحديث حالة الطلب إلى “مكتمل”.

وإذا فشلت خطوة؟ لنفترض أن خطوة الدفع (رقم 3) فشلت. يقوم المنسق بتشغيل الإجراءات التعويضية بالترتيب العكسي:

  1. المنسق: يطلب من خدمة الطلبات إلغاء الطلب (الإجراء التعويضي للخطوة 2).

الميزات: منطق العملية مركزي وواضح، أسهل في الفهم والتعديل والمراقبة، يمكن تتبع حالة الساجا بسهولة.

العيوب: المنسق قد يصبح نقطة فشل مركزية، هناك خطر إضافة منطق أعمال (Business Logic) زائد داخل المنسق بدلاً من إبقائه في الخدمات المختصة.

كمثال بسيط جداً على منطق المنسق بلغة تشبه الجافاسكريبت:


class OrderOrchestrator {
  async createOrder(orderData) {
    let orderId;
    let paymentId;

    try {
      // Step 1: Create Order
      const order = await orderService.create(orderData);
      orderId = order.id;

      // Step 2: Process Payment
      const payment = await paymentService.process(orderId, orderData.paymentDetails);
      paymentId = payment.id;

      // Step 3: Update Inventory
      await inventoryService.deduct(orderData.items);

      // Step 4: Mark order as complete
      await orderService.complete(orderId);

      return { success: true, orderId: orderId };

    } catch (error) {
      console.error("Saga failed! Starting compensation...", error);

      // Compensation logic (runs in reverse)
      if (paymentId) {
        // Compensate for successful payment
        await paymentService.refund(paymentId);
      }
      if (orderId) {
        // Compensate for created order
        await orderService.cancel(orderId);
      }
      
      return { success: false, error: "Order could not be processed." };
    }
  }
}

نصائح من “أبو عمر” لتطبيق الساجا بنجاح

بعد التجربة والخطأ، تعلمت شوية دروس “عالصعب”، وحابب أشارككم إياها:

  • ابدأ بالبساطة: مش كل إشي بده تعقيد يا جماعة. إذا كانت عمليتك تشمل خدمتين أو ثلاث فقط، قد يكون نمط الكوريغرافيا (Choreography) كافياً وأسرع في التطبيق. الأوركسترا (Orchestration) رائعة للعمليات المعقدة والطويلة.
  • اجعل الإجراءات قابلة للتكرار (Idempotent): تخيل لو أن خدمة الدفع استلمت نفس الأمر مرتين بسبب خطأ في الشبكة، هل ستخصم المبلغ مرتين؟ يجب أن تصمم خدماتك بحيث لو استلمت نفس الطلب أكثر من مرة، تكون النتيجة واحدة. هذا مبدأ مهم جداً في الأنظمة الموزعة.
  • صمم الإجراءات التعويضية بعناية فائقة: الإجراء التعويضي يجب أن يكون موثوقاً قدر الإمكان. ماذا لو فشل إجراء “إعادة المبلغ” (Refund)؟ هذه كارثة بحد ذاتها. يجب أن تكون هذه الإجراءات بسيطة جداً ولديها آليات إعادة محاولة قوية.
  • فكر في العزل (Isolation): خلال تنفيذ الساجا، بياناتك ستكون في حالة “غير متناسقة مؤقتاً”. هذا طبيعي. لكن يجب أن تفكر كيف ستتعامل واجهة المستخدم مع هذا. مثلاً، بدلاً من إظهار “تمت الطلبية” فوراً، أظهر “جاري معالجة الطلب…” حتى تكتمل الساجا بنجاح.
  • المراقبة والتسجيل (Logging & Monitoring): في الأنظمة الموزعة، بدون سجلات (Logs) واضحة ومراقبة مركزية، أنت كالأعمى في الظلام. يجب أن يكون لديك نظام يمكنك من تتبع كل خطوة في الساجا عبر جميع الخدمات لتعرف بالضبط أين ومتى حدث الخطأ.

الخلاصة: الساجا ليست عصا سحرية، بل أداة قوية 🧠

نمط الساجا لم يحل مشكلتي بضغطة زر، بل تطلب مني ومن فريقي إعادة التفكير في طريقة تصميمنا للعمليات. هو ليس حلاً لكل المشاكل، بل هو أداة قوية في صندوق أدوات مهندس البرمجيات للتعامل مع واقع الأنظمة الموزعة.

الانتقال للخدمات المصغرة يعني تبني عقلية “التناسق النهائي” بدلاً من “التناسق الفوري” الذي اعتدنا عليه. الساجا هي الجسر الذي يساعدنا على عبور هذه الفجوة بأمان وثقة.

نصيحتي الأخيرة لكل مطور أو معماري برمجيات يفكر في هذا الطريق: ادرس نمط الساجا جيداً، افهم أنواعه، وجرّبه في مشروع صغير قبل تطبيقه على نظامك الأساسي. الاستثمار في فهم هذه الأنماط اليوم سيوفر عليك الكثير من ليالي “الشاي بالنعناع” المتوترة في المستقبل. 💪

ودمتم سالمين.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التوظيف وبناء الهوية التقنية

سيرتي الذاتية كانت تضيع في الثقب الأسود للـ ATS: كيف أنقذتها ‘الكلمات المفتاحية المستهدفة’ من جحيم التجاهل الآلي؟

لسنوات، كنت أرسل سيرتي الذاتية المليئة بالخبرات والمشاريع، ولا أتلقى أي رد. في هذه المقالة، أشارككم قصتي مع "وحش" فرز الطلبات الآلي (ATS) وكيف تعلمت...

15 أبريل، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

طلباتنا كانت تموت في طابور الانتظار: كيف أنقذنا ‘تجميع اتصالات قاعدة البيانات’ (Connection Pooling) من جحيم إنشاء الاتصالات المكلفة؟

أشارككم قصة حقيقية من أرض المعركة البرمجية، حين كاد تطبيقنا أن ينهار تحت ضغط المستخدمين. اكتشفوا معنا كيف كانت تقنية "تجميع اتصالات قاعدة البيانات" (Connection...

15 أبريل، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

معاملاتنا كانت تُسرق في وضح النهار: كيف أنقذتنا نماذج تعلم الآلة من جحيم الاحتيال المتطور؟

أشارككم قصة حقيقية من قلب المعركة ضد الاحتيال المالي، وكيف انتقلنا من يأس الأنظمة التقليدية إلى قوة نماذج تعلم الآلة التي أنقذت شركتنا. هذه رحلتي...

15 أبريل، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

مهندسونا كانوا يرحلون بصمت: كيف أنقذتنا ‘مسارات النمو المهني’ من جحيم الركود الوظيفي؟

كنا نخسر أفضل مهندسينا الواحد تلو الآخر دون أن نفهم السبب الحقيقي. في هذه المقالة، أشارككم قصة كيف اكتشفنا القاتل الصامت "الركود الوظيفي"، وكيف أنقذنا...

15 أبريل، 2026 قراءة المزيد
أدوات وانتاجية

وداعاً للـ `console.log` العشوائي: كيف تتقن فن ‘التنقيح الحواري’ مع نماذج اللغة الكبيرة (LLMs)؟

أشارككم خبرتي كمبرمج، أبو عمر، في الانتقال من التنقيح العشوائي باستخدام `console.log` إلى استراتيجية "التنقيح الحواري" الفعالة مع نماذج الذكاء الاصطناعي. تعلم كيف تحول جلسات...

15 أبريل، 2026 قراءة المزيد
أتمتة العمليات

عملياتنا كانت تموت في صمت: كيف أنقذتنا ‘محركات تنسيق سير العمل’ (Workflow Engines) من جحيم الأعطال غير المتوقعة؟

أشارككم قصة حقيقية من تجربتي كمبرمج وكيف انتقلنا من الفوضى والأعطال الصامتة في عملياتنا المعقدة إلى نظام مستقر وموثوق. اكتشفوا كيف يمكن لـ "محركات تنسيق...

15 أبريل، 2026 قراءة المزيد
البودكاست