Membangun Sistem Feature Flag Kustom: Kontrol Fitur Aplikasi Anda dari Nol
1. Pendahuluan
Pernahkah Anda ingin merilis fitur baru secara bertahap ke sekelompok pengguna tertentu? Atau mungkin Anda ingin melakukan A/B testing tanpa harus melakukan deployment ulang setiap kali ada perubahan? Di sinilah Feature Flag (atau sering disebut Feature Toggle) menjadi penyelamat!
Feature flag adalah teknik pengembangan perangkat lunak yang memungkinkan Anda mengaktifkan atau menonaktifkan fitur tertentu di aplikasi secara dinamis, tanpa perlu mengubah kode atau melakukan deployment baru. Bayangkan sebuah saklar listrik untuk setiap fitur Anda. Keren, kan?
Mengapa Feature Flag Penting?
- Rilis Aman (Safe Rollout): Anda bisa merilis fitur baru ke sebagian kecil pengguna (misal, tim internal atau 5% pengguna) terlebih dahulu. Jika ada masalah, Anda bisa mematikan fitur tersebut dengan cepat tanpa memengaruhi seluruh pengguna. Ini mengurangi risiko deployment yang buruk.
- A/B Testing: Ingin tahu apakah versi A atau versi B dari fitur Anda lebih disukai pengguna? Dengan feature flag, Anda bisa menampilkan kedua versi secara bersamaan ke segmen pengguna yang berbeda dan mengukur dampaknya.
- Kill Switch: Ketika terjadi bug kritis pada fitur yang sudah dirilis, Anda bisa mematikannya secara instan hanya dengan mengubah status flag. Ini jauh lebih cepat daripada melakukan rollback deployment.
- Personalisasi: Menyesuaikan pengalaman pengguna berdasarkan atribut tertentu (lokasi, paket langganan, dll.).
- Pengembangan Paralel: Beberapa tim bisa bekerja pada fitur yang berbeda di main branch yang sama tanpa saling mengganggu, karena fitur yang belum selesai bisa dinonaktifkan.
Banyak layanan komersial yang menawarkan solusi feature flag (seperti LaunchDarkly, Optimizely, Split.io). Namun, sebagai developer yang haus ilmu, terkadang kita ingin memahami bagaimana mekanismenya bekerja di balik layar, atau mungkin kebutuhan kita cukup sederhana sehingga membangun sendiri lebih efisien. Artikel ini akan memandu Anda merancang dan mengimplementasikan sistem feature flag kustom Anda sendiri dari nol! 🚀
2. Komponen Utama Sistem Feature Flag
Sebelum kita menyelam ke implementasi, mari kita pahami komponen dasar yang membentuk sistem feature flag:
- Storage / Database: Ini adalah tempat semua konfigurasi feature flag Anda disimpan. Termasuk nama flag, deskripsi, status aktif/nonaktif, dan yang paling penting, aturan targeting (siapa yang boleh melihat fitur ini).
- Evaluator: Ini adalah “otak” sistem Anda. Sebuah fungsi atau layanan yang mengambil ID pengguna atau konteks lainnya, lalu mengevaluasi aturan targeting dari setiap flag untuk menentukan apakah fitur tersebut harus aktif untuk konteks tersebut.
- Client Library / SDK: Kode yang diintegrasikan ke dalam aplikasi frontend (JavaScript, React, Vue) atau backend (Node.js, Python, Go) Anda. Tugasnya adalah berkomunikasi dengan evaluator (biasanya melalui API) untuk mendapatkan status flag, dan kemudian menyediakannya untuk digunakan di kode aplikasi Anda.
- Admin UI (Opsional tapi Sangat Berguna): Sebuah antarmuka web yang memungkinkan product manager, developer, atau tim lainnya untuk mengelola feature flag secara visual, tanpa harus menyentuh kode atau database secara langsung. Untuk artikel ini, kita akan fokus pada bagian teknisnya terlebih dahulu.
🎯 Tujuan kita: Membangun storage, evaluator, dan contoh client integration yang paling sederhana namun fungsional.
3. Merancang Skema Data untuk Feature Flag
Bagian pertama yang krusial adalah bagaimana kita menyimpan informasi tentang feature flag. Kita akan menggunakan database relasional (misal: PostgreSQL) karena fleksibilitas tipe data JSONB-nya yang sangat cocok untuk aturan targeting yang dinamis.
Berikut adalah contoh skema tabel feature_flags:
CREATE TABLE feature_flags (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
is_enabled_globally BOOLEAN NOT NULL DEFAULT FALSE,
targeting_rules JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
📌 Penjelasan Skema:
id: ID unik untuk setiap flag.name: Nama unik flag (misal:new-checkout-flow,beta-dashboard). Ini yang akan kita gunakan di kode.description: Penjelasan singkat tentang flag.is_enabled_globally: Sebuah boolean sederhana. JikaTRUE, flag ini aktif untuk semua orang, terlepas daritargeting_rules. Ini berguna sebagai kill switch global atau untuk fitur yang ingin dirilis ke seluruh pengguna.targeting_rules: Ini adalah inti dari sistem kita! Kolom JSONB ini akan menyimpan berbagai aturan yang menentukan siapa yang akan melihat fitur ini.
💡 Contoh targeting_rules (JSONB):
{
"user_ids": [101, 205, 312],
"percentage": 50,
"regions": ["ID", "SG"],
"plan_type": "premium",
"browser": "Chrome"
}
Dengan JSONB, kita bisa menyimpan berbagai jenis aturan tanpa perlu mengubah skema tabel setiap kali ada aturan baru. Ini sangat fleksibel!
4. Implementasi Backend: Evaluator dan API
Sekarang, mari kita bangun backend yang akan mengevaluasi flag. Kita akan menggunakan Node.js dengan Express (atau framework backend favorit Anda lainnya).
Langkah 1: Menyiapkan Database Connection (gunakan ORM atau driver database pilihan Anda).
Langkah 2: Membuat Logika Evaluator
Fungsi evaluator ini akan menerima daftar flag dari database dan konteks pengguna (misal: userId, region, planType), lalu menentukan flag mana yang aktif.
// evaluator.js
function evaluateFlag(flag, context) {
// 1. Cek is_enabled_globally
if (flag.is_enabled_globally) {
return true;
}
const rules = flag.targeting_rules;
// 2. Evaluasi aturan user_ids
if (rules.user_ids && rules.user_ids.includes(context.userId)) {
return true;
}
// 3. Evaluasi aturan percentage (misal untuk A/B testing)
if (typeof rules.percentage === 'number') {
// Gunakan hashing yang konsisten untuk user ID agar hasilnya stabil
const hash = simpleHash(context.userId.toString()); // Implementasi simpleHash di bawah
if ((hash % 100) < rules.percentage) {
return true;
}
}
// 4. Evaluasi aturan regions
if (rules.regions && context.region && rules.regions.includes(context.region)) {
return true;
}
// 5. Evaluasi aturan plan_type
if (rules.plan_type && context.planType && rules.plan_type === context.planType) {
return true;
}
// Tambahkan aturan lain sesuai kebutuhan...
// Jika tidak ada aturan yang cocok atau flag tidak aktif secara global
return false;
}
// Implementasi hashing sederhana (untuk contoh)
// Dalam produksi, gunakan hashing yang lebih robust dan konsisten
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert to 32bit integer
}
return Math.abs(hash);
}
async function getEvaluatedFlags(context, db) {
// ⚠️ Penting: Dalam produksi, cache hasil query ini!
// Query semua flag dari database
const allFlags = await db.query('SELECT * FROM feature_flags');
const activeFlags = {};
for (const flag of allFlags.rows) {
activeFlags[flag.name] = evaluateFlag(flag, context);
}
return activeFlags;
}
module.exports = { getEvaluatedFlags };
⚠️ Peringatan: Implementasi simpleHash di atas sangat dasar. Untuk production, gunakan algoritma hashing yang lebih baik dan pastikan hasilnya konsisten di seluruh aplikasi Anda. Tujuannya adalah untuk mendistribusikan pengguna secara merata ke dalam persentase tertentu.
Langkah 3: Membuat API Endpoint
Sekarang kita akan membuat endpoint API yang akan digunakan oleh frontend atau backend lain untuk mendapatkan status flag.
// app.js (contoh dengan Express)
const express = require('express');
const { getEvaluatedFlags } = require('./evaluator');
const { Pool } = require('pg'); // Contoh untuk PostgreSQL
const app = express();
app.use(express.json());
// Konfigurasi Database (ganti dengan kredensial Anda)
const pool = new Pool({
user: 'your_user',
host: 'localhost',
database: 'your_db',
password: 'your_password',
port: 5432,
});
// Endpoint untuk mendapatkan feature flags
app.post('/api/flags', async (req, res) => {
// Konteks pengguna bisa datang dari body request atau token autentikasi
const userContext = req.body.context || {}; // { userId: 123, region: 'ID', planType: 'basic' }
try {
// 📌 Caching: Implementasikan caching di sini (misal dengan Redis)
// agar tidak selalu query database untuk setiap request
const flags = await getEvaluatedFlags(userContext, pool);
res.json(flags);
} catch (error) {
console.error('Error fetching flags:', error);
res.status(500).json({ error: 'Failed to retrieve feature flags' });
}
});
// Contoh endpoint untuk membuat/mengupdate flag (biasanya untuk Admin UI)
app.post('/api/admin/flags', async (req, res) => {
const { name, description, is_enabled_globally, targeting_rules } = req.body;
try {
const result = await pool.query(
`INSERT INTO feature_flags (name, description, is_enabled_globally, targeting_rules)
VALUES ($1, $2, $3, $4)
ON CONFLICT (name) DO UPDATE
SET description = $2, is_enabled_globally = $3, targeting_rules = $4, updated_at = CURRENT_TIMESTAMP
RETURNING *;`,
[name, description, is_enabled_globally, targeting_rules]
);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Error creating/updating flag:', error);
res.status(500).json({ error: 'Failed to create/update feature flag' });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Feature Flag Backend running on port ${PORT}`);
});
✅ Tips Backend:
- Caching: Ini sangat penting! Hasil evaluasi flag harus di-cache (misal: di Redis) untuk periode tertentu. Setiap request tidak boleh langsung mengakses database. Ketika ada perubahan flag di database, cache harus di-invalidate.
- Keamanan: Endpoint admin
/api/admin/flagsharus diamankan dengan otentikasi dan otorisasi yang kuat. Hanya orang yang berwenang yang bisa mengubah flag. - Asynchronous: Gunakan message queue untuk invalidate cache jika Anda memiliki beberapa instance backend.
5. Integrasi Frontend: Menggunakan Feature Flag di Aplikasi Web Anda
Setelah backend siap, saatnya mengintegrasikan ke aplikasi frontend Anda. Kita akan menggunakan React sebagai contoh, tapi konsepnya bisa diaplikasikan ke framework lain seperti Vue atau Angular.
Langkah 1: Membuat Hook Kustom untuk Mengambil Flag
// hooks/useFeatureFlags.js
import React, { useState, useEffect, useContext, createContext } from 'react';
const FeatureFlagsContext = createContext(null);
export const FeatureFlagsProvider = ({ children, initialContext }) => {
const [flags, setFlags] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchFlags = async () => {
try {
// Konteks pengguna bisa diambil dari state global auth, local storage, dll.
// Untuk contoh ini, kita pakai initialContext yang diberikan
const response = await fetch('/api/flags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ context: initialContext }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setFlags(data);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
fetchFlags();
}, [initialContext]); // Re-fetch jika konteks berubah (misal: user login/logout)
// Menyediakan fungsi untuk mendapatkan status flag
const getFlag = (flagName) => flags[flagName];
return (
<FeatureFlagsContext.Provider value={{ getFlag, isLoading, error }}>
{children}
</FeatureFlagsContext.Provider>
);
};
export const useFeatureFlags = () => {
const context = useContext(FeatureFlagsContext);
if (context === null) {
throw new Error('useFeatureFlags must be used within a FeatureFlagsProvider');
}
return context;
};
Langkah 2: Menggunakan FeatureFlagsProvider di Aplikasi Anda
Bungkus aplikasi Anda dengan FeatureFlagsProvider di level tertinggi, biasanya di App.js atau index.js.
// App.js
import React from 'react';
import { FeatureFlagsProvider } from './hooks/useFeatureFlags';
import Dashboard from './Dashboard';
function App() {
// Contoh user context (bisa dari state autentikasi Anda)
const currentUserContext = {
userId: 123,
region: 'ID',
planType: 'premium',
};
return (
<FeatureFlagsProvider initialContext={currentUserContext}>
<div className="App">
<h1>Aplikasi dengan Feature Flags</h1>
<Dashboard />
</div>
</FeatureFlagsProvider>
);
}
export default App;
Langkah 3: Menggunakan Flag di Komponen React
Sekarang, Anda bisa menggunakan useFeatureFlags di komponen mana pun yang membutuhkan status flag.
// Dashboard.js
import React from 'react';
import { useFeatureFlags } from './hooks/useFeatureFlags';
function Dashboard() {
const { getFlag, isLoading, error } = useFeatureFlags();
if (isLoading) {
return <div>Memuat fitur...</div>;
}
if (error) {
return <div>Error memuat fitur: {error.message}</div>;
}
// Menggunakan flag untuk menampilkan/menyembunyikan fitur
const isNewDashboardLayoutEnabled = getFlag('new-dashboard-layout');
const isPromoBannerEnabled = getFlag('promo-banner');
return (
<div>
<h2>Halaman Dashboard</h2>
{/* Fitur Baru: New Dashboard Layout */}
{isNewDashboardLayoutEnabled ? (
<div style={{ border: '1px solid blue', padding: '10px', margin: '10px 0' }}>
<h3>Selamat datang di Layout Dashboard Baru! 🎉</h3>
<p>Nikmati tampilan yang lebih segar dan fungsionalitas yang ditingkatkan.</p>
</div>
) : (
<div style={{ border: '1px solid gray', padding: '10px', margin: '10px 0' }}>
<h3>Dashboard Klasik</h3>
<p>Ini adalah tampilan dashboard lama Anda.</p>
</div>
)}
{/* Fitur Promo Banner */}
{isPromoBannerEnabled && (
<div style={{ background: 'yellow', padding: '10px', margin: '10px 0' }}>
<p>Jangan lewatkan promo spesial kami! Diskon 20% untuk semua paket premium!</p>
</div>
)}
<p>Konten dashboard lainnya...</p>
</div>
);
}
export default Dashboard;
Ini adalah contoh dasar bagaimana Anda bisa mengintegrasikan feature flag di frontend. Konteks pengguna (seperti userId, region, planType) harus diambil dari sistem autentikasi atau manajemen state aplikasi Anda.
6. Best Practices dan Pertimbangan Lanjutan
Membangun sistem feature flag sendiri memang memberdayakan, tapi ada beberapa hal yang perlu diperhatikan:
-
Naming Convention Flag:
- Gunakan nama yang jelas, konsisten, dan mudah dimengerti (misal:
new-feature-x,checkout-v2,enable-dark-mode). - Hindari nama yang ambigu atau terlalu umum.
- Gunakan nama yang jelas, konsisten, dan mudah dimengerti (misal:
-
Lifecycle Flag:
- Ide: Ketika sebuah fitur baru direncanakan.
- Implementasi: Flag dibuat bersamaan dengan kode fitur.
- Rilis: Flag diaktifkan secara bertahap.
- Pembersihan (Cleanup): Setelah fitur stabil dan sepenuhnya dirilis ke semua pengguna, atau jika fitur dibatalkan, hapus flag dan kode yang terkait dengan flag tersebut. ❌ Feature flag yang tidak dibersihkan akan menumpuk dan menjadi technical debt.
-
Keamanan dan Audit:
- Siapa yang boleh mengubah status flag? Pastikan ada otorisasi yang ketat, terutama untuk endpoint admin.
- Rekam setiap perubahan flag (siapa yang mengubah, kapan, perubahan apa). Ini penting untuk troubleshooting dan audit.
-
Performance:
- Seperti yang sudah disebutkan, caching di backend adalah kunci.
- Di frontend, fetch flag hanya sekali saat aplikasi dimuat atau saat konteks pengguna berubah (misal: login/logout).
-
Testing:
- Bagaimana Anda menguji kode yang dibungkus oleh feature flag? Pastikan Anda memiliki test case untuk kedua skenario (flag aktif dan flag nonaktif).
- Gunakan mocking untuk mensimulasikan status flag yang berbeda dalam unit atau integration test Anda.
-
Pertimbangan SSR/SSG:
- Jika aplikasi Anda menggunakan Server-Side Rendering (SSR) atau Static Site Generation (SSG), pastikan feature flag dievaluasi di sisi server juga. Ini penting untuk SEO dan initial render yang konsisten.
-
User Experience (UX):
- Hindari flickering UI saat flag dievaluasi. Tampilkan loading state atau fallback yang halus.
- Pertimbangkan bagaimana perubahan flag secara real-time akan memengaruhi pengguna. Apakah perlu refresh halaman?
Kesimpulan
Membangun sistem feature flag kustom memang membutuhkan upaya awal, tapi manfaat yang ditawarkannya dalam hal fleksibilitas rilis, eksperimen, dan manajemen risiko sangatlah besar. Anda tidak hanya mendapatkan kontrol penuh atas fitur aplikasi Anda, tetapi juga pemahaman mendalam tentang arsitektur di baliknya.
Dengan storage yang fleksibel, evaluator yang cerdas, dan integrasi client yang mulus, Anda bisa mulai merilis fitur dengan lebih percaya diri, melakukan A/B testing dengan mudah, dan memiliki kill switch yang siap sedia. Ingat, kunci keberhasilan ada pada pembersihan flag yang teratur dan praktik terbaik lainnya.
Selamat mencoba membangun sistem feature flag Anda sendiri! Ini adalah langkah besar menuju proses pengembangan yang lebih tangguh dan adaptif.
🔗 Baca Juga
- Manajemen Feature Flags dan A/B Testing di Frontend: Kontrol Penuh Pengalaman Pengguna
- A/B Testing untuk Developer: Membangun Infrastruktur Eksperimen yang Efektif
- Backend-Driven UI (BDUI): Merancang Antarmuka Pengguna yang Dinamis dan Fleksibel dari Backend
- Memilih dan Mengimplementasikan Sistem Manajemen Feature Flag: Beyond Basic Toggles