مستوى المقال: متوسط. يفترض إنك تعرف 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.
- عرّف الـ flags مع نسبة الطرح ومفتاح الإيقاف (kill).
- حوّل معرّف المستخدم لرقم ثابت بين 0 و100 باستخدام hash.
- قارن الرقم بنسبة الطرح: أقل منها يعني "داخل".
- احترم مفتاح الـ kill قبل أي حاجة تانية.
// 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 };