Trunk-Based Development Berbasis Feature Flag

Foto oleh Hannes Grobe via Wikimedia Commons (CC BY-SA 4.0)
Deploy adalah peristiwa teknis — kode baru berjalan di server Anda. Release adalah peristiwa produk — pengguna nyata sekarang bisa melihat fitur. Feature flag memisahkan keduanya sehingga Anda bisa men-deploy kode gelap berkali-kali sehari dan merilis kemudian dengan membalik flag, sering sebagai keputusan produk alih-alih rekayasa.
Mereka membuat Anda bisa merge pekerjaan tak selesai ke branch utama dengan aman. Jalur kode tak lengkap dibungkus dalam flag yang dimatikan, sehingga main selalu bisa di-build dan di-deploy sementara fitur tetap tersembunyi. Ini berarti Anda bisa berintegrasi dalam kenaikan harian kecil alih-alih memelihara branch fitur berumur panjang yang menyimpang dari trunk.
Kill switch adalah operational toggle yang membungkus dependensi berisiko — penyedia pembayaran, query berat, API pihak ketiga tak terpercaya — yang default menyala tapi bisa dimatikan seketika. Saat insiden, on-call menonaktifkan jalur yang gagal dalam hitungan detik alih-alih menulis dan men-deploy revert kode, mengubah keadaan darurat menjadi perbaikan berprioritas rendah.
Beri setiap flag pemilik dan kedaluwarsa saat pembuatan. Begitu release toggle mencapai rollout 100 persen dan stabil, buka tiket pembersihan dan targetkan menghapusnya dalam sekitar 30 hari, menghapus baik definisi flag maupun cabang kode mati. Jalankan audit triwulanan dan biarkan CI memperingatkan pada flag kedaluwarsa agar utang tidak menumpuk diam-diam.
OpenFeature adalah proyek incubating CNCF yang menyediakan spesifikasi netral-vendor dan SDK, memberi Anda API standar di depan backend flag apa pun seperti LaunchDarkly, Unleash, atau penyimpanan self-hosted. Menulis kode terhadapnya sejak hari pertama hampir tidak berbiaya dan membuat Anda bisa mengganti penyedia kemudian sebagai perubahan konfigurasi alih-alih penulisan ulang, menghindari lock-in di tingkat kode.

Foto oleh Hannes Grobe via Wikimedia Commons (CC BY-SA 4.0)
Ringkasan Utama
Trunk-based development berarti semua orang commit ke satu branch utama lewat branch berumur pendek yang di-merge dalam sehari-dua hari. Feature flag membuatnya aman: memisahkan deploy dari release, sehingga kode setengah jadi tetap bisa dikirim tersembunyi di balik flag. Flag juga menjadi kill switch. Tantangannya adalah kebersihan — setiap flag butuh pemilik, tanggal kedaluwarsa, dan tiket pembersihan.
Branch fitur berumur panjang adalah tempat kecepatan mati. Anda membuat branch dari main, bekerja tiga minggu, dan saat Anda membuka pull request trunk sudah bergerak seribu commit di depan Anda. Merge-nya jadi pertarungan berdarah. Saya pernah kehilangan sore penuh gara-gara konflik yang muncul hanya karena dua branch terpisah terlalu lama — kode yang masing-masing benar tapi secara kolektif tidak kompatibel.
Trunk-based development membalik model itu: integrasikan terus-menerus, dalam kenaikan kecil, agar konflik tidak pernah tumbuh lebih dari beberapa baris. Tapi itu memunculkan pertanyaan jelas — kalau saya merge pekerjaan yang belum selesai ke main setiap hari, bagaimana saya mengirim main ke produksi tanpa membuka fitur setengah jadi? Jawabannya adalah feature flag, dan disiplin di sekitarnya yang membedakan tim yang menyukai trunk-based development dari tim yang diam-diam meninggalkannya.
Ide paling berguna di sini adalah memisahkan dua peristiwa yang biasanya dicampur orang. Deploy adalah tindakan teknis: biner baru berjalan di server Anda. Release adalah tindakan produk: pengguna nyata sekarang bisa melihat fitur itu. Pada workflow branch tradisional keduanya terjadi pada saat yang sama, itulah kenapa deploy terasa menakutkan — setiap push ke produksi juga sebuah peluncuran. Feature flag memisahkan keduanya. Anda men-deploy kode puluhan kali sehari, gelap, sementara release adalah keputusan terpisah yang dibuat kemudian dengan membalik flag, sering oleh product manager, bukan engineer.
Begitu deploy dan release terpisah, seluruh profil risiko berubah. Deploy yang hanya mengirim kode tidur tidak bisa merusak fitur yang dilihat pengguna, karena tidak ada kode itu yang berjalan. Rollback berhenti berarti revert dan deploy ulang; ia berarti matikan flag — perubahan konfigurasi yang menyebar dalam hitungan detik alih-alih siklus build-and-ship yang butuh menit. Perbedaan kecepatan itulah inti keseluruhannya.
// A release toggle wrapping an incomplete feature.
// This code is on main and deployed to prod — but dark.
if (await flags.isEnabled('checkout-v2', { userId: user.id })) {
return renderCheckoutV2(cart);
}
return renderCheckoutLegacy(cart); // still the default for everyoneBagian branch dari trunk-based development lebih sederhana dari yang ditakutkan orang. Anda tetap memakai branch — Anda cuma menjaganya hidup selama beberapa jam atau sehari, bukan berminggu-minggu. Buat branch, lakukan perubahan yang fokus, buka pull request kecil, minta review, merge. Trik yang membuat ini berhasil untuk fitur besar adalah Anda tidak menunggu seluruh fitur selesai. Anda merge satu irisan vertikal di balik flag yang mati, lalu irisan berikutnya, lalu berikutnya. Main selalu bisa di-build, selalu lulus tes, selalu bisa di-deploy — fiturnya cuma tidak terlihat sampai flag mengizinkan.
Pasangkan branch berumur pendek dengan merge queue. Ia me-rebase dan menguji setiap pull request terhadap tip terbaru main sebelum merge, sehingga Anda tidak pernah mendaratkan perubahan yang lulus CI melawan trunk basi tapi rusak melawan trunk hari ini. Inilah yang menjaga main tetap hijau saat belasan orang berintegrasi setiap hari.
Kesalahan umum adalah memperlakukan setiap flag secara identik. Taksonomi yang dipopulerkan Pete Hodgson dan diadopsi luas di industri membagi flag berdasarkan berapa lama seharusnya ia hidup dan seberapa dinamis ia perlu. Release toggle ada hanya untuk menyembunyikan pekerjaan tak selesai dan harus mati dalam hitungan hari. Operational toggle — kill switch — mungkin hidup berbulan-bulan sebagai katup pengaman. Mencampur model mental ini membuat Anda berakhir dengan release toggle berumur lima tahun yang tak ada yang berani menghapus.
| Jenis flag | Tujuan | Umur |
|---|---|---|
| Release toggle | Sembunyikan pekerjaan tak selesai yang di-merge ke trunk | Hari hingga minggu |
| Operational (kill switch) | Nonaktifkan subsistem cepat saat insiden | Bulan, berumur panjang |
| Eksperimen | Uji A/B — arahkan kohort ke varian | Selama eksperimen |
| Permission | Batasi fitur per paket, peran, atau hak | Berumur panjang, bagian produk |
Operational toggle adalah yang akan saya perjuangkan untuk dipertahankan bahkan di basis kode yang alergi flag. Bungkus dependensi berisiko apa pun — penyedia pembayaran baru, query laporan mahal, API pihak ketiga yang tidak sepenuhnya Anda percaya — dalam flag yang default menyala tapi bisa dimatikan seketika. Saat dependensi itu mulai timeout jam 2 pagi, Anda tidak membangunkan engineer untuk menulis revert. On-call membalik switch, sistem melepas jalur yang gagal, dan Anda debug pagi harinya. Kill switch mengubah insiden panggil-orang-sekarang menjadi lihat-nanti.
Flag adalah percabangan runtime, artinya kedua sisi harus benar-benar berfungsi. Jalur mati yang tak teruji adalah jebakan klasik: kill switch yang Anda balik saat insiden mengarahkan trafik ke kode yang belum berjalan di produksi berbulan-bulan. Uji kedua keadaan di CI, dan latih kill switch Anda secara terjadwal agar Anda tahu ia masih berfungsi sebelum Anda membutuhkannya.
Setiap feature flag adalah sebuah kondisional, dan setiap kondisional yang tak pernah Anda hapus adalah utang teknis permanen. Basis kode dengan ratusan flag basi menjadi mimpi buruk kombinatorial: jumlah keadaan yang mungkin berlipat ganda dengan setiap flag, dan tidak ada suite tes yang mencakup semuanya. LaunchDarkly dan Unleash keduanya membangun seluruh permukaan produk seputar siklus hidup flag justru karena tim buruk dalam pembersihan. Perbaikannya bukan kepahlawanan — ia memperlakukan penghapusan sebagai bagian dari menyelesaikan fitur, bukan proyek suatu-hari-nanti.
Di sisi tooling, OpenFeature layak diketahui. Ia adalah proyek incubating CNCF — spesifikasi netral-vendor dan sekumpulan SDK yang menaruh API standar di depan backend flag apa pun yang Anda pakai, entah itu LaunchDarkly, Unleash, GO Feature Flag, atau penyimpanan self-hosted. Anda menulis kode terhadap antarmuka OpenFeature sekali, dan mengganti penyedia nanti menjadi perubahan konfigurasi alih-alih penulisan ulang. Untuk setup self-hosted, portabilitas itu persis jenis asuransi anti-lock-in yang saya inginkan.
Adopsi OpenFeature bahkan jika Anda mulai dengan satu penyedia. Menulis kode terhadap API netral-vendor sejak hari pertama hampir tidak berbiaya dan memberi Anda kebebasan bermigrasi backend flag kemudian tanpa menyentuh logika bisnis — yang penting saat cerita harga atau self-hosting hari ini berubah.
Anda tidak butuh platform untuk memulai. Mulai dengan satu release toggle pada fitur berikutnya yang jika tidak akan duduk di branch panjang — boolean yang dibaca dari config sudah cukup untuk membuktikan workflow-nya. Tambahkan kill switch ke dependensi paling berisiko Anda. Tuliskan pemilik dan kedaluwarsa di deskripsi pull request. Begitu fitur sepenuhnya live, hapus flag di sprint yang sama. Lakukan itu tiga atau empat kali dan trunk-based development berhenti menjadi metodologi menakutkan dan menjadi sekadar cara Anda bekerja: main selalu hijau, deploy membosankan, dan release adalah keputusan yang Anda buat dengan sengaja.