04 - Feature Flags (Toggles)
video: https://youtu.be/... no frontmatter da nota.Feature Flags (Toggles)
Feature flags são interruptores de configuração que decidem, em tempo de execução, se um trecho de código executará ou não, permitindo desacoplar o deploy da release. Com flags, o código chega à produção desligado, é ativado para subconjuntos escolhidos de usuários e é removido do binário só depois de comprovado, tudo sem reimplantar nada. Em produção, essa peça transforma o lançamento de funcionalidade em uma decisão operacional reversível ao nível do usuário e do segmento, e não em um evento binário por versão. A combinação com kill switch fornece a capacidade de desligar uma funcionalidade problemática em segundos, sem rollback de deploy. O custo da técnica é permanente e não trivial: cada flag é estado vivo, com impacto sobre testes, performance e complexidade cognitiva, e exige gestão disciplinada para não se transformar em dívida.
Conceito e problema que resolve
O problema atacado é o acoplamento entre publicar código e expor funcionalidade. Sem flags, cada entrega carrega essa dupla indissociável: o que entra em produção fica visível para todo mundo, e a única forma de esconder um recurso problemático é reverter o deploy inteiro. Times com trunk-based development dependem de flags para integrar continuamente sem expor trabalho incompleto, e operações dependem delas para reduzir o risco de releases grandes em releases lógicas controladas.
A flag move o controle da versão para a configuração. O binário contém a funcionalidade nova e a antiga, e o caminho de execução é decidido por uma avaliação no ponto de decisão: a avaliação consulta o estado da flag para o usuário e o contexto correntes e desvia para a ramificação correspondente. O estado da flag vive em um serviço central de gerenciamento, distribuído por push ou polling, e a mudança tem efeito em segundos sem reimplantação.
Tipos de flags: release, operação, experimento e permissão
Nem toda flag tem o mesmo ciclo de vida, e classificá-las define a disciplina de gestão. A tipologia clássica de Martin Fowler separa quatro categorias com propósitos e durabilidades distintos.
- Flags de release: escondem funcionalidade em desenvolvimento ou em lançamento gradual; vivem dias ou semanas e têm remoção obrigatória logo após a completa ativação.
- Flags de operação: controlam comportamento do sistema em execução, como desligar uma integração instável, mudar a taxa de um job ou desviar um fluxo para caminho alternativo; vivem meses ou anos e têm dono declarado.
- Flags de experimento: alimentam testes A-B, com atribuição determinística de variante por usuário; vivem o tempo do experimento e são removidas junto com a análise.
- Flags de permissão: liberam funcionalidades para clientes específicos, planos ou organizações, funcionando como mecanismo de entitlement comercial; tendem a ser de longa duração por natureza.
As duas primeiras são instrumentos de engenharia; as duas últimas, de produto e negócio. A confusão entre elas, sobretudo tratar flag de permissão como flag de release, é a raiz de boa parte da bagunça de flags em sistemas maduros.
O cadastro saudável de uma flag de release registra desde a criação os metadados que farão a remoção possível meses depois.
# Definição declarativa de flag em arquivo versionado no repositório
flag:
chave: checkout-one-click # nomenclatura estável e descritiva
tipo: release # categoria determina ciclo de vida
descricao: "Habilita pagamento com um clique no checkout"
dono: time-checkout # responsável pela remoção
criada-em: "2025-03-10"
expira-em: "2025-05-10" # data limite para remoção obrigatória
valor-padrao: false # fallback seguro se serviço cair
rollout:
estrategia: percentual # hash estável por identidade
percentual: 25
segmentos: # grupos fixados prevalecem do hash
- nome: beta-interno
variante: true
ticket: "PROJ-4821" # rastreio do trabalho original
Esse arquivo vive no repositório, passa por review junto do código e é a fonte da verdade para o serviço de flags, o que conecta a remoção da flag ao mesmo fluxo de pull request do resto do sistema. A data de expiração alimenta o relatório de dívida e impede que a flag sobreviva silenciosamente ao lançamento.
Funcionamento interno da avaliação
Cada avaliação precisa ser determinística, rápida e sem chamadas de rede no caminho crítico quando possível. A arquitetura usual tem três peças: o serviço central que guarda o estado e as regras, o SDK que embute na aplicação o cache das avaliações, e o canal de propagação que atualiza o cache por polling ou streaming. A avaliação local no SDK é o que mantém a latência da flag em microssegundos e a resiliência em caso de queda do serviço central, com política de fallback para o valor padrão.
// Avaliação de flag com rollout por percentual determinístico
// A mesma avaliação sempre retorna o mesmo resultado para o mesmo
// usuário, evitando que ele veja a funcionalidade ligar e desligar
function avaliarFlag(chave, usuario) {
const config = cache.get(chave) ?? {
ativa: false, rolloutPercent: 0, variacaoPadrao: "controle"
};
if (!config.ativa) return config.variacaoPadrao;
// bucket estável: hash da chave + identidade gera o grupo fixo
const hash = murmur3(`${chave}:${usuario.id}`);
const bucket = hash % 100; // 0 a 99
if (bucket >= config.rolloutPercent) {
return config.variacaoPadrao; // fora do percentual liberado
}
// usuário fixado no segmento força a variante declarada
const segmento = config.segmentos.find(s =>
s.ids.includes(usuario.id)
);
return segmento ? segmento.variante : config.varianteLiberada;
}
O ponto crítico do desenho é a estabilidade da atribuição: o percentual deve ser calculado a partir de um hash estável da identidade, e não por sorteio a cada requisição, pois usuário que vê a funcionalidade alternar entre ativa e inativa em requisições consecutivas gera suporte, confusão e dados corrompidos de experimento. Grupos fixados por segmento precisam prevalecer sobre o percentual, garantindo que betas direcionados não dependam de sorte.
Kill switch e rollout controlado
O kill switch é a aplicação mais valiosa em produção: a funcionalidade instável é desligada em segundos, sem rollback e sem reimplantação, enquanto a equipe investiga com calma. Para cumprir esse papel, a flag precisa avaliar no caminho da decisão com custo desprezível, o cache local precisa receber a atualização rápido o bastante para o incidente, e o valor padrão em caso de falha do serviço de flags precisa ser o caminho seguro, tipicamente a funcionalidade desligada.
O rollout gradual por percentual complementa o kill switch como prevenção. Liberar 5% do tráfego, observar as métricas da coorte exposta, subir para 25%, 50% e 100% reproduz a lógica do canary no nível da funcionalidade, com a vantagem de não depender de infraestrutura de service mesh. A combinação das duas camadas é comum em sistemas maduros: o canary protege a versão do código, e a flag protege a funcionalidade dentro da versão.
Rollout por percentual, usuário e segmento
A progressão típica de um lançamento segue três graus de refinamento, cada um servindo um momento diferente do ciclo.
| Grau | Critério | Uso principal | Exemplo |
|---|---|---|---|
| Percentual | Hash estável do usuário | Validação sob tráfego real | 5%, 25%, 50%, 100% |
| Usuário | Identidade individual | Beta interno e betas direcionados | lista de IDs fixados |
| Segmento | Atributo do usuário ou da conta | Planos, regiões, empresas específicas | plano premium, Brasil, organização X |
A ordem usual começa por usuário para smoke test controlado, passa por segmento para grupos com perfil conhecido, e termina em percentual para a rampa estatística. Cada grau reduz a incerteza do seguinte, e a coorte exposta em cada etapa gera os dados que fundamentam a decisão de avançar.
Arquitetura de um sistema de flags
Um sistema de flags em produção tem três camadas com responsabilidades distintas, e desenhar essa fronteira é o que separa a solução robusta da improvisação frágil.
- Camada de gestão: interface administrativa com estado, regras, segmentos, histórico de auditoria e metadados de dono e expiração; é onde produto e engenharia operam as flags.
- Camada de distribuição: API e canais de propagação, por polling periódico ou streaming por websocket, que levam o estado atualizado aos SDKs com latência compatível com o propósito da flag.
- Camada de avaliação: SDK embutido na aplicação, com cache local, hashing determinístico e fallback declarado; é a única camada no caminho de requisição, e por isso precisa ser resiliente e rápida.
A escolha entre ferramenta gerenciada e solução própria depende de escala e de requisitos de privacidade. O padrão aberto OpenFeature reduz o acoplamento ao fornecedor, padronizando a API de avaliação e permitindo trocar o provedor sem reescrever os pontos de decisão.
| Solução | Modelo | Destaque operacional |
|---|---|---|
| LaunchDarkly | Gerenciada SaaS | Propagação por streaming e segmentação avançada |
| Unleash | Open source auto-hospedada | Estratégias de rollout por projeto e token |
| Flagsmith | Open source e nuvem | Frontend edge com avaliação local |
| AWS AppConfig | Gerenciada cloud | Integração nativa com contas e deploys AWS |
| OpenFeature | Especificação aberta | Neutralidade de provedor e SDKs padronizados |
Duas exigências transversais merecem menção. Auditoria de mudanças é indispensável, pois a pergunta de incidente, quem ligou essa flag e quando, precisa de resposta em segundos. E observabilidade das avaliações, com contadores por variante retornada, permite detectar cache defasado, hash inconsistente e percentuais aplicados de fato diferente do declarado.
Dívida de flags, remoção e custo de gestão
Flags vivas custam em várias moedas ao mesmo tempo, e o custo cresce com o quadrado do acúmulo, porque combinações se multiplicam.
- Complexidade de teste: n flags binárias geram 2 elevado a n caminhos de execução; cobertura completa é impossível além de um punhado de flags, e a estratégia de testes precisa decidir quais combinações importam.
- Custo cognitivo: cada ramificação é um estado mental a mais para todo dev que lê o código, inclusive meses depois da remoção do contexto original.
- Custo de performance: avaliações no caminho quente, mesmo baratas, somam; flags em loops internos e em serializers exigem cuidado redobrado.
- Risco de estado inconsistente: cache defasado entre instâncias pode produzir respostas divergentes para o mesmo usuário durante propagações.
- Estale flags: flags esquecidas viram código morto condicional que ninguém tem coragem de remover, por falta de dono e de contexto.
A disciplina que evita o acúmulo é simples de enunciar e difícil de manter: toda flag nasce com dono, data de expiração e plano de remoção registrados no próprio serviço de flags; a remoção da flag de release é parte da definição de pronto da funcionalidade, incluindo o commit que apaga a ramificação condicional; e um relatório periódico de flags expiradas alimenta a fila de limpeza. Ferramentas de análise estática e plugins de lint detectam referências a flags já desativadas, automatizando parte da higiene.
Erros comuns e boas práticas
- Usar a mesma flag para propósitos diferentes, por exemplo como release e depois como permissão comercial, o que impede a remoção e congela a dívida.
- Avaliar flags com chamada de rede síncrona no caminho crítico, transformando o serviço de flags em ponto único de falha da aplicação.
- Valor padrão inseguro no fallback, ativando a funcionalidade nova justamente quando o serviço central está indisponível.
- Rollout por requisição em vez de por usuário, com a funcionalidade piscando e dados de uso inutilizáveis.
- Falta de teste da combinação desligada: o código herda os testes do caminho ativo, e o kill switch em produção descobre que o caminho padrão está quebrado.
- Nomes sem contexto, como flagNovaBotaoAtivo, sem dono nem expiração, virando arqueologia seis meses depois.
Entre as boas práticas, separe flags de release de flags de operação já na nomenclatura, com prefixos claros como release, operação, experimento e permissão; centralize o estado em um serviço com SDK local e propagação rápida; declare valor padrão, dono e expiração no cadastro de cada flag; teste o caminho desligado com o mesmo rigor do ligado; e trate a remoção como tarefa técnica agendada, não como lembrança eventual. Times que seguem esse regime usam flags como instrumento de controle fino; times que não seguem acumulam centenas de interruptores sem dono que ninguém mais se atreve a tocar.