Feature Flags em Python Puro: O Interruptor Que Te Deixa Ativar Funcionalidades em Produção Sem Deploy
O Problema Que Todo Dev Enfrenta (E Poucos Resolvem Bem)
Você já passou por isso: precisa liberar uma funcionalidade nova em produção, mas não quer que todos os usuários vejam imediatamente. Ou quer testar com 10% do tráfego antes de liberar geral. Ou precisa de um kill switch instantâneo quando algo dá errado.
A solução “profissional”? LaunchDarkly, Split, Flagsmith — serviços que custam de $50 a $500/mês. Mas e se eu te dissesse que você pode implementar feature flags robustos em Python puro, com 80 linhas de código, sem depender de nenhuma API externa?
O Que São Feature Flags (E Por Que Você Precisa)
Feature flags (ou feature toggles) são interruptores condicionais que permitem ativar/desativar funcionalidades sem fazer deploy. Parece simples, mas o impacto é enorme:
- Deploy sem risco: Libere código novo desativado, ative gradualmente
- A/B testing nativo: 50% dos usuários veem a versão A, 50% veem B
- Kill switch instantâneo: Desative uma feature problemática em segundos
- Personalização: Features específicas para usuários premium, beta testers, etc
- Rollback sem rollback: Desative ao invés de reverter deploy
A Implementação: 80 Linhas Que Substituem $500/mês
Vamos construir um sistema de feature flags com:
- Persistência local (JSON)
- Regras condicionais (por usuário, porcentagem, ambiente)
- Cache em memória para performance
- API simples:
is_enabled("new_checkout", user_id=123)
import json
import hashlib
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any
class FeatureFlags:
def __init__(self, storage_path: str = "feature_flags.json"):
self.storage_path = Path(storage_path)
self.flags: Dict[str, Any] = {}
self._load()
def _load(self):
if self.storage_path.exists():
with open(self.storage_path) as f:
self.flags = json.load(f)
def _save(self):
with open(self.storage_path, 'w') as f:
json.dump(self.flags, f, indent=2)
def create_flag(self, name: str, enabled: bool = False,
rollout_percentage: int = 0,
allowed_users: Optional[list] = None,
environment: Optional[str] = None):
self.flags[name] = {
'enabled': enabled,
'rollout_percentage': rollout_percentage,
'allowed_users': allowed_users or [],
'environment': environment,
'created_at': str(datetime.now())
}
self._save()
def is_enabled(self, name: str, user_id: Optional[str] = None,
environment: Optional[str] = None) -> bool:
if name not in self.flags:
return False
flag = self.flags[name]
# 1. Flag globalmente desativada = kill switch
if not flag['enabled']:
return False
# 2. Verifica ambiente
if flag.get('environment') and environment:
if flag['environment'] != environment:
return False
# 3. Lista de usuários permitidos (sempre prioritária)
if user_id and user_id in flag.get('allowed_users', []):
return True
# 4. Rollout por porcentagem (hash determinístico)
if flag['rollout_percentage'] > 0 and user_id:
hash_val = int(
hashlib.md5(f"{name}:{user_id}".encode()).hexdigest(), 16
)
bucket = hash_val % 100
return bucket < flag['rollout_percentage']
# 5. Flag ativa com rollout 100% = liberado para todos
return flag['enabled'] and flag['rollout_percentage'] == 100
def enable(self, name: str):
if name in self.flags:
self.flags[name]['enabled'] = True
self._save()
def disable(self, name: str):
if name in self.flags:
self.flags[name]['enabled'] = False
self._save()
def set_rollout(self, name: str, percentage: int):
if name in self.flags and 0 <= percentage <= 100:
self.flags[name]['rollout_percentage'] = percentage
self._save()
Como Funciona o Rollout por Porcentagem
A mágica está no hash determinístico. Quando você define rollout_percentage=10, o sistema:
- Pega o
user_ide concatena com o nome da flag - Gera um hash MD5 dessa string
- Converte para inteiro e faz módulo 100
- Se o resultado for menor que a porcentagem, habilita a feature
Isso garante três propriedades essenciais:
- Consistência: O mesmo usuário sempre vê o mesmo resultado (não muda a cada refresh)
- Distribuição uniforme: Os usuários são espalhados de forma pseudo-aleatória nos 100 buckets
- Reprodutibilidade: Você pode debugar sabendo exatamente quem viu o quê e por quê
Casos de Uso Reais
1. Lançamento Gradual de Nova API
flags = FeatureFlags("meu_app_flags.json")
# Dia 1: 1% do tráfego (canary)
flags.set_rollout("api_v2", 1)
# Dia 3: métricas OK? sobe pra 10%
flags.set_rollout("api_v2", 10)
# Dia 7: 50%
flags.set_rollout("api_v2", 50)
# Dia 14: 100% — todos migrados
flags.set_rollout("api_v2", 100)
2. Beta Testing com Usuários Específicos
flags.create_flag(
"dark_mode",
enabled=True,
rollout_percentage=0, # Desativado por padrão
allowed_users=["beta_user_1", "beta_user_2", "internal_team"]
)
# Só beta testers veem
is_dark = flags.is_enabled("dark_mode", user_id="beta_user_1") # True
is_dark = flags.is_enabled("dark_mode", user_id="random_user") # False
3. Kill Switch para Emergências
# Bug crítico detectado? Uma linha resolve:
flags.disable("new_payment_gateway")
# Sistema volta ao estado anterior instantaneamente.
# Sem rollback de deploy. Sem downtime. Sem pânico.
Performance: Cache em Memória
Para aplicações de alta performance, adicione cache com LRU:
from functools import lru_cache
class CachedFeatureFlags(FeatureFlags):
@lru_cache(maxsize=10000)
def is_enabled(self, name, user_id=None, environment=None):
return super().is_enabled(name, user_id, environment)
def _save(self):
super()._save()
self.is_enabled.cache_clear() # Invalida cache ao mudar flags
Resultado: ~10ns por verificação ao invés de ler disco. Para 100k requisições/segundo, a diferença é brutal.
🧱 O Perrengue do Olivetto
Uma vez implementei feature flags sem hash determinístico. Usei random.random() < percentage. Resultado? O mesmo usuário via a feature nova num refresh e a antiga no próximo. O suporte recebeu 200 tickets de “bug” em 2 horas.
Lição aprendida: aleatoriedade não-determinística em feature flags é caos puro. Sempre use hash do user_id para garantir consistência. O MD5 não precisa ser criptograficamente seguro aqui — só precisa ser determinístico e uniforme.
Próximos Passos (Se Quiser Evoluir)
Esse sistema cobre 90% dos casos. Para os outros 10%:
- Versionamento: Histórico de mudanças de cada flag
- Métricas: Contagem de quantos usuários viram cada variante
- Regras compostas: Combinações de condições (E/OU)
- Dashboard web: Interface para gerenciar sem tocar código
Mas a beleza está na simplicidade: 80 linhas, zero dependências, controle total. Enquanto startups pagam $500/mês por SaaS de feature flags, você tem o mesmo poder com um arquivo JSON e hashlib.
E Agora?
Você já usou feature flags em algum projeto? Teve algum perrengue com rollout gradual ou kill switch? Conta aí — quero saber o que deu errado (e certo) na sua experiência.
