مقدمة: لماذا تفسد الميزات الإصدارات؟
\nكل مطور واجه مرة على الأقل موقفًا تكون فيه وظيفة جديدة رائعة جاهزة، لكن لا يمكن إصدارها. إما أنها لم تجتاز اختبار ضمان الجودة الكامل، أو لم يقم فريق التسويق بعد بإعداد اللافتات، أو ببساطة الخوف من الضغط على زر "نشر" مساء الجمعة. في هذه اللحظة، نبدأ عادةً بكتابة if، أو تبديل BuildConfig، أو الأسوأ من ذلك، التعليق على الكود.
هل هذا مألوف؟ مرحبًا بك في عالم أعلام الميزات (أو مفاتيح الميزات). إنها ليست مجرد "شيء معماري آخر". إنها طوق النجاة الخاص بك الذي يسمح لك بنشر الكود في بيئة الإنتاج، ولكن تفعيله فقط للمختارين. في هذه المقالة، سنشرح كيفية تنفيذ أعلام الميزات في مشاريع Kotlin - من الحلول البسيطة إلى الأنظمة المتقدمة. ونعم، سيكون هناك الكثير من الكود.
\n\nما هو علم الميزة ولماذا تحتاجه؟
\nعلم الميزة (المعروف أيضًا بمفتاح الميزة) هو مفتاح تبديل برمجي يسمح لك بتشغيل أو إيقاف تشغيل وظيفة ما دون تغيير الكود أو إعادة النشر. تخيل أنه يمكنك تحميل خوارزمية توصيات جديدة إلى بيئة الإنتاج، ولكن جعلها نشطة فقط لـ 5% من المستخدمين. أو تشغيلها فقط لموظفي الشركة للاختبار الداخلي. رائع، أليس كذلك؟
\nإليك بعض السيناريوهات التي لا غنى فيها عن أعلام الميزات:
\n- \n
- الإصدارات التجريبية (Canary releases): نشر ميزة جديدة على 1% من الخوادم، مراقبة الأخطاء وزيادة النسبة تدريجيًا. \n
- اختبار A/B: التحقق من أي إصدار من واجهة المستخدم يحقق تحويلًا أفضل، دون نسخ المشروع بأكمله. \n
- مفتاح الإيقاف (Kill switch): إذا بدأت الميزة الجديدة في استهلاك الذاكرة أو تسببت في خطأ، يمكنك ببساطة إيقاف تشغيلها بحركة واحدة (أو طلب API). \n
- فصل الإصدار عن النشر: الكود منشور، لكن الميزة "نائمة" حتى تاريخ X. \n
في Kotlin، الذي يُستخدم بنشاط لكل من الواجهة الخلفية (Ktor, Spring Boot) وتطبيقات الهاتف المحمول (Android)، تصبح أعلام الميزات ذات صلة خاصة بسبب الكتابة الصارمة ومتطلبات نقاء الكود.
\n\nالهندسة المعمارية: كيف لا تحول الأعلام إلى معكرونة (سباغيتي)
\nأكبر خطأ يرتكبه المبتدئون هو نثر if (flag.isEnabled()) { ... } else { ... } في جميع أنحاء المشروع. يؤدي هذا إلى عدم القدرة بعد شهر على فهم أي فروع الكود حية وأيها ميتة. نحتاج إلى إدارة الميزات، وليس مفاتيح تبديل عشوائية.
تعتمد الهندسة المعمارية الجيدة على مبدأ عكس التبعيات. يجب ألا يعرف كود عملك من أين تأتي قيمة العلم (من ملف، قاعدة بيانات، أو خدمة خارجية). إنه يسأل ببساطة: "مرحبًا، FeatureFlagService، هل الميزة new_checkout مفعلة؟"
لننظر في نهجين: بسيط (للمشاريع الصغيرة) ومتقدم (للخدمات المصغرة و Kubernetes).
\n\n1. النهج البسيط: التكوين و Enum
\nإذا كنت بحاجة إلى البدء بسرعة ولا تقوم ببناء نظام بملايين الطلبات في الثانية، يمكنك الاكتفاء بـ enum class بسيط وملف تكوين (مثل application.conf لـ Ktor أو application.yml لـ Spring).
لننشئ enum لجميع الميزات:
\nenum class Feature(val key: String) {\n NEW_PAYMENT_FLOW("feature.new-payment-flow"),\n DARK_MODE("feature.dark-mode"),\n SMART_SEARCH("feature.smart-search")\n}\nالآن الخدمة التي تقرأ التكوين:
\nclass FeatureFlagService(private val config: Map<String, Boolean>) {\n\n fun isEnabled(feature: Feature): Boolean {\n return config.getOrDefault(feature.key, false)\n }\n\n // لاختبارات A/B يمكن إضافة التوزيع العشوائي\n fun isEnabledForUser(feature: Feature, userId: String): Boolean {\n if (!isEnabled(feature)) return false\n // تجزئة بسيطة للتقسيم إلى مجموعات\n val group = userId.hashCode() % 100\n return group < 50 // 50% من المستخدمين