طلبية مكتملة، دفع فاشل: كيف أنقذني نمط ‘الساجا’ (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 لكنها فشلت أمام المدير التقني: كيف أعدت بناءها لتتحدث لغة المهندسين؟

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

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

خدمة واحدة فاشلة كادت أن تسقط النظام بأكمله: كيف أنقذني نمط ‘قاطع الدائرة’ (Circuit Breaker) من كارثة متتالية؟

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

27 فبراير، 2026 قراءة المزيد
اختبارات الاداء والجودة

لقد ‘هاجمت’ تطبيقي بنفسي عمداً: كيف كشفت لي ‘هندسة الفوضى’ نقاط الضعف التي لم تظهرها الاختبارات التقليدية

أشارككم قصة حقيقية حول إطلاق فاشل كاد أن يدمر سمعتنا، وكيف قادتنا هذه التجربة المريرة إلى تبني "هندسة الفوضى" (Chaos Engineering). اكتشفوا معنا كيف يمكن...

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

عاصفة من الطلبات كادت أن تغرق تطبيقي: كيف أنقذتني طوابير الرسائل (Message Queues) من كارثة الجمعة السوداء؟

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

24 فبراير، 2026 قراءة المزيد
​معمارية البرمجيات

تحديث النظام القديم كان كابوساً: كيف ‘خنقت’ المونوليث تدريجياً بنمط Strangler Fig دون توقف الخدمة

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

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