عندما يكون النشر مؤلماً: لماذا يعشق المطورون أعلام الميزات
\n\nتخيل: لقد أمضيت أسبوعاً في تطوير ميزة جديدة. غطيتها بالاختبارات، وأجريت مراجعة الكود، وضغطت زر النشر. وفجأة - انفجار! - كل شيء ينهار في الإنتاج. المستخدمون غاضبون، والمدير متوتر، وأنت تسترجع الإصدار بشكل محموم. هل يبدو الأمر مألوفاً؟
\n\nوالآن تخيل سيناريو آخر: تقوم بتفعيل الميزة الجديدة فقط لـ 5% من المستخدمين. تراقب المقاييس. ترى أن كل شيء على ما يرام. تزيد النسبة تدريجياً إلى 100%. لا توتر، ولا تراجعات، فقط ثقة هادئة.
\n\nهذا هو سحر أعلام الميزات (وتسمى أيضاً مفاتيح الميزات، أعلام الميزات، أعلام الوظائف). في مشاريع TypeScript، تكون هذه التقنية ممتازة بشكل خاص بفضل الكتابة الثابتة والتحكم الصارم في الأنواع. اليوم سنتعرف على كيفية دمج أعلام الميزات في مشروعك، لتنام بهدوء، ويتم تفعيل الميزات دون ألم.
\n\nما هي أعلام الميزات بعبارات بسيطة
\n\nعلم الميزة هو مفتاح تبديل شرطي في الكود، يسمح لك بتفعيل أو تعطيل وظيفة معينة دون تغيير الكود أو إعادة النشر. يبدو الأمر كالتالي تقريباً:
\n\nif (featureFlags.isEnabled('new-checkout')) {\n // كود جديد\n} else {\n // كود قديم\n}\n\n\nكل شيء عبقري بسيط، أليس كذلك؟ لكن وراء هذه البساطة تكمن أداة قوية تستخدمها Google وNetflix وFacebook وغيرها من الشركات العملاقة. إنهم لا ينشرون كل تفصيلة صغيرة - بل يديرون سلوك التطبيق من خلال لوحة تحكم مركزية.
\n\nمتى تكون أعلام الميزات ضرورية حقاً
\n\n- \n
- الإصدار التجريبي (Canary release) - طرح ميزة جديدة لجزء صغير من المستخدمين \n
- الإطلاق المخفي (Dark launch) - اختبار مخفي حيث توجد الميزة لكن المستخدمين لا يرونها \n
- اختبار A/B - مقارنة نسختين من الوظيفة \n
- مفتاح الإيقاف (Kill switch) - تعطيل طارئ لكود إشكالي دون نشر \n
- اختبار الإذن (Permit testing) - اختبار على المستخدمين الداخليين أو حسب الأدوار \n
في مشاريع TypeScript، تكتسب أعلام الميزات سحراً خاصاً: يتحقق المترجم من أنك تستخدم الأعلام بشكل صحيح، ولا تخلط بين الأنواع، ولا تنسى معالجة جميع الحالات.
\n\nهندسة نظام أعلام الميزات
\n\nقبل كتابة الكود، دعنا نفهم مما يتكون النظام الجيد لإدارة الوظائف.
\n\nالمكونات الأساسية
\n\n- \n
- مخزن الأعلام - حيث يتم تخزين القيم (JSON، قاعدة بيانات، خدمة خارجية) \n
- الموفر (Provider) - وحدة تقوم بتحميل وتخزين الأعلام مؤقتاً \n
- الجزء العميل - الكود الذي يتحقق من حالة الأعلام \n
- لوحة التحكم - واجهة لتغيير الأعلام في الوقت الفعلي \n
أبسط تطبيق في TypeScript
\n\nلنبدأ بأبسط مثال. سننشئ نوعاً للأعلام وخدمة بسيطة:
\n\n// types/feature-flags.ts\nexport type FeatureFlag = \n | 'new-checkout'\n | 'dark-mode'\n | 'recommendations-v2'\n | 'beta-chat';\n\nexport interface FeatureFlagConfig {\n flag: FeatureFlag;\n enabled: boolean;\n rolloutPercentage?: number;\n userIds?: string[];\n}\n\n\nالآن سنكتب الخدمة نفسها:
\n\n// services/feature-flag.service.ts\nexport class FeatureFlagService {\n private flags: Map<FeatureFlag, FeatureFlagConfig> = new Map();\n\n constructor(initialFlags: FeatureFlagConfig[]) {\n initialFlags.forEach(flag => {\n this.flags.set(flag.flag, flag);\n });\n }\n\n isEnabled(flag: FeatureFlag, userId?: string): boolean {\n const config = this.flags.get(flag);\n if (!config) return false;\n\n if (!config.enabled) return false;\n\n // التحقق من مستخدمين محددين\n if (config.userIds && userId) {\n return config.userIds.includes(userId);\n }\n\n // التوزيع حسب النسبة المئوية