الرئيسيةمن أناالدوراتالمدونةسوق الأوامرالمناهج والباقاتالشركاء

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • سوق الأوامر
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالمناهجالمدونةالدخول
How To Make It

اعمل نظام Feature Flags بنفسك في Node.js: انشر الكود من غير ما تطلقه

متوسط20 يوليو 20266 دقائق قراءة
اعمل نظام Feature Flags بنفسك في Node.js: انشر الكود من غير ما تطلقه

مستوى المقال: متوسط. يفترض إنك تعرف Node.js و Express على مستوى أساسي، وتقدر تقرأ كود JavaScript بسيط.

هدف المقال واضح: تفصل النشر (deploy) عن الإطلاق (release). يعني تحط الكود الجديد على الإنتاج وهو مقفول، وتفتحه لـ 5% من المستخدمين بس، ولو ظبط تكمّل لحد 100%، ولو باظ تطفيه في أقل من ثانية من غير ما تعمل rollback أو redeploy. هنبني الأداة دي بنفسنا في أقل من 40 سطر، من غير أي مكتبة خارجية.

المشكلة باختصار

الطريقة الشائعة: تدمج الميزة الجديدة، تعمل deploy، وفي نفس اللحظة كل المستخدمين بيشوفوها. الطريقة دي بتفشل وقت الضغط: لو فيه باج ظهر بس عند 50 ألف مستخدم في الساعة، خيارك الوحيد هو rollback كامل — بناء جديد، نشر جديد، و10 دقايق الخدمة فيها مشكلة. المشكلة إنك ربطت "الكود موجود على السيرفر" بـ "كل الناس بتستخدمه"، وهما لازم يكونوا حاجتين منفصلتين.

يعني إيه Feature Flag؟ (مثال بسيط الأول)

تخيّل مفتاح النور في الصالة، بس مش مفتاح عادي. ده مفتاح بيقدر يوزّع النور بالتقسيط: يضيء لـ 5 ضيوف بس من أصل 100 في الأوضة، والباقي لسه قاعدين في نص الإضاءة القديمة. لو الـ 5 عجبهم، تلفّ المفتاح شوية كمان فيوصل لـ 25، بعدين 50، لحد ما الكل يشوف النور الجديد. ولو حد اشتكى، ترجّع المفتاح لصفر في ثانية والكل يرجع زي الأول.

ده بالظبط الـ Feature Flag. تقنيًا هو شرط منطقي (boolean) بيتقيّم وقت التشغيل لكل طلب، وبيقرّر أي مسار كود يشتغل: الجديد ولا القديم. المفتاح مش مكتوب في الكود ثابت، هو بيتقرا من مكان تقدر تغيّره من بره (ملف أو Redis)، فتقدر تبدّل السلوك من غير ما تلمس الكود.

الخطوات: نبني المُقيّم (evaluator)

محتاجين حاجتين: مصدر للـ flags (نبدأ بملف بسيط)، ودالة تقرّر لكل مستخدم هو داخل الطرح ولا لأ. السر في "التوزيع الثابت": لازم نفس المستخدم ياخد نفس القرار كل مرة، عشان ميشوفش الميزة تظهر وتختفي مع كل refresh.

  1. عرّف الـ flags مع نسبة الطرح ومفتاح الإيقاف (kill).
  2. حوّل معرّف المستخدم لرقم ثابت بين 0 و100 باستخدام hash.
  3. قارن الرقم بنسبة الطرح: أقل منها يعني "داخل".
  4. احترم مفتاح الـ kill قبل أي حاجة تانية.
JavaScript
// flags.js — صفر اعتماديات خارجية
const crypto = require('crypto');

// المصدر: ابدأ بكائن بسيط، وبعدين انقله لملف JSON أو Redis
const flags = {
  new_checkout: { enabled: true, rollout: 5, kill: false },
};

// توزيع ثابت: نفس (flag + user) بيدّي دايمًا نفس الرقم 0..100
function bucket(flagKey, userId) {
  const h = crypto.createHash('sha256')
                  .update(`${flagKey}:${userId}`)
                  .digest();
  return (h.readUInt32BE(0) / 0xffffffff) * 100;
}

function isEnabled(flagKey, userId) {
  const f = flags[flagKey];
  if (!f || f.kill || !f.enabled) return false; // مفتاح الطوارئ أولًا
  if (f.rollout >= 100) return true;
  return bucket(flagKey, userId) < f.rollout;    // داخل الطرح؟
}

module.exports = { flags, isEnabled };

ليه الـ hash مش Math.random()؟ لأن random بيدّي قرار مختلف كل نداء، فالمستخدم هيتنطط بين الجديد والقديم. الـ hash بيثبّت القرار على مستوى المستخدم، وده اللي بيحصل فعلاً في أدوات زي LaunchDarkly و OpenFeature.

ربطه بالطلب في Express

دلوقتي كل طلب بيتفرّع لمسارين حسب قرار المفتاح: الكود الجديد لنسبة صغيرة، والقديم للباقي — من نفس النشر.

مخطط مبسط يوضح تفرع الطلب عند فحص الـ Feature Flag إلى مسار الكود الجديد باللون الأخضر أو مسار الكود القديم باللون الرمادي
JavaScript
const express = require('express');
const { isEnabled } = require('./flags');
const app = express();

app.get('/checkout', (req, res) => {
  const userId = req.user?.id ?? req.ip; // ثابت لكل مستخدم
  if (isEnabled('new_checkout', userId)) {
    return res.json({ version: 'new', total: newCheckout(req) });
  }
  return res.json({ version: 'old', total: oldCheckout(req) });
});

app.listen(3000);

مفتاح الإيقاف (Kill Switch) من غير redeploy

عشان تقفل الميزة فورًا، اقرا الـ flags من ملف JSON بتراقبه، أو من Redis. كده تغيّر kill: true في المصدر، والخدمة تلتقطه في أقل من ثانية بدون إعادة نشر:

JavaScript
const fs = require('fs');
// أعد تحميل الإعدادات كل ما الملف يتغيّر
fs.watch('./flags.json', () => {
  try {
    const next = JSON.parse(fs.readFileSync('./flags.json', 'utf8'));
    Object.assign(flags, next); // تحديث حي بدون restart
  } catch (_) { /* تجاهل ملف نص نصّه */ }
});

سيناريو واقعي بالأرقام

افترض متجر بـ 50 ألف طلب/يوم، وعايز تطلّع صفحة checkout جديدة. بدل الإطلاق الكامل، تبدأ بـ rollout: 5 (حوالي 2500 طلب فقط يشوفوا الجديد). تراقب نسبة أخطاء 5xx وزمن الاستجابة على المجموعتين. لو الأخطاء فضلت تحت 2%، ترفع النسبة على 8 خطوات: 5 ← 10 ← 25 ← 50 ← 75 ← 100 خلال يوم أو اتنين. لو قفزت الأخطاء فوق الحد، kill: true بيرجّع الـ 50 ألف كلهم للمسار القديم في أقل من ثانية — من غير بناء ولا نشر.

الـ trade-offs وما يجب الانتباه له

كل flag بيضيف مسارين للكود، يعني بيضاعف مساحة الاختبار. لو عندك 10 flags مفتوحة في نفس الوقت، نظريًا عندك 1024 تركيبة سلوك محتملة. الـ trade-off هنا: بتكسب أمان الطرح التدريجي، بتخسر بساطة الكود وسهولة الاختبار. القاعدة: الـ flag ده مؤقت. بعد ما توصل 100% وتستقر أسبوع، امسح المفتاح والمسار القديم. الـ flags المنسية (flag debt) بتتحول لألغام في الكود.

الافتراض اللي المقال مبني عليه: عندك خدمة واحدة أو خدمات قليلة، والـ flags بتتقيّم داخل العملية (in-process). لو عندك عشرات الخدمات محتاجة تشوف نفس المفتاح لحظيًا، هتحتاج مصدر مركزي (Redis أو أداة تدعم معيار OpenFeature) بدل ملف محلي.

متى لا تستخدم هذه الطريقة

لو مشروعك صغير، مستخدمينه قليلين، والنشر عندك رخيص وسريع (ثواني)، الـ Feature Flags بتضيف تعقيد من غير عائد حقيقي — الـ rollback العادي كفاية. كمان متستخدمش flag لإخفاء إعدادات حساسة أو صلاحيات أمان؛ دي مسؤولية طبقة الأذونات مش المفاتيح. الـ flags للطرح التدريجي والتجارب، مش للتحكم في الوصول.

التحقق من أنه يعمل

اختبر التوزيع الثابت بنفسك: نادِ isEnabled('new_checkout', 'user-123') ألف مرة لنفس المستخدم — لازم ترجّع نفس النتيجة كل مرة. بعدين جرّب 10 آلاف مستخدم مختلف واحسب كام واحد رجع true: المفروض قريب من 5% (بين 4.7% و5.3% تقريبًا). لو النسبة بعيدة، غالبًا الـ hash مش متوزّع صح أو بتستخدم مصدر متغيّر للـ userId.

الخطوة التالية

افتح أقرب endpoint فيه ميزة قدامك، لفّها بـ isEnabled وابدأها بـ rollout: 1. راقب أخطاء الـ 5xx لمدة ساعة على المسار الجديد. لو فضلت نضيفة، اطلع لـ 10%. لو لأ، جرّب مفتاح الـ kill وشوفه بيرجّع الكل للمسار القديم في كام مللي ثانية بالظبط.

المصادر

  • Martin Fowler — Feature Toggles (Pete Hodgson): martinfowler.com/articles/feature-toggles.html — تصنيف أنواع المفاتيح ومشكلة flag debt.
  • OpenFeature (معيار مفتوح للـ feature flags): openfeature.dev — مصدر مركزي وموحّد للمفاتيح عبر الخدمات.
  • Node.js Crypto (دالة الـ hash المستخدمة): nodejs.org/api/crypto.html.
  • LaunchDarkly — Percentage rollouts & bucketing: docs.launchdarkly.com/home/flags/rollouts — منطق التوزيع الثابت بالـ hash.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة