Feature flag — melepas kode tanpa menyalakan fitur
Deploy dan rilis adalah dua hal berbeda. Feature flag membuat kode baru bisa masuk produksi dalam keadaan mati, dinyalakan bertahap, dan dimatikan dalam hitungan detik saat bermasalah — tanpa deploy.
Intisari
- Flag mengubah rollback dari "deploy ulang 10 menit" jadi "matikan saklar, 10 detik".
- Empat jenis: rilis, operasional (saklar darurat), eksperimen, dan izin.
- Flag rilis harus berumur pendek — hapus setelah fitur stabil, atau kodemu jadi labirin percabangan.
- Evaluasi flag harus cepat dan tidak pernah gagal: cache lokal, dengan nilai bawaan yang aman.
- AWS AppConfig menyediakannya secara terkelola; tabel database sederhana sudah cukup untuk memulai.
Antarmuka yang sederhana
type Flag interface {
Aktif(ctx context.Context, nama string) bool
}
// Pemakaian: percabangan yang jelas dan mudah dihapus nanti.
func (h *Handler) Checkout(w http.ResponseWriter, r *http.Request) {
if h.flag.Aktif(r.Context(), "checkout-baru") {
h.checkoutBaru(w, r)
return
}
h.checkoutLama(w, r)
}
// Implementasi: cache lokal yang disegarkan berkala, JANGAN memanggil
// jaringan di jalur permintaan.
type FlagCache struct {
nilai atomic.Pointer[map[string]Aturan] // pembaca tanpa lock (Fase 2)
sumber Sumber
log *slog.Logger
}
func (f *FlagCache) Aktif(ctx context.Context, nama string) bool {
m := f.nilai.Load()
if m == nil {
return false // BAWAAN AMAN: fitur baru mati
}
a, ada := (*m)[nama]
if !ada {
return false
}
return a.Evaluasi(ctx)
}
func (f *FlagCache) segarkanBerkala(ctx context.Context) {
t := time.NewTicker(30 * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
m, err := f.sumber.Muat(ctx)
if err != nil {
// Sumber flag bermasalah: PERTAHANKAN nilai lama.
f.log.WarnContext(ctx, "gagal memuat flag", "err", err)
continue
}
f.nilai.Store(&m)
}
}
}
Dua keputusan yang menjaga flag tidak jadi sumber pemadaman: evaluasi tidak pernah menyentuh jaringan (nilai di memori, disegarkan di latar), dan kegagalan memuat mempertahankan nilai terakhir alih-alih mengosongkannya. Sistem feature flag yang bisa menjatuhkan aplikasi saat ia sendiri bermasalah adalah ironi yang mahal.
Empat jenis flag
| Jenis | Umur | Contoh |
|---|---|---|
| Rilis | Hari–minggu, lalu dihapus | checkout-baru |
| Operasional | Permanen | matikan-rekomendasi — saklar darurat |
| Eksperimen | Selama pengujian | A/B tata letak halaman produk |
| Izin | Permanen | fitur-enterprise per paket langganan |
Flag rilis yang tidak pernah dihapus adalah utang teknis yang berlipat. Sepuluh flag berarti sampai 1024 kombinasi jalur kode — yang tidak satu pun diuji seluruhnya. Buat aturan: setiap flag rilis punya tanggal kedaluwarsa di komentarnya, dan CI memperingatkan flag yang lewat tanggal itu.
Peluncuran bertahap
type Aturan struct {
Aktif bool
Persentase int // 0–100
Pengguna []int64 // daftar putih: tim internal dulu
Tenant []int64
}
func (a Aturan) Evaluasi(ctx context.Context) bool {
if !a.Aktif {
return false
}
u := auth.DariContext(ctx)
if slices.Contains(a.Pengguna, u.ID) {
return true
}
if a.Persentase >= 100 {
return true
}
if a.Persentase <= 0 {
return false
}
// Hash ID pengguna, BUKAN acak: pengguna yang sama harus selalu
// mendapat hasil yang sama, atau tampilannya berubah tiap muat ulang.
h := fnv.New32a()
fmt.Fprintf(h, "%s:%d", a.Nama, u.ID)
return int(h.Sum32()%100) < a.Persentase
}
Urutan peluncuran yang lazim:
1. Deploy dengan flag MATI → kode di produksi, tidak aktif
2. Nyalakan untuk tim internal → uji dengan data sungguhan
3. 5% pengguna, pantau alarm → p95, laju error, metrik bisnis
4. 25% → 50% → 100%
5. Hapus flag dan kode jalur lama ← jangan dilewati
Menguji kedua jalur
func TestCheckout(t *testing.T) {
for _, aktif := range []bool{false, true} {
t.Run(fmt.Sprintf("flag=%v", aktif), func(t *testing.T) {
h := New(svcUji(t), flagTetap{"checkout-baru": aktif})
...
})
}
}
// Flag palsu: satu map, tanpa pustaka.
type flagTetap map[string]bool
func (f flagTetap) Aktif(_ context.Context, nama string) bool { return f[nama] }
Kalau hanya jalur baru yang diuji, flag-mu bukan jaring pengaman. Seluruh nilai feature flag ada pada kemampuan mematikannya saat produksi bermasalah — dan itu berarti jalur lama harus tetap bekerja. Uji keduanya sampai flag-nya dihapus.
Pilihan penyimpanan
| Sumber | Cocok kalau |
|---|---|
| Tabel database + cache 30 detik | Titik awal. Nol layanan baru, sudah cukup untuk sebagian besar |
| AWS AppConfig | Butuh validasi, peluncuran bertahap terkelola, dan rollback otomatis berbasis alarm |
| Parameter Store | Flag sederhana, gratis (Fase 10) |
| Layanan pihak ketiga | Butuh eksperimen dan analitik yang matang |
| Environment variable | Bukan flag — mengubahnya butuh deploy |
Kelebihan AppConfig yang paling relevan: ia bisa memantau alarm CloudWatch selama peluncuran dan membatalkan sendiri kalau laju error naik. Itu menggabungkan flag dengan observability yang sudah kamu bangun di Fase 9 — peluncuran yang mengawasi dirinya sendiri.
Latihan: pasang antarmuka Flag dengan penyimpanan tabel database dan cache 30
detik, lalu bungkus satu endpoint dengan flag. Nyalakan untuk 10% pengguna dan buktikan pengguna yang
sama selalu mendapat jalur yang sama. Terakhir, matikan sumber flag-nya dan pastikan aplikasi tetap
berjalan dengan nilai terakhir.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.