Feature Flags (หรือ Feature Toggles) เป็นเทคนิคที่ทรงพลังที่สุดอย่างหนึ่งใน Modern Software Delivery ช่วยให้คุณ Deploy code ขึ้น Production ได้ทุกวัน โดยไม่ต้องเปิดฟีเจอร์ให้ User ทุกคนเห็นทันที ทำให้สามารถ Release อย่างปลอดภัย ทดสอบกับ User จริง และ Rollback ได้ทันทีโดยไม่ต้อง Redeploy
บทความนี้จะสอน Feature Flags ครบทุกเรื่อง ตั้งแต่แนวคิดพื้นฐาน, Progressive Delivery, A/B Testing, Platform ยอดนิยม ไปจนถึง Best Practices สำหรับทีม DevOps
Feature Flags คืออะไร?
Feature Flags (Feature Toggles) คือเทคนิคที่ใช้ Configuration เพื่อควบคุมว่า Feature ใดจะเปิดหรือปิดใน Runtime โดยไม่ต้องแก้ Code หรือ Deploy ใหม่ เปรียบเหมือน "สวิตช์" ที่เปิด-ปิดฟีเจอร์ได้ทันที
// ตัวอย่างพื้นฐาน
if (featureFlags.isEnabled('new-checkout')) {
showNewCheckout()
} else {
showOldCheckout()
}
ประเภทของ Feature Flags
| ประเภท | วัตถุประสงค์ | อายุ | ตัวอย่าง |
|---|---|---|---|
| Release Flag | ซ่อนฟีเจอร์ที่ยังไม่เสร็จ | สั้น (days-weeks) | new-dashboard, v2-api |
| Experiment Flag | A/B Testing | ปานกลาง (weeks) | checkout-variant-b, pricing-test |
| Ops Flag | ควบคุม operational behavior | ยาว (permanent) | enable-cache, rate-limit-mode |
| Permission Flag | จำกัดสิทธิ์การเข้าถึง | ยาว (permanent) | beta-access, premium-feature |
Progressive Delivery คืออะไร?
Progressive Delivery คือแนวทางการ Release ซอฟต์แวร์แบบค่อยๆ เปิดให้ User เข้าถึง แทนที่จะ Release ทีเดียวให้ทุกคน (Big Bang Release) ประกอบด้วยเทคนิค:
- Canary Release — ปล่อยให้ User กลุ่มเล็กก่อน (เช่น 1%) แล้วค่อยขยาย
- Percentage Rollout — ค่อยๆ เพิ่มเปอร์เซ็นต์ (1% → 10% → 50% → 100%)
- Ring Deployment — Release ทีละกลุ่ม (Internal → Beta → GA)
- A/B Testing — ทดสอบ 2 versions กับ User จริง วัดผลลัพธ์
Canary Release กับ Feature Flags
// Canary: เปิดให้ 5% ของ User ก่อน
const flag = {
name: 'new-payment-flow',
enabled: true,
rolloutPercentage: 5, // 5% ของ users
targetGroups: ['internal-testers']
}
function shouldShowFeature(userId, flag) {
// 1. เช็ก target groups ก่อน
if (flag.targetGroups.includes(getUserGroup(userId))) {
return true
}
// 2. เช็ก percentage rollout
const hash = hashUserId(userId) % 100
return hash < flag.rolloutPercentage
}
A/B Testing กับ Feature Flags
// A/B Test: ทดสอบ 2 variants
const experiment = {
name: 'checkout-redesign',
variants: [
{ key: 'control', weight: 50 }, // A: แบบเดิม
{ key: 'treatment', weight: 50 } // B: แบบใหม่
]
}
function getVariant(userId, experiment) {
const hash = hashUserId(userId) % 100
let cumulative = 0
for (const variant of experiment.variants) {
cumulative += variant.weight
if (hash < cumulative) return variant.key
}
return experiment.variants[0].key
}
// ใช้งาน
const variant = getVariant(userId, experiment)
if (variant === 'treatment') {
renderNewCheckout()
trackEvent('checkout_view', { variant: 'treatment' })
} else {
renderOldCheckout()
trackEvent('checkout_view', { variant: 'control' })
}
User Targeting
// Target by attribute
const flag = {
name: 'beta-feature',
rules: [
// เปิดให้ทุก internal users
{ attribute: 'email', operator: 'endsWith', value: '@company.com', enabled: true },
// เปิดให้ users ในประเทศไทย
{ attribute: 'country', operator: 'equals', value: 'TH', enabled: true },
// เปิดให้ premium users
{ attribute: 'plan', operator: 'in', value: ['pro', 'enterprise'], enabled: true },
// เปิดให้ specific users
{ attribute: 'userId', operator: 'in', value: ['user-123', 'user-456'], enabled: true }
],
defaultValue: false // ปิดสำหรับ users ที่ไม่ตรง rules
}
Feature Flag Platforms เปรียบเทียบ
| Platform | ประเภท | ราคา | จุดเด่น |
|---|---|---|---|
| LaunchDarkly | SaaS | เริ่ม $10/mo | Enterprise-grade, SDK ครบ, Experimentation |
| Unleash | Open Source / SaaS | Free (OSS) | Self-hosted, Open Source, ยืดหยุ่น |
| Flagsmith | Open Source / SaaS | Free (OSS) | Remote Config + Flags, Edge support |
| ConfigCat | SaaS | Free tier | Simple, ราคาถูก, Config management |
| PostHog | Open Source / SaaS | Free tier | Feature Flags + Analytics + Session Replay |
| Statsig | SaaS | Free tier | Experimentation platform + Auto metrics |
Implementing Feature Flags — React
// React + LaunchDarkly
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk'
function CheckoutPage() {
const { newCheckout, showPromoBar } = useFlags()
return (
<div>
{showPromoBar && <PromoBanner />}
{newCheckout ? <NewCheckout /> : <OldCheckout />}
</div>
)
}
// React + Custom Hook (ไม่ใช้ platform)
import { createContext, useContext, useState, useEffect } from 'react'
const FlagContext = createContext({})
export function FlagProvider({ children }) {
const [flags, setFlags] = useState({})
useEffect(() => {
fetch('/api/flags')
.then(r => r.json())
.then(setFlags)
}, [])
return (
<FlagContext.Provider value={flags}>
{children}
</FlagContext.Provider>
)
}
export function useFlag(flagName) {
const flags = useContext(FlagContext)
return flags[flagName] ?? false
}
// ใช้งาน
function MyComponent() {
const showNewUI = useFlag('new-ui')
return showNewUI ? <NewUI /> : <OldUI />
}
Implementing Feature Flags — Node.js
// Node.js + Unleash
import { initialize } from 'unleash-client'
const unleash = initialize({
url: 'https://unleash.example.com/api',
appName: 'my-api',
customHeaders: { Authorization: process.env.UNLEASH_TOKEN }
})
// Express middleware
function featureFlag(flagName) {
return (req, res, next) => {
const context = {
userId: req.user?.id,
properties: {
email: req.user?.email,
country: req.headers['x-country']
}
}
req.flagEnabled = unleash.isEnabled(flagName, context)
next()
}
}
// ใช้งาน
app.get('/api/dashboard',
featureFlag('new-dashboard'),
(req, res) => {
if (req.flagEnabled) {
return res.json(getNewDashboardData())
}
return res.json(getOldDashboardData())
}
)
Implementing Feature Flags — Python
# Python + Custom implementation
import hashlib
import json
from functools import wraps
class FeatureFlagService:
def __init__(self, config_path='flags.json'):
with open(config_path) as f:
self.flags = json.load(f)
def is_enabled(self, flag_name, user_id=None, attributes=None):
flag = self.flags.get(flag_name)
if not flag or not flag.get('enabled'):
return False
# Global kill switch
if flag.get('kill_switch'):
return False
# Percentage rollout
if user_id and 'percentage' in flag:
hash_val = int(hashlib.md5(
f"{flag_name}:{user_id}".encode()
).hexdigest(), 16) % 100
return hash_val < flag['percentage']
return True
# Flask decorator
flags = FeatureFlagService()
def feature_required(flag_name):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if not flags.is_enabled(flag_name, user_id=g.user_id):
abort(404)
return f(*args, **kwargs)
return wrapper
return decorator
@app.route('/new-feature')
@feature_required('new-feature')
def new_feature():
return render_template('new_feature.html')
Feature Flags ใน Microservices
// Centralized Flag Service Architecture
//
// [Flag Service] ← Admin Dashboard
// ↓ (SDK / API)
// ┌────┼────┐
// ↓ ↓ ↓
// [Web] [API] [Worker]
//
// ทุก Service ใช้ flag state เดียวกัน
// Flag evaluation ใน API Gateway
async function apiGatewayMiddleware(req, res, next) {
const flags = await flagService.getAllFlags({
userId: req.user?.id,
service: req.params.service
})
// Inject flags เข้า request header ส่งต่อไป downstream services
req.headers['x-feature-flags'] = JSON.stringify(flags)
next()
}
// Downstream service อ่าน flags จาก header
function getFlags(req) {
return JSON.parse(req.headers['x-feature-flags'] || '{}')
}
Flag Lifecycle Management
Feature Flags มี Lifecycle ที่ต้องจัดการ ไม่เช่นนั้นจะกลายเป็น Technical Debt:
- Creation — สร้าง Flag พร้อม description, owner, expiry date
- Development — ใช้ Flag wrap ฟีเจอร์ใหม่ในระหว่าง development
- Testing — ทดสอบทั้ง flag on/off
- Rollout — ค่อยๆ เปิด (Canary → Percentage → Full)
- Full Release — เปิด 100%
- Cleanup — ลบ Flag code ออก (สำคัญมาก!)
// ตัวอย่าง Flag metadata
{
"new-checkout": {
"enabled": true,
"percentage": 100,
"owner": "team-checkout",
"created": "2026-03-01",
"expires": "2026-05-01",
"description": "New checkout flow with Apple Pay",
"jira": "PROJ-1234",
"status": "full-rollout", // ready-for-cleanup
"stale_after_days": 30
}
}
Kill Switches สำหรับ Incidents
// Kill Switch — ปิดฟีเจอร์ทันทีเมื่อเกิดปัญหา
const KILL_SWITCHES = {
'external-payment': true, // ปิด external payment เมื่อ gateway ล่ม
'ai-recommendations': true, // ปิด AI เมื่อ model service มีปัญหา
'search-service': true, // ปิด search เมื่อ Elasticsearch ล่ม
}
// ใน code
async function processPayment(order) {
if (!flagService.isEnabled('external-payment')) {
// Fallback: queue สำหรับ retry ภายหลัง
await paymentQueue.add(order)
return { status: 'queued', message: 'Payment will be processed shortly' }
}
return await paymentGateway.charge(order)
}
Trunk-based Development + Feature Flags
Feature Flags ช่วยให้ทีมใช้ Trunk-based Development ได้อย่างมั่นใจ:
- ทุกคน commit เข้า
main(trunk) ทุกวัน - ฟีเจอร์ที่ยังไม่เสร็จซ่อนด้วย Feature Flag
- ไม่ต้องใช้ Long-lived Feature Branches
- Deploy
mainไป production ทุกวัน (Continuous Deployment) - เปิด/ปิดฟีเจอร์ผ่าน Flag โดยไม่ต้อง deploy ใหม่
// Workflow
// 1. Developer สร้าง flag "new-search"
// 2. เขียน code wrap ด้วย flag
// 3. Commit เข้า main → Deploy to production (flag ปิด)
// 4. ทดสอบใน production กับ internal users (flag เปิดเฉพาะ internal)
// 5. Canary release 5% → 25% → 50% → 100%
// 6. ลบ flag code → commit → deploy
// ข้อดี:
// - ไม่มี merge conflicts จาก long-lived branches
// - ทุก commit ผ่าน CI/CD ทันที
// - ฟีเจอร์ถูก test กับ production traffic จริง
Testing กับ Feature Flags
// ทดสอบทั้ง flag on และ off
describe('Checkout', () => {
it('shows new checkout when flag is ON', () => {
// Mock flag service
jest.spyOn(flagService, 'isEnabled')
.mockReturnValue(true)
render(<CheckoutPage />)
expect(screen.getByTestId('new-checkout')).toBeInTheDocument()
})
it('shows old checkout when flag is OFF', () => {
jest.spyOn(flagService, 'isEnabled')
.mockReturnValue(false)
render(<CheckoutPage />)
expect(screen.getByTestId('old-checkout')).toBeInTheDocument()
})
})
// Integration test matrix
// ทดสอบ combinations ของ flags ที่อาจกระทบกัน
const flagCombinations = [
{ 'new-checkout': true, 'promo-bar': true },
{ 'new-checkout': true, 'promo-bar': false },
{ 'new-checkout': false, 'promo-bar': true },
{ 'new-checkout': false, 'promo-bar': false },
]
Monitoring Feature Flag Impact
// วัดผลกระทบของ flag ต่อ metrics
function trackFlagImpact(flagName, variant, metrics) {
analytics.track('flag_evaluation', {
flag: flagName,
variant: variant,
// Business metrics
conversion_rate: metrics.conversion,
revenue: metrics.revenue,
// Technical metrics
latency_p99: metrics.latencyP99,
error_rate: metrics.errorRate,
// User experience
page_load_time: metrics.pageLoadTime,
bounce_rate: metrics.bounceRate
})
}
// Dashboard queries
// - เปรียบเทียบ conversion rate ระหว่าง control vs treatment
// - ดู error rate ก่อน/หลังเปิด flag
// - ดู latency impact ของ flag
// - ดู user engagement metrics
Best Practices
| Do | Don't |
|---|---|
| ตั้ง expiry date ให้ทุก flag | ปล่อย flag ค้างไว้ไม่มีกำหนด |
| ใช้ naming convention ชัดเจน | ตั้งชื่อ flag แบบ "flag1", "test123" |
| ทดสอบทั้ง on/off paths | ทดสอบแค่ happy path |
| มี kill switch สำหรับ critical features | ไม่มีทางปิด feature ได้ทันที |
| Cleanup flags ที่ rollout 100% แล้ว | สะสม stale flags เป็นร้อยๆ |
| ใช้ centralized flag service | กระจาย flag config ไปทั่ว codebase |
| Log flag evaluations สำหรับ debugging | ไม่มี visibility ว่า flag ไหนเปิดอยู่ |
| ใช้ server-side evaluation สำหรับ sensitive flags | ส่ง flag logic ไปฝั่ง client ทั้งหมด |
Anti-patterns ที่ต้องระวัง
- Flag Explosion — มี flags มากเกินไป (>100) โดยไม่ cleanup ทำให้ codebase ซับซ้อน
- Nested Flags — Flag ที่ depend กับ flag อื่น (if flag A && flag B && flag C) ยากต่อการ test
- Permanent Release Flags — ใช้ Release Flag แบบ permanent แทนที่จะ cleanup
- Flag-based Architecture — ออกแบบ architecture ทั้งหมดรอบ flags แทนที่จะใช้ proper abstraction
- Missing Default — ไม่มี default value เมื่อ flag service ล่ม
Flag-driven Development Workflow
// Step-by-step workflow สำหรับทีม
//
// 1. Product: สร้าง feature flag ใน dashboard
// → ตั้งชื่อ, description, owner, expiry
//
// 2. Dev: เขียน code wrap ด้วย flag
// → commit เข้า main ทุกวัน
//
// 3. QA: ทดสอบ flag on/off ใน staging
// → ทดสอบ edge cases, combinations
//
// 4. Release Manager: เปิด flag ให้ internal users
// → Dogfooding ภายใน 1-2 วัน
//
// 5. Gradual Rollout: 5% → 25% → 50% → 100%
// → Monitor metrics ทุก stage
//
// 6. Cleanup: ลบ flag code + config
// → PR review + merge ภายใน 2 สัปดาห์
สรุป
Feature Flags เป็นเครื่องมือสำคัญสำหรับ Modern Software Delivery ช่วยให้ทีม Deploy ได้บ่อยขึ้น Release อย่างปลอดภัย และ Rollback ได้ทันทีเมื่อเกิดปัญหา เมื่อรวมกับ Progressive Delivery ทำให้สามารถ Release ฟีเจอร์ใหม่ให้ User กลุ่มเล็กก่อน ทดสอบกับ Traffic จริง แล้วค่อยขยายไปทั้งระบบ
เริ่มต้นง่ายๆ: เลือก Platform ที่เหมาะกับทีม (Unleash สำหรับ self-hosted, LaunchDarkly สำหรับ enterprise, PostHog สำหรับ flags + analytics) สร้าง flag แรก wrap ฟีเจอร์ใหม่ แล้วลอง rollout ทีละ 10% — คุณจะเข้าใจทันทีว่าทำไม Feature Flags ถึงเปลี่ยนวิธีที่เรา deliver software
