اعمل Feature Flags داخلي بـ Unleash وNode.js
مستوى القارئ: متوسط
هتطلع من المقال ده بنظام Feature Flags بسيط يخليك تطلق ميزة على 5% من المستخدمين، تراقبها، ثم تزودها بدون deploy جديد.
المشكلة باختصار
الطريقة الشائعة إنك تعمل deploy وتعتبر إن الميزة اتنشرت لكل الناس. الطريقة دي بتفشل لما يظهر bug بعد أول ساعة. بدل ما توقف السيرفر أو ترجع commit، الأفضل تخلي الكود موجود، لكن تشغيله متحكم فيه من لوحة خارجية.
افترض إن عندك API عليها 50 ألف مستخدم يوميًا. لو نشرت صفحة checkout جديدة لكل المستخدمين مرة واحدة، فأي bug في الدفع ممكن يضرب 100% من الطلبات. مع Feature Flag تبدأ بـ 5% فقط. لو عندك 10 آلاف زيارة للـ checkout في اليوم، فأنت بتعرّض حوالي 500 زيارة في البداية بدل 10 آلاف.
الفكرة بمثال بسيط
ركز في المثال ده. عندك زر جديد اسمه new_checkout. بدل ما تكتب شرطًا ثابتًا داخل الكود، التطبيق يسأل Unleash: هل المستخدم الحالي يشوف الزر الجديد؟ لو الإجابة نعم تعرض المسار الجديد. لو لا، تترك المسار القديم يعمل.
المفهوم العلمي وراء ذلك بسيط: Feature Flag هو قرار runtime. الكود موجود في الإنتاج، لكن تفعيله لا يحتاج بناء نسخة جديدة. Unleash يخزن القواعد والاستراتيجيات، وSDK في Node.js يقيّم القرار محليًا بناءً على context مثل userId أو sessionId. هذا مهم لأن الـ stickiness يحافظ على نفس القرار لنفس المستخدم، بدل ما يشوف الميزة مرة وتختفي مرة.
الخطوات العملية
- شغّل Unleash محليًا للتجربة.
- أنشئ feature flag باسم
new_checkout. - اربط تطبيق Node.js بالـ API token.
- ابدأ rollout بنسبة 5%، ثم ارفعها إلى 25% وبعدها 100%.
- راقب الأخطاء والزمن قبل كل زيادة.
# docker-compose.yml
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: unleash
POSTGRES_USER: unleash_user
POSTGRES_PASSWORD: unleash_pass
volumes:
- pgdata:/var/lib/postgresql/data
unleash:
image: unleashorg/unleash-server:latest
ports:
- "4242:4242"
environment:
DATABASE_HOST: postgres
DATABASE_NAME: unleash
DATABASE_USERNAME: unleash_user
DATABASE_PASSWORD: unleash_pass
DATABASE_SSL: "false"
INIT_FRONTEND_API_TOKENS: default:development.unleash-insecure-frontend-api-token
INIT_CLIENT_API_TOKENS: default:development.unleash-insecure-api-token
depends_on:
- postgres
volumes:
pgdata:
docker compose up -d
# افتح http://localhost:4242
# أنشئ flag باسم new_checkout ثم فعّله في development
npm init -y
npm i express unleash-client
// server.js
const express = require('express');
const { initialize } = require('unleash-client');
const unleash = initialize({
url: 'http://localhost:4242/api/',
appName: 'checkout-api',
customHeaders: {
Authorization: 'default:development.unleash-insecure-api-token'
}
});
const app = express();
app.get('/checkout', (req, res) => {
const userId = req.query.userId || 'anonymous';
const enabled = unleash.isEnabled('new_checkout', { userId });
if (enabled) {
return res.json({ flow: 'new_checkout', discountBox: true });
}
return res.json({ flow: 'classic_checkout', discountBox: false });
});
app.listen(3000, () => console.log('API on http://localhost:3000'));
node server.js
curl "http://localhost:3000/checkout?userId=42"
النتيجة المتوقعة: نفس userId يفضل على نفس القرار طالما إعدادات الـ rollout ثابتة. ده بالظبط اللي يمنع تجربة متذبذبة للمستخدم.
القياس قبل وبعد
بدون Feature Flag، أول deploy يعرض 100% من المستخدمين للكود الجديد. مع rollout بنسبة 5%، أنت تقلل مساحة الخطر 20 مرة في أول مرحلة. الرقم ليس ضمانًا ضد الأخطاء، لكنه يحدد حجم الضرر لو ظهر bug.
راقب 3 أرقام قبل رفع النسبة: معدل أخطاء 5xx، زمن P95 للـ endpoint، وعدد عمليات checkout الناجحة. مثال عملي: لو P95 زاد من 220ms إلى 480ms عند 5%، لا ترفع النسبة. أصلح السبب أو اقفل الفلاج فورًا.
الـ trade-off هنا
المكسب: تفصل deploy عن release، وتقدر تعمل kill switch في ثواني. التكلفة: هتضيف اعتمادًا جديدًا على Unleash، وهتحتاج تنظيف الفلاجز القديمة. لو تركت 30 flag منسية في الكود، هتدفع تكلفة فهم وصيانة في كل تعديل.
أفضل طريقة إنك تكتب owner وتاريخ انتهاء لكل flag. Flag خاص بإطلاق ميزة يجب أن يُحذف بعد الاستقرار. Flag خاص بإطفاء خدمة خارجية ممكن يفضل لأنه operational kill switch.
متى لا تستخدم هذه الطريقة
لا تستخدم Feature Flags لكل if statement صغيرة. لو التغيير داخلي ولا يؤثر على المستخدم، الـ deploy العادي كفاية. لا تستخدمها أيضًا لإخفاء migration غير متوافقة مع قاعدة البيانات. لو schema الجديد يكسر القديم، أنت محتاج خطة migration، مش مجرد flag.
مصادر اعتمدت عليها
- Unleash Feature Toggles
- Unleash Gradual Rollout
- Unleash Node.js SDK
- Docker Compose documentation
- OpenFeature introduction
الخطوة التالية
الخطوة التالية: اختار ميزة واحدة عندك فيها خطر واضح، واعمل لها flag باسم محدد، وابدأ rollout بنسبة 5% فقط لمدة يوم قبل أي زيادة.