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

يا جماعة الخير، السلام عليكم ورحمة الله. اسمي أبو عمر، وأنا اليوم جاي أحكي لكم قصة صارت معنا في واحد من المشاريع الكبيرة اللي اشتغلت عليها. قصة عن ليالٍ طويلة من تصحيح الأخطاء، وعن “كود السباغيتي” اللي كان معشش في مهامنا الخلفية (Background Jobs) ومخلي حياتنا جحيم.

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

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

ما هو جحيم السباغيتي الذي كنا نعيشه؟

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

تداخل الشروط (Nested Ifs) والاعتماد على الحالات (State Flags)

كان الكود تبعنا عبارة عن سلسلة طويلة من جمل if/else المتداخلة. كل خطوة بتعتمد على نجاح الخطوة اللي قبلها. وكنا نستخدم حقل في جدول الطلبيات اسمه status عشان نعرف وين وصلت العملية. فبتلاقي حالات زي PENDING, PAYMENT_VERIFIED, STOCK_DEDUCTED, SHIPPING_FAILED.

المشكلة في هاي الطريقة:

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

صعوبة تصحيح الأخطاء (Debugging) والمراقبة

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

المنقذ: ما هي “محركات سير العمل” (Workflow Engines)؟

بعد ليالٍ من البحث والتجربة، وجدنا الحل اللي أنقذنا: محركات سير العمل (Workflow Engines). في البداية الاسم كان برنّ شوي، وبيخوف، لكن لما فهمنا الفكرة، اكتشفنا إنها أبسط وأقوى مما بنتخيل.

فكر فيها كـ “مخرج مسرحي” لمهامك

أفضل تشبيه لمحرك سير العمل هو “المخرج المسرحي”. في المسرحية، المخرج ما بمثل كل الأدوار، لكنه بيعرف تسلسل الأحداث بالضبط. هو اللي بيحكي للممثل “أ” يدخل عالمسرح، وبعد ما يخلص حواره، بيعطي إشارة للممثل “ب” عشان يبدأ دوره. لو واحد من الممثلين نسي الحوار تبعه أو تعثر (يعني فشلت المهمة)، المخرج عنده خطة بديلة: ممكن يخليه يعيد الحوار (Retry)، أو يدخل ممثل بديل (Error Handling)، أو حتى ينهي المشهد بطريقة مختلفة (Compensation).

محرك سير العمل بيعمل نفس الشي بالضبط:

  1. أنت تعرّف له “المسرحية”: بتكتب كود بسيط يحدد تسلسل المهام (الخطوة الأولى، ثم الثانية، ثم الثالثة). هذا اسمه “تعريف سير العمل” (Workflow Definition).
  2. هو يدير “الممثلين”: كل مهمة (مثل “التحقق من المخزون” أو “معالجة الدفع”) هي “ممثل” أو “عامل” (Worker). المحرك هو اللي بنادي على كل عامل في وقته المناسب.
  3. هو يتعامل مع الأخطاء: لو فشل أي عامل، المحرك بيقدر يعيد المحاولة تلقائياً، أو ينفذ منطق خاص للتعامل مع الخطأ، كل هذا بدون ما تضطر تكتب كود معقد للتعامل مع كل حالة فشل ممكنة.
  4. هو يحتفظ بالحالة (State): المحرك بيعرف بالضبط كل عملية وين وصلت، حتى لو النظام كله انطفى ورجع اشتغل. ما في داعي لحقل الـ status المعقد في قاعدة البيانات.

كيف طبقنا الحل عملياً؟ رحلة من الفوضى إلى التنظيم

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

قبل: كود السباغيتي

الكود القديم كان يشبه هذا (مثال مبسط بلغة Python للتوضيح):


def process_order_spaghetti(order_id):
    order = db.get_order(order_id)
    
    # الخطوة 1: التحقق من المخزون
    try:
        inventory_service.check(order.items)
        db.update_order_status(order_id, "INVENTORY_CHECKED")
    except Exception as e:
        db.update_order_status(order_id, "INVENTORY_FAILED")
        log.error(f"Inventory check failed for {order_id}: {e}")
        return # توقف العملية كلها

    # الخطوة 2: معالجة الدفع
    try:
        payment_service.charge(order.user_id, order.amount)
        db.update_order_status(order_id, "PAYMENT_SUCCEEDED")
    except Exception as e:
        db.update_order_status(order_id, "PAYMENT_FAILED")
        log.error(f"Payment failed for {order_id}: {e}")
        # شو نعمل هسا؟ هل لازم نرجع المخزون؟ قصة...
        return
        
    # الخطوة 3: إرسال إيميل
    try:
        email_service.send_confirmation(order.user_id)
        db.update_order_status(order_id, "COMPLETED")
    except Exception as e:
        # مصيبة! الدفع تم والمخزون انخصم بس ما قدرنا نبعث ايميل
        db.update_order_status(order_id, "EMAIL_FAILED")
        log.error(f"Email failed for {order_id}: {e}")
        return

لاحظ كمية الـ try/except وتحديثات الحالة. هذا الكود صعب جداً صيانته وتطويره.

بعد: باستخدام محرك سير العمل

مع محرك سير العمل، فصلنا “تنسيق” العملية عن “تنفيذ” المهام. صار عنا جزئين:

  1. تعريف سير العمل (Workflow Definition): يصف التسلسل فقط.
  2. الأنشطة (Activities): كل نشاط هو دالة بسيطة تقوم بمهمة واحدة محددة.

الكود الجديد صار يشبه هذا (مثال مفاهيمي مستوحى من Temporal):

ملاحظة فنية: الكود التالي يصف المنطق. في التطبيق الحقيقي، ستحتاج إلى تشغيل “عامل” (Worker) يستمع للمهام من محرك سير العمل.


# الجزء الأول: تعريف سير العمل (المخرج المسرحي)
# هذا الكود يحدد "ماذا" يجب أن يحدث و"بأي ترتيب"

@workflow.defn
class OrderProcessingWorkflow:
    @workflow.run
    async def run(self, order_details):
        # لاحظ عدم وجود try/except أو if/else للتحكم في التدفق
        # المحرك يتكفل بالتعامل مع الأخطاء وإعادة المحاولة
        
        # استدعاء النشاط الأول
        await workflow.execute_activity(
            check_inventory, order_details, retry_policy=RetryPolicy(maximum_attempts=3)
        )
        
        # استدعاء النشاط الثاني
        await workflow.execute_activity(
            process_payment, order_details, start_to_close_timeout=timedelta(minutes=1)
        )
        
        # استدعاء النشاط الثالث
        await workflow.execute_activity(
            ship_order, order_details, retry_policy=RetryPolicy(maximum_attempts=5)
        )
        
        # استدعاء النشاط الرابع
        await workflow.execute_activity(
            send_confirmation_email, order_details, retry_policy=RetryPolicy(maximum_attempts=10)
        )

        return "Order Completed Successfully"

# -------------------------------------------------

# الجزء الثاني: الأنشطة (الممثلون)
# كل دالة تقوم بمهمة واحدة فقط، ولا تعرف شيئاً عن سير العمل

@activity.defn
async def check_inventory(details):
    print(f"Checking inventory for order {details['order_id']}...")
    # ... الكود الفعلي للتحقق من المخزون ...
    # if inventory_not_available: raise Exception("Out of stock!")

@activity.defn
async def process_payment(details):
    print(f"Processing payment for order {details['order_id']}...")
    # ... الكود الفعلي للتواصل مع بوابة الدفع ...

@activity.defn
async def ship_order(details):
    print(f"Creating shipment for order {details['order_id']}...")
    # ... الكود الفعلي لإخطار نظام الشحن ...
    
@activity.defn
async def send_confirmation_email(details):
    print(f"Sending confirmation email for order {details['order_id']}...")
    # ... الكود الفعلي لإرسال الإيميل ...

الجمال في هذه الطريقة:

  • وضوح الكود: تعريف سير العمل أصبح يقرأ كأنه وصف باللغة الإنجليزية للعملية.
  • فصل المسؤوليات: المطور الذي يعمل على نظام الدفع لا يحتاج أن يفهم أي شيء عن نظام المخزون.
  • المتانة (Resilience): لاحظ كيف أضفنا سياسة إعادة المحاولة (RetryPolicy) بسهولة. لو فشلت خدمة الإيميلات، المحرك سيستمر في المحاولة تلقائياً حسب السياسة اللي حددناها، بدون أي تدخل يدوي أو كود إضافي منا.
  • الرؤية والمراقبة: كل محركات سير العمل تأتي مع واجهة رسومية تريك كل عملية بدأت، في أي خطوة هي الآن، وما هي العمليات التي فشلت مع سجل كامل للأخطاء. ودّعنا التخمين ومطاردة الأشباح في السجلات.

نصائح من خبرة أبو عمر: كيف تختار وتستخدم محرك سير العمل المناسب؟

بعد ما عشنا هاي التجربة، صار عندي شوية نصائح بحب أشاركها مع أي فريق بفكر يتبنى هاي التقنية.

1. ابدأ صغيراً ومؤلماً

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

2. صمم مهامك لتكون “Idempotent”

مصطلح تقني شوي، بس فكرته بسيطة. المهمة الـ “Idempotent” هي المهمة اللي لو نفذتها 10 مرات بنفس المدخلات، النتيجة النهائية بتكون نفس نتيجة تنفيذها مرة واحدة. مثلاً، عملية “خصم 10 دولارات من رصيد” ليست كذلك، لأنك لو نفذتها مرتين رح تخصم 20. لكن عملية “ضبط الرصيد ليصبح 90 دولار” هي عملية idempotent. هذا المبدأ مهم جداً لأنه يسمح للمحرك بإعادة المحاولة بأمان عند الفشل.

3. لا تضع منطق العمل في تعريف سير العمل

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

4. اختر الأداة المناسبة للمهمة

ليست كل محركات سير العمل متشابهة. هناك أنواع مختلفة:

  • محركات موجهة للمطورين (Code-first): مثل Temporal.io أو AWS Step Functions. هنا أنت تعرّف سير العمل باستخدام الكود (Python, Go, Java, etc). هذه رائعة للعمليات المعقدة التي تتطلب مرونة عالية.
  • محركات موجهة للرسوميات (BPMN-based): مثل Camunda. تتيح لك وللمحللين رسم سير العمل كـ flowchart باستخدام معيار BPMN 2.0. هذا ممتاز للعمليات التي يشارك في تعريفها أشخاص غير تقنيين (مثل مدراء العمليات).
  • محركات موجهة للبيانات (Data-centric): مثل Apache Airflow. هذه مصممة خصيصاً لتنسيق مهام استخراج وتحويل وتحميل البيانات (ETL).

اختر الأداة التي تناسب طبيعة عملياتك وفريقك.

الخلاصة: وداعاً للسباغيتي، ومرحباً بالتحكم الكامل ✅

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

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

أبو عمر

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

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

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

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

آخر المدونات

التكنلوجيا المالية Fintech

كانت دفاترنا لا تتطابق أبداً: كيف أنقذنا ‘نظام التسوية الآلي’ من جحيم الأخطاء المالية الصامتة؟

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

9 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كانت حاوياتنا جزراً منعزلة: كيف أنقذنا Kubernetes من جحيم التنسيق اليدوي؟

أشارككم قصة من أرض المعركة التقنية، كيف انتقلنا من فوضى إدارة حاويات Docker اليدوية إلى عالم الأتمتة المنظم مع Kubernetes. مقالة عملية للمطورين ومسؤولي الأنظمة...

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

كانت معرفتي التقنية تتلاشى: كيف أنقذني نظام ‘الدماغ الثاني’ من جحيم إعادة اختراع العجلة؟

أشارككم قصتي كـ "أبو عمر"، مطور برمجيات، مع تلاشي المعرفة التقنية وكيف أنقذني بناء "دماغ ثانٍ" باستخدام أداة مثل Obsidian. اكتشفوا كيف تحولت من إعادة...

9 مايو، 2026 قراءة المزيد
نصائح برمجية

كان كودنا ينهار عند أول مفاجأة: كيف أنقذتنا ‘البرمجة الدفاعية’ من جحيم الثقة العمياء بالمدخلات؟

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

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

كان تحديث نظامنا القديم أشبه بجراحة قلب مفتوح: كيف أنقذنا نمط ‘التين الخانق’ من جحيم المخاطرة الكبرى؟

أشارككم قصة حقيقية من قلب المعركة التقنية، حيث واجهنا مهمة تحديث نظام قديم ومعقد. في هذه المقالة، سأشرح كيف أنقذنا نمط "التين الخانق" (Strangler Fig...

9 مايو، 2026 قراءة المزيد
خوارزميات

كانت قاعدة بياناتنا تستنزفها الأشباح: كيف أنقذتنا ‘مرشحات بلوم’ من جحيم الاستعلامات الضائعة؟

في عالم البرمجة، ليست كل المشاكل تتطلب حلولاً دقيقة 100%. أشارككم قصة من قلب المعركة التقنية، وكيف أنقذنا هيكل بيانات احتمالي بسيط يُدعى 'مرشح بلوم'...

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