Directus Feature Flag
Directus Headless CMS REST GraphQL API Feature Flag Toggle Canary Release A/B Testing Kill Switch Progressive Rollout Role-based Access Webhooks Flows
| Feature Flag Type | ใช้เมื่อ | ตัวอย่าง |
|---|---|---|
| Release Flag | ปล่อยฟีเจอร์ใหม่ | New checkout flow |
| Experiment Flag | A/B Testing | Button color test |
| Ops Flag | ควบคุมระบบ | Maintenance mode |
| Permission Flag | ควบคุมสิทธิ์ | Premium features |
| Kill Switch | ปิดฟีเจอร์ฉุกเฉิน | Disable payments |
Directus Setup และ Feature Flag Schema
# === Directus Feature Flag Setup ===
# Docker Compose
# version: '3.8'
# services:
# directus:
# image: directus/directus:latest
# ports:
# - "8055:8055"
# environment:
# KEY: "random-key-here"
# SECRET: "random-secret-here"
# DB_CLIENT: "pg"
# DB_HOST: "db"
# DB_PORT: "5432"
# DB_DATABASE: "directus"
# DB_USER: "directus"
# DB_PASSWORD: "directus"
# ADMIN_EMAIL: "admin@example.com"
# ADMIN_PASSWORD: "admin123"
# depends_on:
# - db
#
# db:
# image: postgres:15
# environment:
# POSTGRES_DB: directus
# POSTGRES_USER: directus
# POSTGRES_PASSWORD: directus
# volumes:
# - pgdata:/var/lib/postgresql/data
#
# volumes:
# pgdata:
# Feature Flag Schema (SQL)
# CREATE TABLE feature_flags (
# id SERIAL PRIMARY KEY,
# name VARCHAR(100) UNIQUE NOT NULL,
# description TEXT,
# enabled BOOLEAN DEFAULT false,
# rollout_percentage INT DEFAULT 0,
# target_users JSONB DEFAULT '[]',
# target_groups JSONB DEFAULT '[]',
# metadata JSONB DEFAULT '{}',
# created_at TIMESTAMP DEFAULT NOW(),
# updated_at TIMESTAMP DEFAULT NOW()
# );
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class FeatureFlag:
name: str
description: str
enabled: bool
rollout_pct: int
target_groups: List[str]
flag_type: str
created: str
flags = [
FeatureFlag("new_checkout", "New checkout flow with Stripe", True, 25,
["beta_users", "internal"], "Release", "2024-03-01"),
FeatureFlag("dark_mode", "Dark mode UI", True, 100,
["all"], "Release", "2024-02-15"),
FeatureFlag("ai_recommendations", "AI product recommendations", True, 10,
["premium"], "Experiment", "2024-03-10"),
FeatureFlag("maintenance_mode", "Enable maintenance page", False, 0,
["all"], "Ops", "2024-01-01"),
FeatureFlag("payment_gateway_v2", "New payment gateway", True, 50,
["beta_users"], "Release", "2024-03-05"),
FeatureFlag("kill_notifications", "Disable push notifications", False, 0,
["all"], "Kill Switch", "2024-02-20"),
]
print("=== Feature Flags Dashboard ===")
for f in flags:
status = "ON" if f.enabled else "OFF"
groups = ", ".join(f.target_groups)
print(f" [{status}] {f.name} ({f.flag_type})")
print(f" {f.description}")
print(f" Rollout: {f.rollout_pct}% | Groups: {groups}")
API และ SDK Integration
# === Feature Flag API ===
# Directus REST API
# GET /items/feature_flags
# GET /items/feature_flags?filter[name][_eq]=new_checkout
# PATCH /items/feature_flags/1 {"enabled": true, "rollout_percentage": 50}
# Python SDK
# from directus_sdk import DirectusClient
#
# client = DirectusClient("http://localhost:8055", token="YOUR_TOKEN")
#
# # Get all flags
# flags = client.get_items("feature_flags")
#
# # Check flag for user
# def is_enabled(flag_name, user_id=None, user_group=None):
# flag = client.get_items("feature_flags",
# params={"filter[name][_eq]": flag_name})
# if not flag:
# return False
# flag = flag[0]
# if not flag["enabled"]:
# return False
# # Check target groups
# if user_group and flag["target_groups"]:
# if user_group not in flag["target_groups"] and "all" not in flag["target_groups"]:
# return False
# # Check rollout percentage
# if flag["rollout_percentage"] < 100:
# import hashlib
# hash_val = int(hashlib.md5(f"{flag_name}:{user_id}".encode()).hexdigest(), 16)
# if (hash_val % 100) >= flag["rollout_percentage"]:
# return False
# return True
# JavaScript Client
# import { createDirectus, rest, readItems } from '@directus/sdk';
#
# const client = createDirectus('http://localhost:8055').with(rest());
#
# async function getFlag(name) {
# const flags = await client.request(
# readItems('feature_flags', {
# filter: { name: { _eq: name } },
# })
# );
# return flags[0] || null;
# }
#
# // Usage in React
# function FeatureGate({ flag, children, fallback }) {
# const [enabled, setEnabled] = useState(false);
# useEffect(() => {
# getFlag(flag).then(f => setEnabled(f?.enabled));
# }, [flag]);
# return enabled ? children : (fallback || null);
# }
import hashlib
def check_rollout(flag_name: str, user_id: str, rollout_pct: int) -> bool:
hash_val = int(hashlib.md5(f"{flag_name}:{user_id}".encode()).hexdigest(), 16)
return (hash_val % 100) < rollout_pct
print("\nRollout Simulation (new_checkout at 25%):")
users = [f"user_{i}" for i in range(20)]
enabled_count = sum(1 for u in users if check_rollout("new_checkout", u, 25))
print(f" {enabled_count}/{len(users)} users enabled ({enabled_count/len(users)*100:.0f}%)")
Flows Automation และ Best Practices
# === Directus Flows for Feature Flags ===
# Flow: Auto-disable flag after date
# Trigger: Schedule (daily)
# Operations:
# 1. Read Items: feature_flags where end_date < today
# 2. Update Items: set enabled = false
# 3. Send Webhook: notify Slack
# Flow: Gradual rollout
# Trigger: Manual
# Operations:
# 1. Read current rollout_percentage
# 2. Increase by 10%
# 3. Wait 1 hour
# 4. Check error rate from monitoring
# 5. If OK: increase again, else rollback to 0%
best_practices = {
"Naming": [
"ใช้ snake_case: new_checkout, dark_mode",
"Prefix ตาม type: release_, exp_, ops_",
"ชื่อบอก context: payment_gateway_v2",
],
"Lifecycle": [
"สร้าง Flag ก่อน Deploy code",
"เปิด Flag ทีละ % (5, 25, 50, 100)",
"Monitor Metrics ทุกขั้น",
"ลบ Flag หลังใช้เสร็จ (Technical Debt)",
],
"Testing": [
"ทดสอบทั้ง ON และ OFF",
"ทดสอบ Default (Flag ไม่มี)",
"ทดสอบ Rollout % ถูกต้อง",
"ทดสอบ Target Groups ถูกกลุ่ม",
],
"Operations": [
"Kill Switch สำหรับทุกฟีเจอร์สำคัญ",
"Audit Log ทุกการเปลี่ยนแปลง Flag",
"Alert เมื่อ Flag เปลี่ยนใน Production",
"Review Flags ทุกเดือน ลบที่ไม่ใช้",
],
}
print("Feature Flag Best Practices:")
for category, tips in best_practices.items():
print(f"\n [{category}]")
for tip in tips:
print(f" - {tip}")
# Comparison with dedicated services
comparison = {
"Directus (DIY)": {"cost": "Free (self-hosted)", "features": "Basic", "setup": "ง่าย"},
"LaunchDarkly": {"cost": "$10/mo+", "features": "Enterprise", "setup": "SDK ครบ"},
"Unleash": {"cost": "Free (OSS)", "features": "Advanced", "setup": "ปานกลาง"},
"Flagsmith": {"cost": "Free (OSS)", "features": "Advanced", "setup": "ปานกลาง"},
}
print(f"\n\nFeature Flag Services:")
for service, info in comparison.items():
print(f" [{service}]")
for k, v in info.items():
print(f" {k}: {v}")
เคล็ดลับ
- Kill Switch: ทุกฟีเจอร์สำคัญต้องมี Kill Switch ปิดได้ทันที
- Cleanup: ลบ Flag ที่ใช้เสร็จแล้ว ไม่อย่างนั้นเป็น Technical Debt
- Gradual: ปล่อยฟีเจอร์ทีละ % ดู Metrics ก่อนเพิ่ม
- Directus: ใช้ Directus เป็น Feature Flag Service เมื่อไม่ต้องการฟีเจอร์ซับซ้อน
- Cache: Cache Flags ฝั่ง Client ลด API Calls ตั้ง TTL 1-5 นาที
การนำความรู้ไปประยุกต์ใช้งานจริง
แหล่งเรียนรู้ที่แนะนำ ได้แก่ Official Documentation ที่อัพเดทล่าสุดเสมอ Online Course จาก Coursera Udemy edX ช่อง YouTube คุณภาพทั้งไทยและอังกฤษ และ Community อย่าง Discord Reddit Stack Overflow ที่ช่วยแลกเปลี่ยนประสบการณ์กับนักพัฒนาทั่วโลก
เปรียบเทียบข้อดีและข้อเสีย
จากตารางเปรียบเทียบจะเห็นว่าข้อดีมีมากกว่าข้อเสียอย่างชัดเจน โดยเฉพาะในแง่ของประสิทธิภาพและความสามารถในการ Scale สำหรับข้อเสียส่วนใหญ่สามารถแก้ไขได้ด้วยการเรียนรู้อย่างเป็นระบบและวางแผนทรัพยากรให้เหมาะสม
Directus คืออะไร
Open Source Headless CMS REST GraphQL API Database Admin UI RBAC Webhooks Flows PostgreSQL MySQL Self-hosted Cloud
Feature Flag คืออะไร
เปิด/ปิดฟีเจอร์ไม่ต้อง Deploy Canary Release A/B Testing Kill Switch ลดความเสี่ยง Config แทน Code
Directus ใช้เป็น Feature Flag Service ได้อย่างไร
สร้าง Collection feature_flags API Endpoint Client ดึง Flags Flows Automation Roles สิทธิ์ Webhooks แจ้งเตือน
Feature Flag กับ Feature Toggle ต่างกันไหม
คำเดียวกัน Toggle Boolean เปิด/ปิด Flag Conditions ซับซ้อนกว่า ทางปฏิบัติใช้แทนกัน LaunchDarkly Unleash Flagsmith
สรุป
Directus Headless CMS Feature Flag Toggle REST GraphQL API Canary Release A/B Testing Kill Switch Progressive Rollout Flows Automation Best Practices Lifecycle Cleanup Cache
