Özellik Bayrakları (Feature Flags)
Özellik bayrakları (feature toggles), ekiplerin kod değişikliklerini hemen tüm kullanıcılara sunmadan dağıtmasına olanak tanır. Bu yaklaşım, kademeli dağıtımlar, A/B testleri ve uygulama mağazası güncellemesi gerektirmeden hızlı geri alma (rollback) gibi avantajlar sağlar.
Feature Flag Mimarisi
Bayrak Türleri
- Kill Switches: Acil durumlarda özellikleri devre dışı bırakma
- Release Flags: Özellikleri kademeli olarak açma
- Permission Flags: Rol tabanlı erişim
- Experiment Flags: A/B testleri
- Operational Flags: Performans ve operasyonel kontroller
Uygulama Stratejileri
1. Konfigürasyon Tabanlı Bayraklar
typescript
// Feature flag konfigürasyonu
interface FeatureFlag {
key: string;
enabled: boolean;
rolloutPercentage: number;
targetAudience: string[];
conditions: FlagCondition[];
metadata: Record<string, any>;
}
interface FlagCondition {
type: 'user_id' | 'app_version' | 'device_type' | 'location';
operator: 'equals' | 'contains' | 'greater_than' | 'in_list';
value: any;
}
2. Çalışma Zamanı Değerlendirme Motoru
typescript
class FeatureFlagEngine {
private flags: Map<string, FeatureFlag> = new Map();
private userContext: UserContext;
constructor(userContext: UserContext) {
this.userContext = userContext;
}
isEnabled(flagKey: string): boolean {
const flag = this.flags.get(flagKey);
if (!flag) return false;
return this.evaluateFlag(flag);
}
private evaluateFlag(flag: FeatureFlag): boolean {
if (!flag.enabled) return false;
if (!this.isInRollout(flag.rolloutPercentage)) return false;
return this.evaluateConditions(flag.conditions);
}
private isInRollout(percentage: number): boolean {
const userId = this.userContext.userId;
const hash = this.hashUserId(userId);
return (hash % 100) < percentage;
}
private evaluateConditions(conditions: FlagCondition[]): boolean {
return conditions.every(condition => {
switch (condition.type) {
case 'user_id':
return this.evaluateUserCondition(condition);
case 'app_version':
return this.evaluateVersionCondition(condition);
case 'device_type':
return this.evaluateDeviceCondition(condition);
case 'location':
return this.evaluateLocationCondition(condition);
default:
return false;
}
});
}
}
Platforma Özel Uygulamalar
Android (Kotlin)
kotlin
// Feature Flag Manager
class FeatureFlagManager private constructor(
private val context: Context,
private val remoteConfigService: RemoteConfigService
) {
companion object {
@Volatile
private var INSTANCE: FeatureFlagManager? = null
fun getInstance(context: Context, remoteConfigService: RemoteConfigService): FeatureFlagManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: FeatureFlagManager(context, remoteConfigService).also { INSTANCE = it }
}
}
}
private val localCache = FeatureFlagCache(context)
private val evaluationEngine = FlagEvaluationEngine()
suspend fun isFeatureEnabled(flagKey: String): Boolean {
return try {
val flag = getFlag(flagKey)
evaluationEngine.evaluate(flag, getUserContext())
} catch (e: Exception) {
Log.e("FeatureFlags", "Error evaluating flag $flagKey", e)
getDefaultValue(flagKey)
}
}
suspend fun getFeatureVariant(flagKey: String): String {
val flag = getFlag(flagKey)
return evaluationEngine.getVariant(flag, getUserContext())
}
private suspend fun getFlag(flagKey: String): FeatureFlag {
// Önce yerel önbellek
localCache.getFlag(flagKey)?.let { return it }
// Uzaktan al
val flag = remoteConfigService.getFlag(flagKey)
localCache.saveFlag(flag)
return flag
}
private fun getUserContext(): UserContext {
return UserContext(
userId = getCurrentUserId(),
deviceId = getDeviceId(),
appVersion = getAppVersion(),
deviceType = getDeviceType(),
location = getCurrentLocation()
)
}
}
React Native
typescript
// FeatureFlag Service
class FeatureFlagService {
private flags: Map<string, boolean> = new Map();
async initialize() {
try {
const response = await fetch('/api/feature-flags');
const flagsData = await response.json();
Object.entries(flagsData).forEach(([key, value]) => {
this.flags.set(key, value as boolean);
});
} catch (error) {
console.warn('Feature flags yüklenemedi, varsayılan değerler kullanılıyor');
}
}
isEnabled(flagName: string): boolean {
return this.flags.get(flagName) ?? false;
}
setOverride(flagName: string, value: boolean) {
this.flags.set(flagName, value);
}
}
Flutter
dart
// Feature Flag Manager
class FeatureFlagManager {
static final FeatureFlagManager _instance = FeatureFlagManager._internal();
factory FeatureFlagManager() => _instance;
FeatureFlagManager._internal();
final Map<String, bool> _flags = {};
Future<void> initialize() async {
try {
final response = await http.get(Uri.parse('/api/feature-flags'));
final Map<String, dynamic> data = json.decode(response.body);
data.forEach((key, value) {
_flags[key] = value as bool;
});
} catch (e) {
print('Feature flags yüklenemedi: $e');
}
}
bool isEnabled(String flagName) {
return _flags[flagName] ?? false;
}
void setLocalOverride(String flagName, bool value) {
_flags[flagName] = value;
}
}
iOS (Swift)
swift
// FeatureFlagManager.swift
class FeatureFlagManager {
static let shared = FeatureFlagManager()
private var flags: [String: Bool] = [:]
private init() {}
func initialize() async {
do {
guard let url = URL(string: "/api/feature-flags") else { return }
let (data, _) = try await URLSession.shared.data(from: url)
let flagsDict = try JSONSerialization.jsonObject(with: data) as? [String: Bool]
self.flags = flagsDict ?? [:]
} catch {
print("Feature flags yüklenemedi: \(error)")
}
}
func isEnabled(_ flagName: String) -> Bool {
return flags[flagName] ?? false
}
func setOverride(_ flagName: String, value: Bool) {
flags[flagName] = value
}
}
Backend Servisleri
Remote Config Servisleri
typescript
// Firebase Remote Config
import { getRemoteConfig, fetchAndActivate, getValue } from 'firebase/remote-config';
class FirebaseFeatureFlags {
private remoteConfig = getRemoteConfig();
async initialize() {
this.remoteConfig.settings = {
minimumFetchIntervalMillis: 3600000, // 1 saat
};
this.remoteConfig.defaultConfig = {
newCheckoutFlow: false,
premiumFeatures: false,
darkModeEnabled: true,
};
await fetchAndActivate(this.remoteConfig);
}
}
Gelişmiş Özellikler
- Kullanıcı Segmentasyonu: Belirli kullanıcı gruplarına özel bayraklar
- Gerçek Zamanlı Güncellemeler: Bayrak değişikliklerinin anında uygulamaya yansıması
- Analitik Entegrasyonu: Bayrak etkinliği ve kullanıcı davranışlarının izlenmesi
En İyi Uygulamalar
- Bayrakları kısa ömürlü tutun, gereksiz bayrakları temizleyin
- Kritik özellikler için kill switch kullanın
- Bayrak yönetimini merkezi dashboard ile yönetin
- Test ve prod ortamlarında farklı bayrak yapılandırmaları kullanın
- Kodda bayrak kontrollerini açık ve anlaşılır tutun
Feature flag mimarisi, mobil uygulamalarda güvenli, esnek ve hızlı dağıtım için modern bir yaklaşımdır. Doğru uygulandığında, hem geliştirme hem de operasyonel süreçlerde büyük avantaj sağlar.