طلبية مكتملة، دفع فاشل: كيف أنقذني نمط ‘الساجا’ (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) واضحة ومراقبة مركزية، أنت كالأعمى في الظلام. يجب أن يكون لديك نظام يمكنك من تتبع كل خطوة في الساجا عبر جميع الخدمات لتعرف بالضبط أين ومتى حدث الخطأ.

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

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

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

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

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

أبو عمر

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

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

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

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

آخر المدونات

الحوسبة السحابية

كل نقرة في لوحة التحكم كانت قنبلة موقوتة: كيف أنقذتني ‘البنية التحتية كشيفرة’ (IaC) من كارثة محققة؟

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

17 مارس، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

مقابلاتي السلوكية كانت كارثة: كيف أنقذتني طريقة STAR من أسئلة ‘حدثنا عن موقف صعب…؟’

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

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

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

أشارككم قصة حقيقية من قلب المعركة البرمجية، حيث كادت خدمة واحدة بطيئة أن تُسقط نظامنا بالكامل. سأشرح لكم بالتفصيل نمط "قاطع الدائرة" (Circuit Breaker)، وكيف...

16 مارس، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

كنا نخزن بطاقات الائتمان مباشرة… قصة تسريب بيانات وكيف أنقذني الترميز (Tokenization)

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

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

استيقظتُ في الثالثة فجراً لإعادة تشغيل سيرفر: كيف علّمتُ نظامي أن يشفي نفسه بنفسه عبر الأتمتة؟

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

14 مارس، 2026 قراءة المزيد
تسويق رقمي

إعلاناتي كانت تستهدف الجميع… وبالتالي لم تصل لأحد: كيف استخدمتُ نماذج التجزئة (Clustering) لاكتشاف شرائح عملاء لم أكن أعرف بوجودها؟

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

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