يا جماعة الخير، السلام عليكم ورحمة الله.
اسمحوا لي أن أبدأ بقصة قصيرة حدثت معي ومع فريقي قبل بضع سنوات. كانت الساعة قد تجاوزت الثانية صباحًا، وأنا في المكتب مع اثنين من زملائي، وأكواب القهوة السادة الفارغة تملأ الطاولة. كنا نحاول إصلاح خطأ حرج في نظامنا الأساسي، ذلك النظام القديم الذي ورثناه والذي كنا نطلق عليه اسم “الغول”.
كان “الغول” نظامًا متراصًا (Monolith) كبيراً، كُتب قبل سنوات طويلة بلغة وتقنيات أكل عليها الدهر وشرب. كلما حاولنا إصلاح جزء منه، كانت ثلاثة أجزاء أخرى تتعطل بشكل غامض. في تلك الليلة، بعد أن تسبب إصلاح بسيط في توقف خدمة الدفع بالكامل، نظر إلينا مدير المشروع بعينين يملؤهما الإرهاق وقال جملته الشهيرة: “خلص، لازم نعيد كتابة كل النظام من الصفر!”.
في تلك اللحظة، شعرت ببرودة تسري في عظامي. إعادة الكتابة الكاملة؟ هذا ليس قرارًا، هذا حكم بالإعدام على إنتاجية الفريق لشهور، وربما لسنوات. إنه يعني الدخول في نفق مظلم لا نعرف متى أو كيف سنخرج منه. هنا تدخلت وقلت: “يا جماعة، اهدوا شوي… في حل ثاني. شو رأيكم نستخدم طريقة ‘التينة الخانقة’؟”. نظر إليّ الجميع باستغراب، وكأنني أتحدث عن تعويذة سحرية. لكنها كانت بالفعل بداية خلاصنا.
المعضلة الأزلية: إعادة الكتابة أم الترقيع المستمر؟
قبل أن نغوص في تفاصيل الحل، دعونا نتفق على أن كل فريق تقني يواجه هذا السؤال في مرحلة ما. لديكم نظام قديم، يعمل لكن بصعوبة، وتطويره أصبح مكلفًا وبطيئًا ومحفوفًا بالمخاطر.
- إعادة الكتابة الكاملة (The Big Rewrite): تبدو فكرة مغرية. فرصة للبدء من جديد، باستخدام أحدث التقنيات، وتصميم نظيف، وحل كل المشاكل. لكنها في الواقع فخ خطير. فالمشاريع التي تعتمد هذا النهج غالبًا ما تفشل أو تتجاوز الميزانية والوقت بشكل كارثي. أنت لا تعرف كل خبايا النظام القديم، وستقضي شهورًا طويلة في محاولة الوصول لنفس الميزات الحالية قبل أن تبدأ في إضافة أي شيء جديد.
- الترقيع المستمر (Patching): هو الخيار الآمن ظاهريًا. نستمر في إضافة “لاصقات” برمجية على النظام القديم. لكن مع الوقت، يصبح النظام ككرة من الشريط اللاصق، معقدًا، هشًا، ومستحيل الصيانة. أنت تؤجل المشكلة فقط، لا تحلها.
فكيف نخرج من هذه الدوامة؟ هنا يأتي دور بطل قصتنا.
نمط التين الخانق (Strangler Fig Pattern): ما هو وشو قصته؟
الاسم قد يبدو غريبًا، لكنه مستوحى مباشرة من الطبيعة. صاغ هذا المصطلح الخبير “مارتن فاولر” بعد رحلة له إلى أستراليا، حيث رأى أشجار التين الخانقة.
من وحي الطبيعة 🌳
شجرة التين الخانقة تبدأ حياتها كبذرة صغيرة تنمو على فروع شجرة أخرى ضخمة وقديمة (الشجرة المضيفة). مع الوقت، تُنزل التينة جذورها إلى الأرض، وتنمو وتلتف حول الشجرة القديمة، وتتنافس معها على الضوء والموارد. شيئًا فشيئًا، تنمو التينة وتصبح أقوى، بينما تضعف الشجرة القديمة وتتحلل بداخلها، حتى لا يتبقى في النهاية سوى شجرة التين الجديدة، القوية، والتي أخذت مكان القديمة بالكامل. العملية تدريجية، وآمنة، وفعالة.
في عالم البرمجيات
الفكرة هي نفسها تمامًا. بدلًا من هدم النظام القديم دفعة واحدة، نقوم ببناء نظام جديد حوله، قطعة بقطعة. النظام الجديد “يخنق” النظام القديم تدريجيًا حتى يتقاعد القديم بالكامل ويصبح النظام الجديد هو المسيطر.
“نمط التين الخانق هو نهج لتحديث الأنظمة القديمة بشكل تدريجي وآمن، عبر بناء خدمات جديدة تحل محل وظائف النظام القديم واحدة تلو الأخرى، مع توجيه حركة المرور إليها تدريجيًا.”
خطوات عملية لتطبيق النمط: كيف فعلناها بالضبط؟
طيب يا أبو عمر، حكيت كتير، ورّينا الشغل! حسنًا، إليكم الخطوات العملية التي اتبعناها لخنق “الغول” وإنقاذ مشروعنا.
الخطوة الأولى: تحديد الوظيفة المراد استبدالها (Identify)
لا تحاول استبدال كل شيء دفعة واحدة. اختر جزءًا واحدًا من النظام. نصيحتي: ابدأ بشيء ليس شديد التعقيد، لكنه في نفس الوقت يمثل قيمة مضافة واضحة عند تحديثه. في حالتنا، اخترنا وحدة إدارة ملفات المستخدمين (User Profiles). كانت قديمة، بطيئة، وتسبب لنا الكثير من المشاكل.
الخطوة الثانية: بناء “الواجهة” أو “البروكسي” (Facade)
هذه هي أهم خطوة. تحتاج إلى وضع طبقة وسيطة (Proxy أو Facade) أمام نظامك القديم. كل الطلبات التي كانت تذهب مباشرة إلى النظام القديم، يجب أن تمر الآن عبر هذه الواجهة. هذه الواجهة هي التي ستقرر لاحقًا توجيه الطلب إلى النظام القديم أم إلى الخدمة الجديدة.
يمكن أن تكون هذه الواجهة بسيطة مثل Nginx/HAProxy reverse proxy، أو خدمة API Gateway، أو حتى تطبيق بسيط مكتوب بأي لغة.
الخطوة الثالثة: بناء الخدمة الجديدة (Implement)
الآن يأتي الجزء الممتع. قم ببناء الخدمة الجديدة (Microservice) التي ستحل محل الوظيفة التي اخترتها في الخطوة الأولى. استخدم أحدث التقنيات، وأفضل الممارسات، واجعلها مستقلة وقوية. في حالتنا، بنينا خدمة إدارة المستخدمين الجديدة باستخدام Node.js ووضعناها في حاوية Docker.
الخطوة الرابعة: التوجيه والتحويل التدريجي (Strangle)
هنا يبدأ السحر. في الواجهة التي بنيتها (البروكسي)، ستقوم بتعديل المنطق لتوجيه الطلبات. في البداية، كل الطلبات المتعلقة بملفات المستخدمين ستظل تذهب للنظام القديم. ثم، تبدأ بالتحويل:
- يمكنك البدء بتوجيه 1% فقط من الطلبات إلى الخدمة الجديدة.
- راقب الأداء والأخطاء عن كثب. هل كل شيء يعمل كما هو متوقع؟
- إذا كانت الأمور جيدة، زد النسبة إلى 10%، ثم 25%، 50%، وهكذا.
- هذا التحويل التدريجي يقلل المخاطرة إلى الصفر تقريبًا. لو حدثت مشكلة في الخدمة الجديدة، يمكنك ببساطة إعادة النسبة إلى 0% والعودة للنظام القديم فورًا دون أن يشعر المستخدم بأي شيء.
مثال بسيط جدًا على بروكسي باستخدام Express.js (Node.js):
const express = require('express');
const axios = require('axios');
const app = express();
const NEW_USERS_SERVICE_URL = 'http://new-users-service:3001';
const LEGACY_SYSTEM_URL = 'http://legacy-ghoul-system:8080';
// هذا هو "المفتاح" الذي يقرر أين يذهب الطلب
function shouldRouteToNewService(req) {
// منطق التحويل يمكن أن يكون معقدًا:
// 1. نسبة مئوية عشوائية
// 2. بناءً على هيدر معين (e.g., 'x-use-new-service: true')
// 3. بناءً على هوية المستخدم (للتجربة على مستخدمين داخليين أولاً)
return Math.random() {
const targetUrl = req.originalUrl;
if (shouldRouteToNewService(req)) {
console.log(`توجيه الطلب إلى الخدمة الجديدة: ${targetUrl}`);
try {
// أعد توجيه الطلب إلى الخدمة الجديدة
const response = await axios({
method: req.method,
url: `${NEW_USERS_SERVICE_URL}${targetUrl}`,
data: req.body,
headers: req.headers
});
res.status(response.status).send(response.data);
} catch (error) {
// في حال فشل الخدمة الجديدة، يمكننا تسجيل الخطأ والعودة للنظام القديم
console.error('فشل في الخدمة الجديدة، العودة للنظام القديم', error.message);
// ... منطق العودة للنظام القديم هنا ...
res.status(500).send('حدث خطأ في الخدمة الجديدة');
}
} else {
console.log(`توجيه الطلب إلى النظام القديم: ${targetUrl}`);
// ... منطق إعادة التوجيه إلى النظام القديم هنا ...
// Proxy request to LEGACY_SYSTEM_URL
res.redirect(307, `${LEGACY_SYSTEM_URL}${targetUrl}`);
}
});
app.listen(3000, () => {
console.log('البروكسي الخانق يعمل على المنفذ 3000');
});
الخطوة الخامسة: إزالة الكود القديم (Retire)
بمجرد أن يتم توجيه 100% من الطلبات إلى الخدمة الجديدة وتتأكد من استقرارها لفترة كافية، تأتي اللحظة الحاسمة والمُرضية: حذف الكود القديم من النظام الأصلي. يا له من شعور رائع! 🎉
الآن، كرر هذه العملية (الخطوات 1-5) مع وظيفة أخرى في النظام القديم. ومع كل دورة، تنمو شجرتك الجديدة وتخنق جزءًا آخر من “الغول”، حتى يختفي تمامًا.
نصائح من قلب المعركة: خلاصة خبرة أبو عمر
- المراقبة ثم المراقبة (Monitoring): قبل أن تبدأ، تأكد من أن لديك أدوات مراقبة قوية (Logging, Metrics, Tracing). بدونها، ستكون كمن يقود سيارة في الظلام. يجب أن تعرف فورًا إذا كانت خدمتك الجديدة تسبب مشاكل.
- قاعدة البيانات هي التحدي الأكبر: غالبًا ما يكون أصعب جزء هو التعامل مع البيانات المشتركة. قد تحتاج في البداية أن تجعل الخدمة الجديدة والقديمة تتشاركان نفس قاعدة البيانات، ثم تفكر لاحقًا في فصل البيانات. كن حذرًا جدًا هنا.
- لا تتحمس أكثر من اللازم: ابدأ صغيرًا. لا تحاول خنق نصف النظام دفعة واحدة. النجاح في خنق جزء صغير سيعطيك الزخم والثقة (ودعم الإدارة) للمتابعة.
- التواصل هو المفتاح: تأكد من أن كل أعضاء الفريق، من المطورين إلى مدراء المنتجات، يفهمون هذا النهج. الشفافية حول العملية والمخاطر والفوائد ضرورية.
الخلاصة: لا تهدم المعبد القديم دفعة واحدة
يا جماعة، تحديث الأنظمة القديمة ليس سباق سرعة، بل هو ماراثون. إعادة الكتابة الكاملة تشبه هدم مبنى قديم بالديناميت؛ قد تنجح، لكنك على الأغلب ستسبب أضرارًا جانبية هائلة وقد ينهار كل شيء فوق رأسك.
نمط التين الخانق، على الجانب الآخر، يشبه ترميم المبنى غرفة بغرفة، مع الحفاظ على هيكله قائمًا ويعمل طوال الوقت. إنه نهج عملي، آمن، ويحترم حقيقة أن الأنظمة الحية لا يمكن إطفاؤها لشهور من أجل “التحديث”.
في المرة القادمة التي يصرخ فيها أحدهم “يجب أن نعيد كتابة كل شيء!”، خذ نفسًا عميقًا، واطلب فنجان قهوة، واقترح عليهم أن يزرعوا “تينة خانقة” صغيرة. قد تنقذ مشروعك وفريقك من كابوس حقيقي. 👍
ودمتم سالمين مبرمجين.