FEATURE-FLAGS SYSTEM-DESIGN BACKEND FRONTEND WEB-DEVELOPMENT PRODUCT-DEVELOPMENT DEVOPS RELEASE-MANAGEMENT CONFIGURATION-MANAGEMENT EXPERIMENTATION

Membangun Sistem Feature Flag Kustom: Kontrol Fitur Aplikasi Anda dari Nol

⏱️ 18 menit baca
👨‍💻

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?

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:

  1. 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).
  2. 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.
  3. 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.
  4. 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:

💡 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:

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:

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