06 / 08
Feature flag ve yayın stratejisi
Deploy ile yayını ayırmak, flag türleri ve taşıma maliyeti, Flags SDK ile sunucuda değerlendirilen bir flag, kullanıcıyı sabit kovaya yerleştirerek yüzdeli açılış, canary ve blue-green yayın, geri alma ve flag'leri test etmek.
Deploy Etmek ile Yayınlamak Farklıdır
Deploy, yeni kodun sunucuya gitmesidir. Yayın (release), kullanıcının yeni özelliği görmesidir. Çoğu projede ikisi aynı andadır: kod canlıya çıktığı an herkes yeni özelliği görür ve bir sorun çıkarsa tek çare geri deploy etmektir.
Feature flag (özellik bayrağı) ikisini ayırır. Kod canlıdadır ama özellik kapalıdır; kimin ne zaman göreceğine bir yapılandırma karar verir. Sorun çıkarsa flag kapatılır, deploy gerekmez.
Flag Türleri
Pete Hodgson'ın martinfowler.com'da yayımlanan yazısı flag'leri ne kadar yaşadıklarına ve ne kadar dinamik olduklarına göre dörde ayırır:1
| Tür | Amaç | Ömür |
|---|---|---|
| Yayın (release) | Yarım kalmış ya da yeni bir özelliği kapalı tutarak canlıya çıkarmak | Günler, birkaç hafta |
| Deney (experiment) | A/B testi; kullanıcıları gruplara ayırıp davranışı karşılaştırmak | Anlamlı sonuç alınana kadar |
| Operasyon (ops) | Yük altında ağır bir özelliği kapatmak; "acil durum anahtarı" | Kısa ya da kalıcı |
| İzin (permissioning) | Özelliği yalnızca belirli kullanıcılara açmak (beta, premium, iç ekip) | Uzun, çoğu zaman kalıcı |
Hodgson'ın en önemli uyarısı maliyetle ilgilidir: flag'ler bir envanterdir ve taşıma maliyeti vardır. Her flag kodda bir dal, test edilmesi gereken bir kombinasyon ve unutulursa kalıcı bir karmaşıklık demektir. Yayın flag'leri işini bitirince silinmelidir.1
Yap
Her flag'e bir sahip ve bir silinme tarihi verin.
Özellik yüzde 100'e ulaşıp bir süre sorunsuz çalıştıysa flag'i ve eski kodu silen pull request'i açın.
Flags SDK
Flags SDK, Next.js ekibinin açık kaynak flag kütüphanesidir; herhangi bir flag sağlayıcısıyla ya da kendi kurallarınızla çalışır.2
$ pnpm add flagsHer flag bir fonksiyon olarak tanımlanır:23
// lib/flags.ts
import "server-only";
import { flag } from "flags/next";
import { getEnv } from "./env";
import { isInRollout } from "./rollout";
import { SESSION_COOKIE } from "./session";
import { readSession } from "./store";
type Entities = { user?: { id: string } };
export const orderSummaryFlag = flag<boolean, Entities>({
key: "order-summary",
description: "Sipariş listesinin üstünde adet ve toplam tutar özeti",
defaultValue: false,
options: [
{ value: false, label: "Kapalı" },
{ value: true, label: "Açık" },
],
identify({ cookies }) {
const session = readSession(cookies.get(SESSION_COOKIE)?.value);
return session ? { user: { id: session.user.id } } : undefined;
},
decide({ entities }) {
if (!entities?.user) return false;
return isInRollout(entities.user.id, "order-summary", getEnv().ORDER_SUMMARY_ROLLOUT);
},
});keyflag'in kimliğidir.defaultValueflag değerlendirilemezse kullanılır. Varsayılanın tanımda olması her çağrıda aynı varsayılanı garanti eder.3identifykim için karar verildiğini bulur. Normalize edilmişheadersvecookiesalır; döndürdüğü varlıklardecide'a geçer.4decidene döneceğine karar verir.
Hodgson'ın yazısı karar noktasını (kodda flag'in sorulduğu yer) karar mantığından (flag'in neye göre açıldığı) ayırmayı önerir.1 Flags SDK'nın identify ve decide ayrımı tam olarak budur. Kural değişince çağıran kod değişmez.
Kullanımı:
// app/orders/page.tsx
async function OrdersSection() {
const user = await getCurrentUser();
const showSummary = await orderSummaryFlag();
return <OrdersPanel canCancel={user.role === "admin"} showSummary={showSummary} />;
}Her flag aynı biçimde çağrılır: await orderSummaryFlag(). Editörde "Tüm referansları bul" ile flag'in nerelerde kullanıldığı görünür; silme günü geldiğinde bu çok işe yarar.3
Neden Sunucuda?
Flags SDK flag'lerin sunucuda değerlendirilmesini savunur. Tarayıcıda değerlendirilen bir flag, değeri ağdan gelene kadar ya bir yükleniyor göstergesi ya da tahmini bir sürüm göstermek zorundadır; değer farklı çıkarsa sayfa kayar.5 Sunucuda değerlendirilen flag'de sunucu yalnızca doğru sürümü gönderir.
Bedeli: flag cookie okuduğu için sayfanın o kısmı isteğe bağlı (dinamik) olur. Bu projede flag zaten oturum okuyan, Suspense içindeki bir bileşende çağrılır (bkz. Veri çekme ve streaming). Statik kalması gereken pazarlama sayfaları için SDK'nın proxy ile önceden hesaplama (precompute) kalıpları vardır.5
Yüzdeli Açılış
"Kullanıcıların yüzde 10'una aç" demek kolaydır, doğru yapmak değil. Math.random() < 0.1 her istekte farklı sonuç verir: kullanıcı sayfayı yenileyince özellik kaybolur.
Doğrusu kullanıcıyı sabit bir kovaya yerleştirmektir. Kullanıcı kimliğinin özeti (hash) 0-99 arasında bir sayıya çevrilir; sayı yüzdeden küçükse kullanıcı içeridedir:
// lib/rollout.ts
export function rolloutBucket(id: string, salt: string): number {
let hash = 0x811c9dc5;
for (const char of `${salt}:${id}`) {
hash ^= char.codePointAt(0)!;
hash = Math.imul(hash, 0x01000193);
}
return (hash >>> 0) % 100;
}
export function isInRollout(id: string, salt: string, percentage: number): boolean {
return rolloutBucket(id, salt) < percentage;
}Bu yöntemin üç özelliği, projede birer testle doğrulanır:
Kalıcı: Aynı kullanıcı her istekte aynı kovaya düşer.
Dengeli: 10.000 kullanıcıda yüzde 30'luk açılış yaklaşık yüzde 30'u içerir.
Büyüyen: Yüzde 10'dan 30'a çıkınca önceden içeride olan kimse dışarıda kalmaz; çünkü kovası 10'dan küçük olan, 30'dan da küçüktür.
salt (tuz) her flag'e farklı bir dağılım verir. Tuz olmasaydı, her yeni özelliği ilk görenler hep aynı yüzde 10 olurdu.
İpucu
Projede yüzde, ORDER_SUMMARY_ROLLOUT ortam değişkeninden gelir; değiştirmek sunucunun yeniden başlatılmasını gerektirir. Gerçek projelerde bu değer bir flag sağlayıcısında (LaunchDarkly, Statsig, GrowthBook ya da Vercel Flags gibi) tutulur ve anında değişir. Flags SDK'nın sağlayıcı adaptörleri bunun içindir; decide'ın içi değişir, çağıran kod değişmez.2
Canary ve Blue-Green
Flag'ler özellik düzeyinde çalışır. Bir de deploy düzeyinde stratejiler vardır:
Canary yayın. Danilo Sato'nun martinfowler.com'daki tarifine göre yeni sürüm önce trafiğin küçük bir kısmına verilir. Sorun yoksa pay kademeli olarak artırılır, sorun çıkarsa trafik eski sürüme döner.6 Adını madencilerin zehirli gazı erken fark etmek için ocağa götürdüğü kanaryadan alır.
Blue-green deploy. Martin Fowler'ın anlattığı yöntemde iki aynı canlı ortam vardır. Biri trafiği alırken yeni sürüm diğerine kurulur ve test edilir; sonra yönlendirici trafiği yeni ortama çevirir. Geri almak için yönlendiriciyi eski ortama çevirmek yeterlidir.7
| Strateji | Ne kontrol edilir | Geri alma |
|---|---|---|
| Feature flag | Tek bir özellik, kullanıcı bazında | Flag'i kapatmak, saniyeler |
| Canary | Tüm sürüm, trafik yüzdesiyle | Trafiği eski sürüme çevirmek |
| Blue-green | Tüm sürüm, tek seferde | Yönlendiriciyi eski ortama çevirmek |
Vercel gibi platformlar her deploy'u ayrı tutar ve canlıyı eski bir deploy'a anında döndürmeye izin verir. Kendi altyapınızda bu, eski imajı yeniden başlatmak demektir; imajları commit SHA'sıyla etiketlemenin bir sebebi de budur (bkz. CI/CD).
Uyarı
Kademeli yayın sırasında eski ve yeni sürüm aynı anda çalışır. Veritabanı şeması değişiyorsa yeni sürüm eski şemayla, eski sürüm yeni şemayla da çalışabilmelidir. Next.js tarafındaki karşılığı sürüm farkıdır (version skew); deploymentId bunun için vardır (bkz. Deploy seçenekleri).
Açılışı İzlemek
Yüzdeyi artırmadan önce sorun: hata oranı arttı mı? Gecikme değişti mi? Hata izleme ve loglarda flag'in açık olduğu kullanıcıları ayırabilmek için flag değerini kayıtlara ekleyin:
logger.info({ event: "orders_viewed", userId: user.id, orderSummary: showSummary }, "Siparişler görüntülendi");Tipik bir açılış takvimi: iç ekip, yüzde 1, yüzde 10, yüzde 50, yüzde 100. Her adımda metriklere bakın; bir sonraki adıma geçmek bir karardır, otomatik değildir.
Flag'leri Test Etmek
Flag'li bir özelliğin iki hali vardır ve ikisi de test edilmelidir.
Saf fonksiyon olarak. rolloutBucket ve isInRollout tarayıcı gerektirmez; projede tests/rollout.spec.ts bunları doğrular. Gerçek bir projede bu testler Vitest gibi bir birim test aracında daha hızlı çalışır.
Uçtan uca. Projede Playwright sunucuyu ORDER_SUMMARY_ROLLOUT=100 ile başlatır ve özetin göründüğünü doğrular (bkz. Playwright):
// playwright.config.ts
webServer: {
command: "npm run build && npm run start",
url: "http://localhost:3000/login",
env: { PORT: "3000", APP_ENV: "local", APP_VERSION: "test", ORDER_SUMMARY_ROLLOUT: "100" },
},Flags SDK test başına değer vermek için şifreli bir override cookie'si de destekler. Bunun için FLAGS_SECRET gerekir; aynı secret Vercel Toolbar'ın Flags Explorer'ı ile kendi oturumunuzda flag değiştirmeye de yarar.8
Kendin Dene
Projenize bir yayın flag'i ekleyin. Kapalıyken eski, açıkken yeni bileşeni gösterin.
rolloutBucket'ı 10.000 kimlikle çalıştırın ve yüzde 10, 30, 50 için dağılımı ölçün.Tuzu çıkarın ve iki farklı flag için içeride kalan kullanıcıların aynı olduğunu görün.
Projenizdeki flag'leri listeleyin. Hangisi yüzde 100'de ve hâlâ kodda?
Detaylı Öğrenmek İçin
Yazı: Pete Hodgson, "Feature Toggles (aka Feature Flags)"; flag türleri, karar noktası ve taşıma maliyeti.1
Resmi doküman: Flags SDK, "Quickstart" ve "Flags as Code"; Next.js'te flag tanımlamak.23
Yazı: Danilo Sato, "Canary Release"; kademeli trafikle yayın.6
Kaynaklar
: — 9 Ekim 2017. Yayın, deney, operasyon ve izin flag'leri, karar noktasını mantıktan ayırmak, taşıma maliyeti.
Next.js'te kurulum, flag tanımlamak ve App Router'da kullanmak.
Flag'lerin fonksiyon olması, tanımdaki varsayılan değer ve bağlamın tanımda kurulması.
`identify`, tipli varlıklar, normalize edilmiş headers ve cookies.
Sunucuda değerlendirmenin gerekçesi ve statik sayfa kalıpları.
: — 25 Haziran 2014. Yeni sürümü trafiğin küçük bir kısmıyla denemek.
: — 1 Mart 2010. İki canlı ortam ve yönlendiriciyle geçiş ve geri alma.
Playwright ile override cookie'si ve `FLAGS_SECRET`.
Son güncelleme tarihi: 22 Ekim 2026
