Feature flag adalah salah satu cara paling praktis untuk membuat rilis di Spring Boot lebih aman. Alih-alih langsung mengekspos kode baru ke semua pengguna setelah deploy, Anda bisa menanamkan logika sakelar untuk mengaktifkan fitur hanya untuk sebagian trafik, cohort tertentu, atau bahkan tidak sama sekali sampai sistem siap.

Dalam praktiknya, pendekatan ini membantu pada tiga hal penting: memisahkan deploy dari release, rollout bertahap, dan kill switch saat insiden. Jika fitur baru memicu lonjakan error atau latensi, tim sering kali tidak perlu rollback seluruh aplikasi; cukup matikan flag, lalu layanan kembali ke perilaku lama sambil investigasi berjalan.

Mengapa feature flag relevan untuk alur rilis yang lebih aman

Tanpa feature flag, deploy dan release sering menjadi satu paket. Begitu artifact baru naik ke production, semua perubahan aktif sekaligus. Model ini sederhana, tetapi risikonya besar:

  • Sulit mengisolasi dampak satu fitur tertentu.
  • Rollback sering berarti redeploy versi lama, yang lebih lambat dan berisiko.
  • Fitur yang seharusnya diuji bertahap justru langsung terkena seluruh pengguna.

Dengan feature flag, alur rilis menjadi lebih terkontrol:

  1. Deploy kode baru ke production dalam keadaan nonaktif.
  2. Lakukan dark launch bila perlu: jalankan sebagian jalur kode tanpa mengekspos hasilnya ke pengguna.
  3. Aktifkan untuk internal user atau cohort terbatas.
  4. Naikkan cakupan secara bertahap sambil memantau metrik.
  5. Jika terjadi masalah, gunakan kill switch untuk mematikan fitur tanpa rollback kode.

Ini bukan berarti feature flag menggantikan testing atau rollback. Tujuannya adalah mengurangi blast radius dan memberi kontrol operasional yang lebih baik saat perubahan sudah berada di production.

Jenis rollout yang umum dipakai

1. Dark launch

Pada dark launch, kode fitur baru sudah dideploy dan bahkan bisa dieksekusi, tetapi hasilnya belum dipakai oleh pengguna akhir. Contohnya:

  • Menjalankan perhitungan baru di belakang layar lalu membandingkan hasilnya dengan versi lama.
  • Mengirim event tambahan untuk observability tanpa mengubah respons API.
  • Menulis hasil ke log atau metric untuk validasi performa.

Pendekatan ini berguna ketika Anda ingin menguji perilaku sistem nyata tanpa risiko mengubah pengalaman pengguna.

2. Rollout berbasis cohort

Fitur diaktifkan hanya untuk kelompok tertentu, misalnya:

  • Tim internal atau akun admin.
  • Tenant tertentu pada aplikasi multi-tenant.
  • Pengguna di region tertentu.
  • Pelanggan beta.

Ini cocok bila Anda ingin mendapatkan umpan balik terarah dan mempersempit dampak bila ada kegagalan.

3. Rollout berbasis persentase

Fitur diaktifkan ke sebagian pengguna, misalnya 1%, 5%, 25%, lalu 100%. Agar konsisten, pemilihan pengguna sebaiknya deterministik, biasanya berdasarkan hash dari user ID atau tenant ID. Dengan begitu, pengguna yang sama tidak bolak-balik masuk dan keluar dari varian fitur pada setiap request.

Rollout persentase cocok untuk sistem dengan trafik cukup besar dan identitas pengguna yang stabil.

Implementasi sederhana feature flag di Spring Boot

Implementasi paling dasar bisa dimulai dari konfigurasi lokal. Ini belum setara dengan platform feature management penuh, tetapi cukup untuk banyak tim kecil selama disiplin operasionalnya baik.

Representasi konfigurasi flag

Misalnya kita punya fitur baru untuk mesin rekomendasi:

features:
  recommendation-v2:
    enabled: false
    rollout-percentage: 0
    allowed-tenants: []

Kemudian bind ke class konfigurasi:

package com.example.flags;

import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "features.recommendation-v2")
public class RecommendationV2Properties {
    private boolean enabled;
    private int rolloutPercentage;
    private List<String> allowedTenants = new ArrayList<>();

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public int getRolloutPercentage() {
        return rolloutPercentage;
    }

    public void setRolloutPercentage(int rolloutPercentage) {
        this.rolloutPercentage = rolloutPercentage;
    }

    public List<String> getAllowedTenants() {
        return allowedTenants;
    }

    public void setAllowedTenants(List<String> allowedTenants) {
        this.allowedTenants = allowedTenants;
    }
}

Aktifkan binding konfigurasi sesuai pola yang Anda pakai di aplikasi, misalnya dengan @EnableConfigurationProperties atau scanning konfigurasi properti.

Service evaluator untuk keputusan flag

Jangan menyebar logika if flag mentah ke seluruh codebase. Lebih baik pusatkan evaluasinya pada satu service agar aturan rollout konsisten dan mudah diuji.

package com.example.flags;

import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;
import org.springframework.stereotype.Service;

@Service
public class RecommendationFlagService {

    private final RecommendationV2Properties properties;

    public RecommendationFlagService(RecommendationV2Properties properties) {
        this.properties = properties;
    }

    public boolean isEnabledFor(String userId, String tenantId) {
        if (!properties.isEnabled()) {
            return false;
        }

        if (tenantId != null && properties.getAllowedTenants().contains(tenantId)) {
            return true;
        }

        int percentage = properties.getRolloutPercentage();
        if (percentage <= 0) {
            return false;
        }
        if (percentage >= 100) {
            return true;
        }

        if (userId == null || userId.isBlank()) {
            return false;
        }

        return bucket(userId) < percentage;
    }

    private int bucket(String key) {
        CRC32 crc32 = new CRC32();
        crc32.update(key.getBytes(StandardCharsets.UTF_8));
        long value = crc32.getValue();
        return (int) (value % 100);
    }
}

Kenapa pendekatan ini bekerja:

  • enabled menjadi sakelar global, berguna sebagai kill switch.
  • allowedTenants memungkinkan rollout berbasis cohort.
  • rolloutPercentage mengaktifkan fitur secara bertahap.
  • Hash deterministik memastikan pengguna yang sama tetap berada di bucket yang sama.

Catatan: Untuk sistem yang sensitif terhadap konsistensi lintas bahasa atau lintas layanan, pilih algoritma hashing yang konsisten dan dokumentasikan dengan jelas. Tujuannya bukan keamanan kriptografis, melainkan distribusi yang stabil.

Pemakaian pada service bisnis

package com.example.recommendation;

import com.example.flags.RecommendationFlagService;
import org.springframework.stereotype.Service;

@Service
public class RecommendationService {

    private final RecommendationFlagService flagService;
    private final LegacyRecommendationEngine legacyEngine;
    private final RecommendationV2Engine v2Engine;

    public RecommendationService(
            RecommendationFlagService flagService,
            LegacyRecommendationEngine legacyEngine,
            RecommendationV2Engine v2Engine) {
        this.flagService = flagService;
        this.legacyEngine = legacyEngine;
        this.v2Engine = v2Engine;
    }

    public RecommendationResult getRecommendations(String userId, String tenantId) {
        if (flagService.isEnabledFor(userId, tenantId)) {
            return v2Engine.generate(userId, tenantId);
        }
        return legacyEngine.generate(userId, tenantId);
    }
}

Di sini, kode lama dan baru hidup berdampingan untuk sementara. Ini memang menambah kompleksitas, tetapi memberi jalur fallback yang cepat.

Memisahkan deploy dari release dalam praktik tim

Pola kerja yang umum dan aman:

  1. Gabungkan kode fitur ke branch utama dengan flag dalam posisi mati.
  2. Deploy ke staging lalu uji skenario on/off.
  3. Deploy ke production tanpa mengaktifkan fitur untuk publik.
  4. Dark launch bila perlu untuk mengamati query, latensi, atau event internal.
  5. Aktifkan ke internal user atau tenant tertentu.
  6. Naikkan persentase rollout secara bertahap sambil memantau metrik.
  7. Matikan flag jika ada anomali, tanpa redeploy.
  8. Bersihkan kode lama setelah fitur stabil dan 100% aktif.

Keuntungan utamanya adalah keputusan release berpindah dari proses build/deploy ke kontrol operasional yang lebih cepat. Untuk tim kecil, ini sangat membantu ketika deploy dilakukan di jam kerja tetapi eksposur ke pengguna ditingkatkan secara hati-hati.

Metrik yang wajib dipantau dengan Micrometer dan Actuator

Feature flag hanya aman jika dibarengi observability. Saat rollout dimulai, Anda perlu tahu apakah perubahan benar-benar sehat atau justru memperburuk sistem.

Metrik minimum yang sebaiknya dipantau

  • Error rate: jumlah exception, HTTP 5xx, atau kegagalan downstream.
  • Latency: p50, p95, p99 untuk endpoint atau operasi utama.
  • Traffic volume: request rate, jumlah event, atau throughput.
  • Resource usage: CPU, memory, thread pool, connection pool.
  • Business metric: conversion, checkout success, login success, atau jumlah order gagal.

Jangan hanya melihat metrik teknis. Bisa saja endpoint tetap cepat, tetapi fitur baru merusak hasil bisnis, misalnya rekomendasi kosong atau diskon tidak terpasang.

Menandai jalur flag dengan Micrometer

Anda bisa menambah counter atau timer untuk membedakan jalur legacy dan flagged.

package com.example.recommendation;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;

@Service
public class RecommendationMetrics {

    private final Counter legacyCounter;
    private final Counter v2Counter;

    public RecommendationMetrics(MeterRegistry registry) {
        this.legacyCounter = Counter.builder("recommendation_requests_total")
                .tag("path", "legacy")
                .register(registry);

        this.v2Counter = Counter.builder("recommendation_requests_total")
                .tag("path", "v2")
                .register(registry);
    }

    public void markLegacy() {
        legacyCounter.increment();
    }

    public void markV2() {
        v2Counter.increment();
    }
}

Lalu panggil metric tersebut dari service bisnis. Dengan ini, dashboard akan memperlihatkan proporsi trafik yang masuk ke jalur baru versus lama. Jika jalur baru naik dari 5% ke 25%, Anda bisa melihat apakah error rate dan latency ikut berubah.

Actuator untuk health dan inspeksi operasional

Spring Boot Actuator membantu membuka endpoint observability seperti health, metrics, dan info sesuai konfigurasi yang Anda izinkan. Untuk rollout bertahap, ini berguna untuk:

  • Memastikan instance sehat sebelum menaikkan persentase rollout.
  • Melihat metric yang terkait endpoint atau dependency tertentu.
  • Mengecek apakah semua instance memuat konfigurasi flag yang sama.

Di lingkungan production, batasi eksposur endpoint Actuator dengan autentikasi, jaringan internal, atau gateway yang sesuai. Jangan membuka semuanya ke publik.

Sinyal kapan flag cukup dimatikan tanpa rollback kode

Tidak semua insiden mengharuskan rollback artifact. Dalam beberapa kasus, mematikan flag jauh lebih cepat dan cukup aman bila:

  • Masalah hanya muncul pada jalur fitur baru, sementara jalur lama masih utuh.
  • Kontrak database dan API lama masih kompatibel.
  • Fitur baru tidak melakukan migrasi irreversibel saat aktif.
  • Setelah flag dimatikan, request berikutnya otomatis kembali ke alur lama.

Sebaliknya, rollback kode mungkin tetap diperlukan bila:

  • Perubahan skema database tidak backward compatible.
  • Kode baru mengubah format event yang dipakai sistem lain.
  • Startup aplikasi sendiri sudah gagal sebelum flag sempat dievaluasi.
  • Efek samping fitur sudah terjadi dan tidak hilang hanya dengan mematikan flag.

Aturan praktis: feature flag efektif sebagai kill switch jika jalur lama masih tersedia, aman dipakai, dan tidak rusak oleh perubahan deployment terbaru.

Contoh dark launch yang realistis

Misalkan Anda sedang mengganti algoritma penentuan ongkir. Anda belum ingin pengguna melihat hasil baru, tetapi ingin tahu apakah performanya layak dan hasilnya masuk akal. Maka alurnya bisa seperti ini:

  1. Endpoint tetap mengembalikan hasil dari algoritma lama.
  2. Jika flag dark launch aktif, service juga menjalankan algoritma baru di belakang layar.
  3. Hasil baru tidak dikirim ke client, tetapi dicatat sebagai metric, log, atau sample untuk dibandingkan.
public ShippingQuote getQuote(OrderContext ctx) {
    ShippingQuote legacy = legacyCalculator.calculate(ctx);

    if (darkLaunchFlagService.isEnabledFor(ctx.userId(), ctx.tenantId())) {
        try {
            ShippingQuote candidate = newCalculator.calculate(ctx);
            comparisonRecorder.record(legacy, candidate);
        } catch (Exception ex) {
            darkLaunchErrorCounter.increment();
        }
    }

    return legacy;
}

Keuntungan dark launch adalah Anda bisa mengevaluasi beban CPU, query database, dependency eksternal, dan kualitas hasil sebelum benar-benar mengaktifkannya untuk pengguna.

Risiko umum dan kesalahan yang sering terjadi

1. Flag stale yang tidak pernah dibersihkan

Feature flag seharusnya bersifat sementara untuk kebutuhan rollout atau eksperimen. Jika dibiarkan terlalu lama:

  • Kode menjadi sulit dibaca.
  • Cabang logika bertambah.
  • Pengujian makin rumit karena kombinasi state flag meningkat.

Solusinya:

  • Setiap flag harus punya owner, tujuan, dan tanggal evaluasi.
  • Buat daftar flag aktif di backlog teknis.
  • Hapus flag dan jalur lama segera setelah rollout selesai dan stabil.

2. Branching kompleks di terlalu banyak lapisan

Kesalahan umum lain adalah menaruh pengecekan flag di controller, service, repository, dan client eksternal sekaligus. Akibatnya, perilaku sistem sulit dipahami.

Lebih baik:

  • Pusatkan evaluasi flag pada service khusus.
  • Simpan branching sedekat mungkin dengan keputusan bisnis.
  • Hindari menumpuk beberapa flag pada jalur request yang sama tanpa alasan jelas.

3. Inkonsistensi konfigurasi antar-instance

Jika Anda menjalankan banyak instance aplikasi, perubahan flag yang tidak sinkron bisa memicu perilaku acak: sebagian request masuk ke fitur baru, sebagian tidak, bukan karena rollout yang direncanakan tetapi karena konfigurasi berbeda.

Penyebabnya bisa berupa:

  • Environment variable berbeda antar pod atau node.
  • Konfigurasi tidak direload secara konsisten.
  • Cache lokal pada satu instance belum diperbarui.

Pencegahan:

  • Gunakan satu sumber konfigurasi yang jelas.
  • Pastikan mekanisme distribusi perubahan konfigurasi terdokumentasi.
  • Tambahkan endpoint internal atau log startup yang menunjukkan state flag saat ini.
  • Audit bahwa seluruh instance memuat nilai yang sama sebelum rollout dilanjutkan.

4. Mengandalkan flag untuk menutupi desain yang tidak kompatibel

Feature flag bukan solusi jika perubahan Anda merusak kompatibilitas data atau kontrak antar-layanan. Untuk perubahan besar, tetap perlu strategi seperti expand-and-contract, migrasi bertahap, atau versioning API.

Testing yang perlu dilakukan

Minimal, uji empat skenario berikut:

  1. Flag off: aplikasi harus tetap berjalan dengan perilaku lama.
  2. Flag on: fitur baru berjalan sesuai ekspektasi.
  3. Partial rollout: pemilihan cohort atau bucket konsisten.
  4. Kill switch: mematikan flag mengembalikan sistem ke jalur aman tanpa error tambahan.

Untuk rollout berbasis persentase, tambahkan test yang memastikan fungsi hashing stabil. Jika implementasi bucket berubah tanpa sengaja, distribusi pengguna bisa berubah drastis dan mengganggu eksperimen atau observasi.

Postmortem ringan: insiden fitur baru dan peran kill switch

Berikut contoh insiden yang realistis pada tim kecil.

Kronologi singkat

  • Tim merilis fitur Recommendation V2 ke production dalam kondisi flag mati.
  • Setelah validasi dasar, fitur diaktifkan untuk 10% user.
  • Dalam 15 menit, latency endpoint produk naik dan connection pool database mulai penuh.
  • Error 5xx belum melonjak tinggi, tetapi p95 meningkat cukup tajam dan dashboard menunjukkan query baru jauh lebih berat dari estimasi.
  • Tim mematikan flag global. Trafik kembali ke engine lama, latensi turun, dan insiden berhenti tanpa redeploy.

Akar masalah

Algoritma baru menjalankan query tambahan yang tidak terlihat bermasalah di staging karena volume data lebih kecil. Pada production, query tersebut memicu akses tabel besar tanpa indeks yang tepat.

Mengapa rollback kode tidak diperlukan

Karena jalur lama masih utuh dan fitur baru dibungkus penuh oleh flag, mematikan flag langsung mengembalikan perilaku sistem. Tidak ada migrasi database yang bersifat irreversibel, dan aplikasi tetap sehat secara umum.

Pembelajaran

  • Dark launch seharusnya dilakukan lebih dulu untuk memotret query dan latensi sebelum traffic publik masuk.
  • Dashboard metrik per jalur fitur sangat membantu membedakan masalah di engine lama dan baru.
  • Kill switch efektif hanya karena fallback ke kode lama memang disiapkan dengan benar.

Tindakan pencegahan yang realistis untuk tim kecil

Tim kecil sering tidak punya platform feature management yang kompleks. Itu tidak masalah, asalkan beberapa disiplin dasar diterapkan.

  • Batasi jumlah flag aktif. Jangan membuka terlalu banyak eksperimen bersamaan.
  • Tetapkan owner untuk setiap flag.
  • Dokumentasikan tujuan flag: rollout, eksperimen, dark launch, atau kill switch permanen.
  • Pasang metrik sebelum rollout, bukan setelah insiden terjadi.
  • Gunakan naming yang jelas, misalnya recommendation-v2, bukan new-feature-1.
  • Siapkan prosedur operasional sederhana: siapa yang boleh mengubah flag, di mana nilainya disimpan, dan bagaimana verifikasinya.
  • Hapus flag setelah selesai untuk mencegah technical debt.
  • Latih kill switch saat game day atau simulasi insiden ringan.

Kapan memakai solusi sederhana, kapan perlu platform khusus

Konfigurasi lokal di Spring Boot cukup bila:

  • Jumlah flag sedikit.
  • Perubahan flag tidak terlalu sering.
  • Tim kecil dan alur release masih sederhana.
  • Cohort dan aturan rollout belum kompleks.

Anda mungkin perlu platform khusus atau service konfigurasi terpusat bila:

  • Butuh update flag real-time tanpa restart atau redeploy.
  • Jumlah flag bertambah dan perlu audit trail.
  • Rollout harus lintas banyak service secara konsisten.
  • Perlu targeting yang lebih kaya, misalnya berdasarkan atribut user, region, paket langganan, atau eksperimen A/B.

Poin pentingnya bukan memilih alat paling besar, melainkan memastikan kontrol rilis, observability, dan kebersihan siklus hidup flag berjalan baik.

Kesimpulan

Spring Boot: Feature Flag untuk rollout bertahap dan kill switch adalah pendekatan praktis untuk membuat deploy lebih aman. Dengan memisahkan deploy dari release, Anda bisa melakukan dark launch, mengaktifkan fitur per cohort atau persentase, lalu mematikan fitur dengan cepat saat insiden tanpa selalu rollback kode.

Agar berhasil di production, feature flag harus diperlakukan sebagai bagian dari desain rilis, bukan sekadar if-else tambahan. Artinya, Anda perlu evaluator yang konsisten, fallback yang jelas, metrik dengan Micrometer/Actuator, prosedur operasional, dan disiplin membersihkan flag yang sudah selesai dipakai.