يا جماعة الخير، السلام عليكم ورحمة الله.
اسمحوا لي اليوم أن أشارككم قصة من قلب المعاناة، قصة من أيام وليالٍ طويلة قضيناها نحاول أن نفهم “ليش السيستم مش شغال صح؟”. قبل عدة سنوات، في إحدى الشركات التي عملت بها، كان لدينا نظام معقد لمعالجة طلبات العملاء. تبدأ العملية باستلام الطلب، ثم التحقق من الدفع، ثم حجز الموارد في المخزن، ثم إرسال الطلب لشركة الشحن، وأخيراً إرسال إيميل تأكيد للعميل مع رقم التتبع.
أتذكر جيداً ذاك اليوم، كان يوم جمعة، والمفترض أنه يوم عطلة وراحة. لكن هاتف الشركة لم يتوقف عن الرنين. عملاء غاضبون يقولون إنهم دفعوا المال لكن لم يصلهم أي شيء، وآخرون يقولون إن حالة طلبهم “قيد المعالجة” منذ يومين! دخلنا في حالة طوارئ، وبدأنا نغوص في سجلات النظام (Logs). الكارثة كانت أن السجلات لم تكن تظهر أي خطأ واضح. بعض العمليات كانت تبدأ، ثم تختفي في منتصف الطريق دون أي أثر. حرفياً، كانت عملياتنا تموت في صمت.
قضينا عطلة نهاية الأسبوع بأكملها ونحن نصلح الأمور يدوياً: نتصل بشركة الشحن لنتأكد من الطلبات، ونعيد إرسال الإيميلات، ونعتذر للعملاء. الوضع كان “مكركب” على الآخر. في تلك اللحظة، أدركنا أن المشكلة ليست في الكود الذي ينفذ كل خطوة، بل في “الغراء” الذي يربط هذه الخطوات ببعضها. هذا الغراء كان هشاً، وينكسر عند أول عاصفة (أو حتى نسمة هواء)، ولا يخبر أحداً أنه انكسر. هنا بدأت رحلتنا للبحث عن حل جذري، وهنا تعرفنا على عالم “محركات تنسيق سير العمل” (Workflow Engines).
ما هو الجحيم الذي كنا نعيش فيه؟ (شرح المشكلة بالتفصيل)
لكي تفهموا عمق المشكلة، دعونا نحلل طبيعة هذه العمليات التي كانت تسبب لنا الصداع النصفي الدائم.
العمليات الطويلة والمعقدة (Long-Running Processes)
أي عملية تجارية (Business Process) تتكون من عدة خطوات، وغالباً ما تمتد على فترة زمنية طويلة، هي ما نسميه “عملية طويلة الأمد”. أمثلة على ذلك:
- تسجيل مستخدم جديد: إنشاء حساب، إرسال إيميل ترحيبي، تفعيل خدمات معينة، إضافته إلى قائمة بريدية.
- معالجة طلبية: التحقق من الدفع، تحديث المخزون، تجهيز الشحنة، تسليمها لشركة الشحن، إعلام العميل.
- معالجة ملفات الفيديو: رفع الفيديو، تحويله إلى صيغ مختلفة (transcoding)، استخراج صورة مصغرة، تخزينه في شبكة توصيل المحتوى (CDN).
الطريقة الساذجة التي كنا نتبعها (والتي يتبعها الكثيرون) هي كتابة دالة (function) واحدة كبيرة، أو سلسلة من استدعاءات الـ APIs التي تنادي بعضها البعض. هذا النهج يعمل بشكل جيد في الظروف المثالية، لكن العالم الحقيقي ليس مثالياً.
صمت الأعطال القاتل
ماذا يحدث لو فشلت إحدى هذه الخطوات؟
- تعطل الشبكة أثناء الاتصال بخدمة خارجية.
- خدمة الدفع تأخرت في الرد (Timeout).
- قاعدة البيانات كانت مشغولة ولم تقبل الكتابة للحظات.
- حدث خطأ غير متوقع في الكود نفسه.
في نظامنا القديم، كان الفشل في خطوة واحدة يعني توقف العملية بأكملها. لا يوجد سجل يوضح أين توقفت بالضبط، ولا توجد آلية لإعادة المحاولة تلقائياً، والأسوأ من ذلك، أن البيانات تبقى في حالة غير متسقة (Inconsistent State). مثلاً، تم خصم المال من العميل (خطوة 1 نجحت)، لكن حجز المنتج في المخزن فشل (خطوة 2 فشلت). النتيجة؟ عميل غاضب ومنتج لم يتم حجزه.
جحيم إعادة المحاولة والتعويض (The Hell of Retries and Compensations)
طبعاً، حاولنا حل المشكلة برمجياً. بدأنا نضيف كوداً معقداً للتعامل مع الأخطاء. صار الكود مليئاً بـ try-catch، وحلقات while لإعادة المحاولة، ومنطق “التراجع” أو التعويض (Compensation).
منطق التعويض يعني أنه إذا فشلت الخطوة “ن”، يجب عليك التراجع عن آثار الخطوات الناجحة التي سبقتها. فإذا فشلت خطوة الشحن، يجب عليك إعادة المبلغ المالي للعميل. هذا المنطق معقد جداً وصعب الصيانة. صار الكود، زي ما بنحكي عنا في فلسطين، “زي صحن معكرونة، ما بتعرف أوله من آخره”. أصبح من المستحيل تقريباً اختبار كل السيناريوهات المحتملة.
المنقذ: محركات تنسيق سير العمل (Workflow Engines)
بعد بحث طويل، وجدنا الحل في مفهوم “محركات تنسيق سير العمل”. هذه التقنية لم تكن جديدة، لكنها كانت الحل الأمثل لمشكلتنا.
ما هي باختصار؟
ببساطة، محرك سير العمل هو نظام متخصص وظيفته الوحيدة هي تنسيق وإدارة العمليات متعددة الخطوات. فكر فيه على أنه “مايسترو الأوركسترا”. هو لا يعزف على الآلات الموسيقية (أي لا ينفذ منطق عملك بنفسه)، بل يخبر كل عازف (كل خدمة أو دالة) متى يبدأ، ومتى يتوقف، وماذا يفعل إذا أخطأ أحدهم.
هذا الفصل بين “التنسيق” (Orchestration) و”التنفيذ” (Execution) هو جوهر القوة في هذه المحركات.
المبادئ الأساسية التي غيرت كل شيء
محركات سير العمل مبنية على عدة مبادئ أساسية توفر حلاً لكل مشاكلنا السابقة:
- حفظ الحالة (Statefulness): المحرك يعرف دائماً وبشكل دقيق حالة كل عملية جارية. هل هي في الخطوة الأولى؟ الثانية؟ هل فشلت؟ هل تنتظر؟ إذا تعطل النظام بأكمله وأعيد تشغيله، يعرف المحرك تماماً من أين يكمل كل عملية.
- الديمومة (Durability): يتم حفظ هذه الحالة بشكل دائم في قاعدة بيانات. هذا يضمن أن حالة سير العمل تنجو من تعطل الخوادم أو إعادة التشغيل.
- القابلية للمراقبة (Observability): هذه كانت الميزة التي أبهرتنا. فجأة، أصبح لدينا لوحة تحكم (Dashboard) تعرض كل عمليات سير العمل الجارية، الناجحة، والفاشلة. لكل عملية فاشلة، يمكنك أن ترى بالضبط أي خطوة فشلت، وما هو الخطأ، وعدد مرات إعادة المحاولة. وداعاً للأعطال الصامتة!
- إدارة الأخطاء وإعادة المحاولة كميزة مدمجة: بدلاً من كتابة منطق إعادة المحاولة بنفسك، أنت ببساطة تقوم بتعريف ذلك. مثلاً: “أعد محاولة هذه الخطوة 5 مرات، مع زيادة فترة الانتظار بين كل محاولة”. المحرك يتكفل بالباقي.
- المهلات والجدولة: يمكنك بسهولة تحديد مهلة زمنية لكل خطوة (“إذا لم تنتهِ هذه الخطوة خلال 10 دقائق، اعتبرها فاشلة”)، أو جدولة خطوات مستقبلية (“بعد 3 أيام من الشحن، أرسل إيميلاً للعميل لتقييم المنتج”).
كيف تبدو الأمور على أرض الواقع؟ (مثال عملي)
دعونا نأخذ سيناريو “تسجيل مستخدم جديد” ونرى كيف يبدو بالطريقة القديمة والجديدة.
السيناريو:
- إنشاء سجل للمستخدم في قاعدة البيانات.
- إرسال إيميل ترحيبي.
- توفير بعض الموارد له على خدمة خارجية (Third-party API).
- إضافته إلى القائمة البريدية للتسويق.
الطريقة القديمة (الكود “المكركب”)
الكود قد يبدو هكذا (مثال بـ Pseudocode):
function registerUser(userInfo) {
try {
// الخطوة 1
createUserInDB(userInfo);
} catch (dbError) {
log("فشل في إنشاء المستخدم في قاعدة البيانات");
throw dbError; // أوقف كل شيء
}
try {
// الخطوة 2
sendWelcomeEmail(userInfo.email);
} catch (emailError) {
// هل يجب أن أوقف العملية كلها؟ أم أتجاهل الخطأ؟
log("فشل إرسال الإيميل الترحيبي");
// لنكمل على أي حال...
}
// الخطوة 3 - هذه الخطوة حرجة وقد تفشل
let retries = 3;
while (retries > 0) {
try {
provisionResources(userInfo.id);
break; // نجحت، اخرج من الحلقة
} catch (apiError) {
retries--;
if (retries == 0) {
log("فشل توفير الموارد بعد عدة محاولات");
// هنا الكارثة: يجب التراجع عن الخطوة 1
deleteUserFromDB(userInfo.id);
throw apiError; // أوقف كل شيء
}
sleep(5000); // انتظر 5 ثوانٍ قبل إعادة المحاولة
}
}
// الخطوة 4
addToNewsletter(userInfo.email);
return "تم التسجيل بنجاح";
}
لاحظوا مدى تعقيد الكود. منطق العمل مختلط مع منطق إدارة الأخطاء وإعادة المحاولة، مما يجعله صعب القراءة والصيانة.
الطريقة الجديدة: سير العمل كشيفرة (Workflow as Code)
معظم محركات سير العمل الحديثة (مثل Temporal، Cadence، أو AWS Step Functions) تسمح لك بتعريف سير العمل كشيفرة برمجية عادية. الكود يبدو بسيطاً ومتسلسلاً، لكن المحرك هو من يدير كل التعقيدات خلف الكواليس.
إليكم كيف سيبدو نفس المنطق باستخدام مفهوم “سير العمل كشيفرة” (مثال مستوحى من Temporal SDK بلغة Python):
# هذا هو تعريف "سير العمل"
# الكود يبدو عادياً، لكن المحرك يضمن تنفيذه بشكل موثوق
async def new_user_workflow(user_info: UserInfo):
# الخطوة 1: نشاط حرج
# المحرك سيعيد المحاولة تلقائياً عند الفشل بناءً على الإعدادات
await activities.create_user_in_db(user_info)
# الخطوة 2: نشاط غير حرج
# إذا فشل، لا نريد إيقاف سير العمل بأكمله
try:
await activities.send_welcome_email(user_info.email)
except Exception as e:
# فقط سجل الخطأ وأكمل
workflow.logger.warning(f"فشل إرسال الإيميل الترحيبي: {e}")
# الخطوة 3: نشاط حرج مع مهلة زمنية
# إذا استغرق أكثر من 10 دقائق، سيفشل تلقائياً
await workflow.execute_activity(
activities.provision_resources,
user_info.id,
start_to_close_timeout=timedelta(minutes=10),
retry_policy=RetryPolicy(maximum_attempts=5)
)
# الخطوة 4: نشاط بسيط
await activities.add_to_newsletter(user_info.email)
return f"اكتمل تسجيل المستخدم {user_info.name} بنجاح!"
# "الأنشطة" (Activities) هي الدوال العادية التي تنفذ منطق العمل
# يتم تشغيلها على "Workers" منفصلة
def create_user_in_db(user_info):
# ... كود الاتصال بقاعدة البيانات ...
def send_welcome_email(email):
# ... كود إرسال الإيميل ...
لاحظوا الفرق! الكود الذي يصف سير العمل (new_user_workflow) أصبح نظيفاً جداً ويركز فقط على تسلسل منطق العمل. كل تفاصيل إعادة المحاولة، والمهلات، وإدارة الأخطاء تم تعريفها بشكل وصفي (Declarative)، والمحرك هو من يتولى تنفيذها.
نصائح من “أبو عمر” لبدء رحلتك مع محركات سير العمل
إذا كنتم تعانون من نفس المشاكل التي عانينا منها، فإليكم بعض النصائح العملية من خبرتي:
نصيحة شخصية: تبني محرك سير عمل ليس مجرد تغيير تقني، بل هو تغيير في طريقة التفكير. تبدأ في تصميم أنظمتك لتكون قادرة على الصمود والتعافي من الأخطاء منذ اليوم الأول، بدلاً من إصلاحها بعد وقوع الكارثة.
ابدأ بالعمليات الأكثر إيلاماً
لا تحاول إعادة هيكلة كل شيء دفعة واحدة. حدد أكثر عملية تسبب لك المشاكل والأعطال الصامتة، وابدأ بها. ستكون هذه العملية هي دليلك لإثبات قيمة هذا النهج لبقية الفريق.
لا تخترع العجلة من جديد
هناك حلول ممتازة ومجربة في السوق. لا تضيع وقتك في بناء محرك سير عمل خاص بك إلا إذا كان لديك سبب قوي جداً. انظر إلى الخيارات المتاحة:
- مفتوحة المصدر (Open Source): Temporal و Cadence هما الخياران الأقوى حالياً، ويدعمان لغات برمجة متعددة.
- خدمات سحابية مُدارة (Managed Services): AWS Step Functions، و Google Cloud Workflows، و Azure Logic Apps. هذه الخيارات ممتازة إذا كنت تعتمد بشكل كبير على أحد مزودي الخدمات السحابية.
فكّر في “الأنشطة” (Activities) كوحدات مستقلة وقابلة للتكرار
الخطوة الواحدة في سير العمل (تسمى عادةً Activity) يجب أن تكون مصممة بشكل جيد. من أفضل الممارسات أن تجعلها Idempotent، ويعني ذلك أن تشغيلها عدة مرات بنفس المدخلات يعطي نفس النتيجة النهائية. مثلاً، دالة “إنشاء مستخدم” إذا تم استدعاؤها مرتين بنفس معلومات المستخدم، يجب ألا تنشئ مستخدمين اثنين، بل تتأكد من وجود المستخدم وتعيد نفس المعرّف. هذا يجعل عمليات إعادة المحاولة آمنة تماماً.
“سير العمل كشيفرة” هو المستقبل
بعض الأدوات القديمة كانت تعتمد على واجهات رسومية (Drag-and-Drop) لتصميم سير العمل. هذا قد يكون جيداً للعمليات البسيطة، لكن للأنظمة المعقدة، فإن تعريف سير العمل كشيفرة (Workflow as Code) هو الأفضل. لماذا؟ لأنه يتيح لك:
- استخدام نظام إدارة الإصدارات (مثل Git) لتتبع التغييرات.
- كتابة اختبارات آلية (Unit Tests) لسير العمل.
- إعادة استخدام الكود والمنطق بسهولة.
- التعاون بين المطورين بنفس الأدوات التي يستخدمونها يومياً.
الخلاصة: من الفوضى إلى النظام
رحلتنا من “الكود المكركب” والأعطال الصامتة إلى نظام موثوق وقابل للمراقبة كانت طويلة، لكنها كانت تستحق كل دقيقة. محركات سير العمل لم تحل مشاكلنا التقنية فحسب، بل منحتنا شيئاً أثمن بكثير: راحة البال.
لم نعد نقضي عطلات نهاية الأسبوع في مطاردة الأشباح في سجلات النظام. أصبحنا نثق بأن نظامنا إما سينجز المهمة المطلوبة بالكامل، أو سيخبرنا بوضوح ودقة لماذا وأين فشل، مما يسمح لنا بإصلاح المشكلة وإعادة تشغيل العملية من حيث توقفت بضغطة زر.
نصيحتي الأخيرة لك: إذا كانت أنظمتك تحتوي على أي عمليات تتجاوز مجرد طلب واستجابة بسيطة، فإن الاستثمار في تعلم وتطبيق محرك سير عمل ليس رفاهية، بل هو ضرورة لبناء برمجيات قوية وموثوقة تستطيع الصمود في وجه فوضى العالم الحقيقي. 🙏