DevOps & CI/CD • 9 min de leitura

Feature Flags: como realizar deploys mais seguros e confiáveis

A gestão de mudanças no código-fonte de um sistema é uma tarefa constante e desafiadora para os desenvolvedores. Com a crescente complexidade dos sistemas de software, é cada vez mais difícil garantir que as alterações não causem problemas inesperados. Nesse contexto, surge a necessidade de encontrar estratégias eficazes para minimizar riscos durante os processos de deploy.

A gestão de mudanças em sistemas de software é uma das tarefas mais delicadas do ciclo de desenvolvimento. À medida que os sistemas crescem em complexidade, aumenta também o risco de que uma alteração, por menor que seja, cause efeitos inesperados em produção. Feature flags surgem como uma resposta prática a esse desafio: uma técnica que permite entregar código sem necessariamente entregar funcionalidade, separando os dois momentos com precisão cirúrgica.

Neste artigo, você vai entender o que são feature flags, como funcionam na prática, como implementá-las em JavaScript, Python e Rust, e quais padrões seguir para evitar as armadilhas mais comuns.

A ideia por trás das Feature Flags

Feature Flags — também chamadas de feature toggles ou bandeiras de recursos — são variáveis de configuração que controlam dinamicamente quais funcionalidades de um sistema estão ativas em um dado momento. Em vez de lançar um deploy que imediatamente expõe uma nova feature a todos os usuários, a feature é entregue "desligada" e ativada de forma controlada, sem necessidade de recompilar ou reimplantar o código.

O conceito pode ser resumido assim:

"Separar o ato de implantar código do ato de liberar funcionalidade."

Isso tem implicações profundas no ciclo de vida de um sistema. Um desenvolvedor pode fazer merge de uma feature incompleta na branch principal, protegida por uma flag desativada. Outro pode usar a flag para realizar um canary release, ativando a funcionalidade apenas para 5% dos usuários antes de um rollout completo. Um terceiro pode desativar um módulo com problema em produção em segundos, sem rollback de deploy.

O que muda na prática

Redução de risco em deploys

O maior benefício é desacoplar o deploy do release. Um deploy frequente com features protegidas por flags é menos arriscado do que deploys grandes e espaçados carregados de mudanças acumuladas.

Facilidade de rollback

Quando algo dá errado, basta desativar a flag. Não há necessidade de reverter commits, rodar pipelines de CI/CD inteiros ou coordenar times. O rollback é instantâneo.

Testes em produção com controle

Flags permitem estratégias como:

  • Canary releases: ativar para uma fatia pequena de usuários antes do rollout geral.
  • A/B testing: comparar duas versões de uma feature com grupos distintos.
  • Beta opt-in: permitir que usuários específicos testem funcionalidades antes da liberação geral.

Integração contínua sem branches de longa duração

Com feature flags, times podem adotar trunk-based development — todos trabalhando na mesma branch principal — sem o caos de merges conflitantes de branches de vida longa.

Por baixo dos panos

Em sua forma mais simples, uma feature flag é uma condicional no código:

if (featureFlags.isEnabled('BUSCA_AVANCADA')) {
  return buscarAvancado(termo);
} else {
  return buscarSimples(termo);
}

A lógica que determina se a flag está ativa pode vir de diversas fontes:

  • Variáveis de ambiente (FEATURE_BUSCA_AVANCADA=true)
  • Banco de dados ou cache (Redis, por exemplo)
  • Serviços especializados (LaunchDarkly, Flagsmith, Unleash)
  • Arquivos de configuração (.env, YAML)

A chave está em centralizar esse controle em um único serviço ou módulo, evitando que verificações de flags se espalhem de forma inconsistente pelo código.

Implementação

JavaScript — Node.js com variáveis de ambiente

Uma abordagem simples e sem dependências externas: um módulo centralizado que lê as flags do ambiente e expõe uma API limpa para o resto da aplicação.

// flags.js
const FLAGS = {
  BUSCA_AVANCADA:   process.env.FEATURE_BUSCA_AVANCADA   === 'true',
  NOTIFICACAO_EMAIL: process.env.FEATURE_NOTIFICACAO_EMAIL === 'true',
  NOVO_CHECKOUT:    process.env.FEATURE_NOVO_CHECKOUT    === 'true',
};

export function isEnabled(flag) {
  const enabled = FLAGS[flag] ?? false;
  console.debug(`[FeatureFlag] ${flag} = ${enabled}`);
  return enabled;
}
// busca.js
import { isEnabled } from './flags.js';

export function buscar(termo) {
  if (isEnabled('BUSCA_AVANCADA')) {
    return buscarAvancado(termo);
  }
  return buscarSimples(termo);
}
# .env.production
FEATURE_BUSCA_AVANCADA=true
FEATURE_NOTIFICACAO_EMAIL=true
FEATURE_NOVO_CHECKOUT=false

Para flags que precisam mudar sem reiniciar o processo, substitua a leitura estática por um fetch periódico a um serviço externo (Unleash, Flagsmith) ou ao Redis.

Python — com recarga dinâmica via Redis

Quando flags precisam mudar em tempo real — sem redeploy ou reinício — armazená-las no Redis é a abordagem mais comum.

# flags.py
import os
import logging
import redis

logger = logging.getLogger(__name__)

_redis = redis.Redis(
    host=os.getenv("REDIS_HOST", "localhost"),
    port=int(os.getenv("REDIS_PORT", 6379)),
    decode_responses=True,
)

def is_enabled(flag: str) -> bool:
    try:
        value = _redis.get(f"feature:{flag.lower()}")
        enabled = value == "true"
    except redis.RedisError:
        # Falha segura: flag desativada se Redis estiver indisponível
        enabled = False

    logger.debug("FeatureFlag [%s] = %s", flag, enabled)
    return enabled
# busca.py
from flags import is_enabled

def buscar(termo: str) -> str:
    if is_enabled("BUSCA_AVANCADA"):
        return buscar_avancado(termo)
    return buscar_simples(termo)
# Ativar/desativar sem redeploy, direto no Redis
redis-cli SET feature:busca_avancada true
redis-cli SET feature:novo_checkout false

A falha segura (enabled = False quando Redis está indisponível) é uma decisão de design importante: em caso de falha de infraestrutura, o sistema degradado é preferível a expor features instáveis.

Rust — com AtomicBool para zero custo em hot path

Em Rust, flags que mudam raramente mas são verificadas com alta frequência se beneficiam de AtomicBool — leitura sem lock e sem alocação.

// flags.rs
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};

pub struct FeatureFlags {
    inner: HashMap<&'static str, AtomicBool>,
}

impl FeatureFlags {
    pub fn from_env() -> Self {
        let mut map = HashMap::new();

        let definitions = [
            ("BUSCA_AVANCADA",    "FEATURE_BUSCA_AVANCADA"),
            ("NOTIFICACAO_EMAIL", "FEATURE_NOTIFICACAO_EMAIL"),
            ("NOVO_CHECKOUT",     "FEATURE_NOVO_CHECKOUT"),
        ];

        for (flag, env_key) in definitions {
            let enabled = std::env::var(env_key)
                .map(|v| v == "true")
                .unwrap_or(false);
            map.insert(flag, AtomicBool::new(enabled));
        }

        Self { inner: map }
    }

    pub fn is_enabled(&self, flag: &str) -> bool {
        self.inner
            .get(flag)
            .map(|b| b.load(Ordering::Relaxed))
            .unwrap_or(false)
    }

    pub fn set(&self, flag: &str, value: bool) {
        if let Some(b) = self.inner.get(flag) {
            b.store(value, Ordering::Relaxed);
        }
    }
}

// Singleton global thread-safe
static FLAGS: OnceLock<Arc<FeatureFlags>> = OnceLock::new();

pub fn flags() -> &'static Arc<FeatureFlags> {
    FLAGS.get_or_init(|| Arc::new(FeatureFlags::from_env()))
}
// busca.rs
use crate::flags::flags;

pub fn buscar(termo: &str) -> String {
    if flags().is_enabled("BUSCA_AVANCADA") {
        buscar_avancado(termo)
    } else {
        buscar_simples(termo)
    }
}

O método set() permite atualizar flags em runtime — por exemplo, a partir de um endpoint interno de administração ou de um watcher do Redis — sem reiniciar o processo e sem custo de sincronização no caminho de leitura.

Nota em todos os exemplos: em ambientes com múltiplas instâncias, centralize as flags em um serviço externo (Unleash, Flagsmith) para garantir consistência entre nodes.

O que vale a pena guardar

Use um módulo/serviço dedicado

Acessar variáveis de ambiente diretamente em vários lugares do código torna as flags difíceis de testar e auditar. Um módulo central (flags.js, flags.py, flags.rs) permite mockar comportamentos em testes, adicionar logging em um único ponto e trocar a fonte das flags (env → Redis → serviço externo) sem tocar no resto da aplicação.

Defina nomes consistentes e descritivos

Prefira nomes que descrevam a funcionalidade, não a implementação. NOVO_CHECKOUT é melhor que FLAG_V2_CHECKOUT_REFACTORING_JUNHO. Documente cada flag: quem criou, para que serve e quando pode ser removida.

Planeje a remoção das flags

Feature flags têm ciclo de vida. Uma flag que permanece indefinidamente no código vira dívida técnica. Ao criar uma flag, já defina os critérios para sua remoção: "quando 100% dos usuários estiverem no novo fluxo por 30 dias, esta flag pode ser removida."

Habilite logging para auditoria

Registre quando uma flag é avaliada e qual foi o resultado. Isso é essencial para depurar comportamentos inconsistentes entre ambientes. Os três exemplos acima já incluem logging — mantenha esse padrão.

Garanta segurança em concorrência

Em ambientes concorrentes, evite mutações de estado compartilhado na lógica de avaliação de flags. O exemplo Rust usa AtomicBool para isso. Em Python, o Redis já é thread-safe por natureza. Em JavaScript, a natureza single-threaded do event loop elimina o problema para a maioria dos casos.

Onde as coisas costumam quebrar

Flag debt: flags que nunca são removidas acumulam-se silenciosamente e tornam o código difícil de entender. Toda flag criada deveria ter uma data ou condição de saída — "quando 100% dos usuários estiverem no novo fluxo por 30 dias, esta flag some". Sem isso, o código vira um museu de decisões antigas.

Lógica espalhada: verificar flags diretamente em controller, service e repository ao mesmo tempo é receita para inconsistência. A flag muda num lugar, o comportamento continua errado em outro. Um módulo central resolve.

Combinações explosivas: 10 flags booleanas = 1.024 estados possíveis. Ninguém vai testar todos. Mantenha o número de flags ativas ao mesmo tempo pequeno, e documente explicitamente quando duas flags têm dependência entre si — se B só faz sentido com A ativa, isso precisa estar escrito em algum lugar.

Quando não faz sentido usar

Flags não são a ferramenta certa para tudo. Controle de acesso permanente pertence a um sistema de permissões (RBAC, ABAC), não a uma variável de ambiente. Credenciais e parâmetros de infraestrutura não são features. E uma feature escondida por flag ainda precisa ser testada — a flag não substitui o teste, só adia a exposição.

Para ir além

Feature flags são uma ferramenta poderosa quando usadas com disciplina — e uma fonte de dor quando não são. O maior risco não está na implementação, mas no acúmulo: flags que ninguém sabe se ainda importam, condicionais que ninguém tem coragem de remover, comportamentos que dependem de combinações que nunca foram testadas juntas.

A chave é tratar flags como código de primeira classe: com dono, com documentação e com data de validade.

  • Feature Toggles — Martin Fowler: o artigo de referência, com taxonomia completa dos tipos de flags.
  • Unleash e Flagsmith: plataformas open-source para gerenciamento centralizado.
  • Trunk-based development: o modelo de branching que mais se beneficia de flags bem gerenciadas.

Referências

  • FOWLER, M. Feature Toggles. Disponível em: https://martinfowler.com/articles/feature-toggles.html
  • HUMBLE, J.; FARLEY, D. Continuous Delivery. Addison-Wesley, 2010.
  • Flagsmith Documentation. Disponível em: https://docs.flagsmith.com
  • Amazon Web Services. Feature Management com AWS AppConfig. Disponível em: https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html