معظم فرق Go SaaS تنشر الميزات الجديدة بإحدى طريقتين: تفعيل كل شيء دفعة واحدة وانتظار المشاكل، أو الحفاظ على فروع طويلة الأمد تتباعد حتى تصبح مصدر ألم حقيقي عند الدمج. أعلام الميزات تحل كلا المشكلتين بفصل نشر الكود عن تفعيل الميزة.
معظم فرق Go SaaS تنشر الميزات الجديدة بإحدى طريقتين: تفعيل كل شيء دفعة واحدة وانتظار المشاكل، أو الحفاظ على فروع طويلة الأمد تتباعد حتى تصبح مصدر ألم حقيقي عند الدمج. أعلام الميزات تحل كلا المشكلتين بفصل نشر الكود عن تفعيل الميزة. الميزة تدخل إلى الكود الإنتاجي، لكن سلوك الإنتاج يبقى كما هو حتى يُفعَّل العلم صراحةً.
هكذا تتعامل منصة RTYLR مع عمليات النشر التدريجي عبر حسابات المستأجرين في لبنان ودول الخليج، دون الحاجة لإعادة تشغيل الخدمات أو تنسيق عمليات النشر.
لماذا متغيرات البيئة غير كافية
الأعلام المبنية على متغيرات البيئة تعمل مع العلم الأول. تنهار عند العلم الخامس. تحتاج إلى إعادة نشر لتغييرها، ولا يمكن تحديدها لمستأجر بعينه، ولا تترك أثراً للمراجعة، وتسبب تبايناً بين بيئات التطوير والإنتاج.
الحل الصحيح لأي SaaS يضم أكثر من مستأجرين وعدة أعلام هو نظام أعلام مدعوم بقاعدة البيانات. الأعلام تعيش في PostgreSQL. خدمات Go تقرأها عند معالجة كل طلب. تغيير علم يستغرق ثوانٍ ويترك سجلاً.
نموذج البيانات
المخطط يحتاج للإجابة عن سؤالين لكل علم: هل هذا العلم مفعَّل عالمياً؟ وهل هو مفعَّل لهذا المستأجر تحديداً؟
CREATE TABLE feature_flags (
name TEXT PRIMARY KEY,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE feature_flag_overrides (
flag_name TEXT NOT NULL REFERENCES feature_flags(name) ON DELETE CASCADE,
tenant_id UUID NOT NULL,
enabled BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (flag_name, tenant_id)
);
جدول feature_flags يحتفظ بالحالة الافتراضية لكل علم. جدول feature_flag_overrides يحتفظ بالتجاوزات الخاصة بكل مستأجر. المنطق بسيط: إذا وُجد تجاوز للمستأجر، استخدمه. وإلا، استخدم الإعداد العالمي.
محلل الأعلام في Go
المحلل يعيش في هيكل واحد يُنشأ عند بدء التشغيل ويُحقن في معالجات الطلبات.
type FlagResolver struct {
db *sql.DB
cache sync.Map
ttl time.Duration
}
func (r *FlagResolver) IsEnabled(ctx context.Context, name string, tenantID uuid.UUID) (bool, error) {
cacheKey := name + ":" + tenantID.String()
if v, ok := r.cache.Load(cacheKey); ok {
cf := v.(cachedFlag)
if time.Now().Before(cf.expiresAt) {
return cf.enabled, nil
}
}
enabled, err := r.resolve(ctx, name, tenantID)
if err != nil {
return false, err
}
r.cache.Store(cacheKey, cachedFlag{enabled: enabled, expiresAt: time.Now().Add(r.ttl)})
return enabled, nil
}
مدة صلاحية التخزين المؤقت 30 ثانية تعني أن التغييرات على الأعلام تنتشر خلال نصف دقيقة دون الضغط على قاعدة البيانات في كل طلب.
استخدام الأعلام في المعالجات
يجب التحقق من الأعلام في أقرب نقطة ممكنة من مسار الطلب. معالج يتحقق من علم في منتصف التنفيذ ويُعيد نتائج جزئية أصعب في التفكير منه مقارنةً بمعالج يضع الميزة بالكامل خلف سياج في البداية.
نشر تدريجي عبر مستأجري RTYLR
في سياق RTYLR، قد تكون الميزة الجديدة جاهزة لكنها اختُبرت فقط مع إعدادات مطاعم بيروت. المخاطر مختلفة لمطعم في الرياض له هيكل قائمة وإعداد طابعات وسير عمل طاقم مختلفة.
جدول التجاوزات يجعل هذا الأمر بسيطاً. الفريق الهندسي يفعِّل العلم لمستأجر واحد موثوق أولاً. بعد أسبوع من التشغيل النظيف، تحصل مجموعة من عشرة مستأجرين على التجاوز. بعد شهر، يصبح الإعداد العالمي مفعَّلاً والتجاوزات تُنظَّف والكود القديم يُحذف.
هذا النمط لا يحتاج أي خدمة خارجية. يعمل داخل نفس قاعدة بيانات PostgreSQL التي يستخدمها باقي النظام.
دورة حياة الأعلام ومنع تراكم الديون
أكثر نمط فشل شائع مع أعلام الميزات ليس إنشاءها، بل عدم تنظيفها أبداً. بعد ستة أشهر من التشغيل الإنتاجي، تتراكم خمسون علماً. عشرون منها مطرودة بالكامل لكنها لا تزال في الكود. عشرة أخرى لميزات تُركت. الباقي نشط فعلاً.
قاعدتان تمنعان هذا الأمر: الأولى، اسمِ الأعلام بطريقة تعكس دورة حياتها. checkout_v2 أفضل من new_checkout_flow. الثانية، أضف عمود scheduled_removal_date وراجع الأعلام المنتهية في كل دورة تطوير.
دروس من الإنتاج
في RTYLR، التحول إلى الأعلام المدعومة بقاعدة البيانات غيَّر طريقة الشحن عبر نشرات لبنان والخليج. ثلاثة أشياء برزت بوضوح:
أولاً، عمليات الاسترداد التي كانت تستلزم إعادة نشر باتت تستغرق أقل من دقيقة بقلب علم. الحوادث التي تسببت في توقف لعشرين دقيقة باتت تُغلق في دقيقتين.
ثانياً، أعلام الميزات أجبرت الفريق على كتابة مسارَي كود منفصلَين، مما حسَّن التغطية الاختبارية بشكل غير متوقع.
ثالثاً، مدة صلاحية التخزين المؤقت للمحلل خانة قابلة للضبط. في التشغيل العادي، 30 ثانية كافية. أثناء الحوادث النشطة، تُخفَّض إلى صفر.
هل تريد البدء؟
إذا كنت تبني خلفية Go SaaS في لبنان أو في منطقة MENA وتريد بنية نشر تتيح لك الشحن بدون خوف، يمكن لـ Voxire مساعدتك في التصميم والتنفيذ من الصفر. نبني أنظمة مصممة للعمل في الإنتاج، لا في العروض التوضيحية فقط.
https://voxire.com/get-a-quote/



