Feature flags: entregue código com confiança antes de ligar a feature

1. Fundamentos das Feature Flags

Feature flags (ou toggles) são mecanismos que permitem ativar ou desativar funcionalidades em tempo de execução, sem necessidade de novo deploy. O propósito central é separar o momento de deploy (quando o código vai para produção) do momento de release (quando a funcionalidade fica disponível para os usuários).

Existem quatro tipos principais de flags:

  • Release toggle: controla liberação de novas features
  • Experiment toggle: usado em testes A/B e experimentos
  • Ops toggle: gerencia aspectos operacionais (manutenção, migrações)
  • Permission toggle: libera acesso baseado em permissões de usuário

O ciclo de vida de uma flag segue quatro estágios: criação, ativação controlada, remediação (caso necessário) e remoção definitiva.

2. Arquitetura e Armazenamento de Flags

A escolha da arquitetura de armazenamento impacta diretamente a latência e a confiabilidade do sistema.

Armazenamento centralizado (banco de dados relacional ou Redis) oferece consistência forte, mas pode introduzir latência em cada verificação. Armazenamento distribuído (arquivos de configuração locais) elimina latência de rede, mas dificulta atualizações em tempo real.

Exemplo de configuração local em JSON:

{
  "features": {
    "novo-checkout": {
      "enabled": false,
      "rules": {
        "percentage": 10,
        "countries": ["BR", "PT"]
      }
    },
    "dark-mode": {
      "enabled": true,
      "users": ["admin@exemplo.com"]
    }
  }
}

Para propagação, três estratégias são comuns:
- Polling: cliente consulta servidor em intervalos (ex: 30s)
- Push (WebSocket): servidor notifica mudanças instantaneamente
- Sidecar: processo auxiliar gerencia flags e expõe via API local

Segurança é crítica: apenas engenheiros seniores e SREs devem alterar flags em produção, com auditoria completa de cada mudança.

3. Implementação Técnica e Padrões de Código

A implementação mais básica usa estruturas condicionais:

if feature_flags.is_enabled("novo-checkout", user):
    return processar_checkout_v2(pedido)
else:
    return processar_checkout_v1(pedido)

Para serviços assíncronos (workers, filas), a injeção de dependência do provedor de flags é essencial:

class ProcessadorPedidos:
    def __init__(self, flag_provider):
        self.flag_provider = flag_provider

    def processar(self, pedido):
        if self.flag_provider.is_enabled("batch-processing"):
            return self._processar_lote(pedido)
        return self._processar_individual(pedido)

Sempre defina defaults seguros e fallbacks para evitar falhas em cascata quando o provedor de flags estiver indisponível:

def is_enabled(self, flag_name, context=None):
    try:
        return self.client.get_variant(flag_name, context) == "on"
    except ConnectionError:
        return False  # Fallback seguro: feature desativada

4. Estratégias de Implantação Gradual (Canary e Rolling)

Liberação gradual reduz riscos. Duas abordagens principais:

Por percentual de usuários: pode ser determinística (hash do user ID) ou aleatória. A abordagem determinística garante que o mesmo usuário sempre veja a mesma versão:

def usuario_no_grupo(user_id, percentual):
    hash_value = hash(str(user_id)) % 100
    return hash_value < percentual

Segmentação por atributos: geografia, plano de assinatura, dispositivo, tenant:

if (flag.is_enabled("recomendacao-ia") and
    user.plano == "premium" and
    user.pais in ["BR", "US"]):
    return recomendar_com_ia(usuario)

Durante a ativação progressiva, monitore métricas de saúde: latência média, taxa de erro HTTP 5xx, taxa de conversão. Se alguma métrica ultrapassar thresholds predefinidos, desative automaticamente a flag.

5. Testes e Validação com Feature Flags

Testes unitários devem mockar o provedor de flags:

class MockFlagProvider:
    def is_enabled(self, flag_name, context=None):
        return True  # ou False, dependendo do teste

def test_novo_checkout_com_flag_ativa():
    provider = MockFlagProvider()
    servico = CheckoutService(provider)
    resultado = servico.processar(pedido_valido)
    assert resultado["versao"] == "v2"

Testes de integração precisam cobrir combinações de estados de flags:

@pytest.mark.parametrize("flag_a,flag_b,esperado", [
    (True, True, "ambas_ativas"),
    (True, False, "apenas_a"),
    (False, True, "apenas_b"),
    (False, False, "nenhuma"),
])
def test_combinacao_flags(flag_a, flag_b, esperado):
    # Configurar flags e executar teste

Testes em produção incluem validação A/B (comparar métricas entre grupos) e dark launches (enviar tráfego real para nova feature sem exibir ao usuário, apenas para medir desempenho).

6. Gerenciamento de Dívida Técnica e Remoção de Flags

Flags temporárias devem ter política de expiração clara. Defina TTL (time-to-live) no momento da criação:

{
  "feature": "migracao-pagamentos",
  "created_at": "2024-01-15",
  "ttl_days": 90,
  "expires_at": "2024-04-15"
}

Automatize a remoção com scripts que varrem o código em busca de flags vencidas:

# Script de limpeza (pseudo-código)
for arquivo in codigo_fonte:
    for flag in extrair_flags(arquivo):
        if flag.expirada():
            remover_referencias(flag)
            atualizar_ci_cd_gate(flag)

Flags permanentes são anti-pattern. Elas aumentam a complexidade acidental, degradam performance (cada verificação tem custo) e dificultam a manutenção. Se uma flag dura mais de 6 meses, reavalie sua necessidade.

7. Integração com Observabilidade e Resiliência

Cada decisão de flag deve ser logada com contexto completo:

logger.info(
    "Flag decision",
    extra={
        "flag": "novo-checkout",
        "user_id": user.id,
        "decision": "enabled",
        "latency_ms": 2.3
    }
)

Métricas essenciais por flag:
- Latência da verificação
- Taxa de erro na feature controlada
- Taxa de conversão (comparando grupo controle vs. tratamento)

O kill switch é o recurso mais crítico: uma flag que desativa completamente uma feature problemática sem novo deploy. Deve ser a primeira flag a ser criada e a última a ser removida:

if feature_flags.is_enabled("kill-switch-pagamentos"):
    return {"status": "error", "message": "Serviço temporariamente indisponível"}

Conclusão

Feature flags transformam a entrega de software, permitindo deploy contínuo com riscos controlados. A chave para o sucesso está em: implementar com fallbacks seguros, monitorar métricas durante liberações graduais, e remover flags obsoletas antes que se tornem dívida técnica. Quando bem gerenciadas, as flags dão às equipes a confiança necessária para entregar código novo sabendo que, se algo der errado, um simples toggle resolve — sem pressa, sem pânico, sem novo deploy.

Referências