Feature Flags em Python Puro: O Sistema Que Ativa e Desativa Funcionalidades em Produção Sem Deploy, Sem Downtime e Sem Depender de LaunchDarkly
Você já passou por isso: o time pediu uma feature nova, você fez o deploy na sexta-feira à tarde, e na segunda de manhã o CEO liga perguntando por que o botão de checkout sumiu para metade dos usuários. A resposta? “Ah, é que a gente esqueceu de avisar que isso era só para teste interno.”
Feature flags não são luxo de startup com grana pra pagar LaunchDarkly. São uma necessidade básica de qualquer sistema que evolui em produção. E a melhor parte? Você pode implementar um sistema completo em Python puro, sem dependências externas, em menos de 200 linhas de código.
Neste post, vou te mostrar como construir um sistema de feature flags que suporta:
- Toggles booleanos simples
- Rollout gradual por porcentagem
- Segmentação por usuário (A/B testing)
- Persistência em arquivo JSON
- API REST para controle remoto
Tudo isso com código testado, pronto pra copiar e usar hoje.
O Problema Que Feature Flags Resolvem
Imagine que você precisa lançar uma nova funcionalidade, mas quer testar primeiro com 5% dos usuários. Sem feature flags, você tem duas opções:
- Branch de longa duração — Você mantém o código novo em uma branch separada, rebaseando toda semana, até “estar pronto”. Resultado: merge conflicts infinitos e deploy arriscado.
- Deploy parcial — Você deploya o código novo, mas com
ifhardcoded checando variável de ambiente. Resultado: código sujo, difícil de remover depois, e zero controle granular.
Feature flags resolvem isso separando deploy de release. Você deploya o código novo (desativado) quando quiser, e ativa a feature gradualmente via configuração externa, sem novo deploy.
A Implementação: Feature Flag Engine em Python Puro
Vamos construir um sistema completo. O código abaixo é uma classe FeatureFlagEngine que carrega configurações de um arquivo JSON e fornece métodos para checar se uma feature está ativa para um determinado usuário.
import json
import hashlib
import threading
from typing import Dict, Optional, Any
from pathlib import Path
class FeatureFlagEngine:
# Sistema de Feature Flags em Python puro
def __init__(self, config_path="feature_flags.json"):
self.config_path = Path(config_path)
self.flags = {}
self._lock = threading.Lock()
self._load_config()
def _load_config(self):
if self.config_path.exists():
with open(self.config_path, 'r', encoding='utf-8') as f:
self.flags = json.load(f)
else:
self.flags = {}
self._save_config()
def _save_config(self):
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(self.flags, f, indent=2, ensure_ascii=False)
def reload(self):
with self._lock:
self._load_config()
def is_enabled(self, flag_name, user_id=None):
# Verifica se uma feature flag está ativa
if flag_name not in self.flags:
return False
flag_config = self.flags[flag_name]
if not flag_config.get('enabled', False):
return False
# Flag ativada para todos (sem restrições)
if (flag_config.get('rollout_percentage') is None
and not flag_config.get('target_users')):
return True
# Rollout por porcentagem
if flag_config.get('rollout_percentage') is not None and user_id:
percentage = flag_config['rollout_percentage']
return self._user_in_percentage(user_id, percentage)
# Segmentação por usuários específicos
if flag_config.get('target_users') and user_id:
return user_id in flag_config['target_users']
return False
def _user_in_percentage(self, user_id, percentage):
# Hash determinístico: mesmo usuário sempre cai no mesmo grupo
hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
user_percentile = (hash_value % 10000) / 100.0 # 0.00 a 99.99
return user_percentile < percentage
def enable_flag(self, flag_name, rollout_percentage=None, target_users=None):
# Ativa uma feature flag com opções de rollout e segmentação
with self._lock:
self.flags[flag_name] = {
'enabled': True,
'rollout_percentage': rollout_percentage,
'target_users': target_users or []
}
self._save_config()
def disable_flag(self, flag_name):
# Desativa uma feature flag
with self._lock:
if flag_name in self.flags:
self.flags[flag_name]['enabled'] = False
self._save_config()
def set_rollout(self, flag_name, percentage):
# Define a porcentagem de rollout para uma flag ativa
with self._lock:
if flag_name in self.flags:
self.flags[flag_name]['rollout_percentage'] = percentage
self._save_config()
def list_flags(self):
# Lista todas as feature flags
return self.flags.copy()
Como Usar na Prática
Exemplo 1: Toggle Simples
engine = FeatureFlagEngine()
# Ativa uma feature para todos os usuários
engine.enable_flag('novo_checkout')
# Usa no código
def processar_checkout(user_id, dados):
if engine.is_enabled('novo_checkout', user_id):
return novo_processamento_checkout(dados)
else:
return processamento_checkout_legacy(dados)
Exemplo 2: Rollout Gradual (5% → 100%)
# Ativa para 5% dos usuários
engine.enable_flag('recomendacoes_ia', rollout_percentage=5.0)
# Aumenta gradualmente ao longo dos dias
engine.set_rollout('recomendacoes_ia', 10.0) # 10%
engine.set_rollout('recomendacoes_ia', 25.0) # 25%
engine.set_rollout('recomendacoes_ia', 100.0) # 100% (todos)
Exemplo 3: Segmentação por Usuários Específicos (Beta Testers)
# Ativa só para usuários beta
engine.enable_flag(
'dashboard_v2',
target_users=['user_123', 'user_456', 'user_789']
)
# Ou combina: 10% dos usuários + beta testers sempre inclusos
engine.enable_flag(
'dashboard_v2',
rollout_percentage=10.0,
target_users=['user_123', 'user_456']
)
API REST para Controle Remoto
Agora a parte mais legal: uma API REST simples para controlar as flags sem precisar editar o arquivo JSON manualmente.
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class FeatureFlagAPI(BaseHTTPRequestHandler):
engine = None # Configurado externamente
def do_GET(self):
if self.path == '/flags':
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
response = json.dumps(self.engine.list_flags())
self.wfile.write(response.encode())
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length).decode()
data = json.loads(body)
if self.path == '/flags/enable':
flag_name = data.get('flag_name')
rollout = data.get('rollout_percentage')
target_users = data.get('target_users', [])
self.engine.enable_flag(flag_name, rollout, target_users)
self.send_response(200)
self.end_headers()
self.wfile.write(b'{"status": "enabled"}')
elif self.path == '/flags/disable':
flag_name = data.get('flag_name')
self.engine.disable_flag(flag_name)
self.send_response(200)
self.end_headers()
self.wfile.write(b'{"status": "disabled"}')
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass # Silencia logs padrão
def start_api_server(engine, port=8080):
FeatureFlagAPI.engine = engine
server = HTTPServer(('localhost', port), FeatureFlagAPI)
print(f"API rodando em http://localhost:{port}")
server.serve_forever()
# Uso:
# engine = FeatureFlagEngine()
# start_api_server(engine)
Controle via curl:
# Listar todas as flags
curl http://localhost:8080/flags
# Ativar para 10% dos usuários
curl -X POST http://localhost:8080/flags/enable \
-H "Content-Type: application/json" \
-d '{"flag_name": "novo_checkout", "rollout_percentage": 10.0}'
# Desativar uma flag
curl -X POST http://localhost:8080/flags/disable \
-H "Content-Type: application/json" \
-d '{"flag_name": "novo_checkout"}'
🔥 O Perrengue do Olivetto: O Deploy Que Quebrou o Checkout de 50% dos Usuários
Em 2023, eu estava trabalhando em um e-commerce que decidiu refatorar o fluxo de checkout. A equipe passou três meses desenvolvendo, testando em staging, e finalmente fez o deploy numa sexta-feira às 16h. “Vai dar bom”, pensamos.
Não deu.
O problema? O novo checkout dependia de uma API de cálculo de frete que estava com instabilidade intermitente. Para alguns usuários, o cálculo funcionava; para outros, retornava erro 503. Resultado: 50% dos usuários não conseguiam finalizar a compra.
O time de suporte começou a receber chamados às 16h30. Às 17h, já tínhamos perdido R$ 12.000 em vendas. A solução? Reverter o deploy às pressas, o que levou 40 minutos de downtime total.
O que teria salvado o dia?
Se tivéssemos implementado feature flags, o código novo teria sido deployado desativado. Teríamos ativado gradualmente: 1% dos usuários na sexta à tarde, 5% no sábado, 20% no domingo. Quando os erros de frete aparecessem nos primeiros 1%, teríamos desativado a flag em 30 segundos — sem downtime, sem revert de deploy, sem perda de vendas.
Lição aprendida: feature flags não são sobre tecnologia, são sobre mitigação de risco.
Boas Práticas (Que Eu Aprendi na Marra)
1. Nomeie flags de forma descritiva — novo_checkout_v2 é melhor que flag_123. Você vai agradecer daqui a 6 meses.
2. Documente cada flag — Mantenha um arquivo FLAGS.md explicando o que cada flag faz, quando foi criada, e quem é o responsável.
3. Remova flags antigas — Feature flags são temporárias. Se uma flag está ativa há mais de 3 meses, é hora de remover o código antigo e a flag.
4. Monitore flags ativas — Use logs para rastrear quantas vezes cada flag é checada. Isso ajuda a identificar flags órfãs.
5. Não use flags para tudo — Feature flags são para funcionalidades. Não use para configuração de ambiente (use variáveis de ambiente) ou segredos (use um cofre de segredos).
Conclusão
Feature flags são uma das ferramentas mais subestimadas no arsenal de um desenvolvedor. Elas te dão controle granular sobre releases, permitem testes A/B sem infraestrutura complexa, e reduzem drasticamente o risco de deploys.
A implementação que mostrei aqui é simples, mas funcional. Você pode estender com:
- Integração com banco de dados (Redis, PostgreSQL)
- Cache em memória para performance
- Auditoria de mudanças (quem ativou/desativou cada flag)
- Interface web para gerenciamento visual
Agora me conta: qual feature você gostaria de lançar gradualmente no seu sistema atual? Responde aí nos comentários que eu quero saber como vocês estão usando (ou não usando) feature flags.
