Em 2009, John Allspaw e Paul Hammond apresentaram no Velocity Conference "10+ Deploys per Day: Dev and Ops Cooperation at Flickr". A apresentação foi um marco: pela primeira vez, uma empresa grande demonstrava que deploy frequente e operação estável não são opostos — são complementares. Um dos mecanismos centrais que tornava isso possível eram os feature flags: a separação entre o ato de fazer deploy de código e o ato de ativar uma feature para usuários.
Feature flags (também chamados de feature toggles ou feature switches) são condicionais em runtime que controlam se uma feature está ativa para um usuário, grupo de usuários, ou porcentagem do tráfego. O código está em produção — compilado, testado, deployado — mas a feature só ativa quando o flag é ligado. Isso inverte a relação entre deploy e release: você pode fazer deploy de código incompleto, fazer deploy de código de alto risco sem ativá-lo, e ativar features gradualmente em vez de tudo de uma vez.
Pete Hodgson, em seu ensaio "Feature Toggles (aka Feature Flags)" (martinfowler.com, 2017), formalizou a taxonomia de flags em quatro categorias: release toggles (controle de deploy vs release), experiment toggles (A/B testing), ops toggles (controle operacional em runtime), e permission toggles (controle de acesso por usuário/tenant). Este conceito foca nos release toggles e ops toggles — os mais relevantes para disponibilidade e resiliência — e nos padrões de progressive delivery que os release toggles habilitam.
Release toggles — separando deploy de release
Um release toggle permite que código seja deployado para produção sem ser exposto aos usuários. Isso é incrivelmente valioso por várias razões:
Primeiro, permite que branches de vida longa sejam eliminadas. Em vez de manter uma branch de feature separada por semanas até a feature estar "pronta", o código vai para main em partes menores, protegido por flag. O risco de integração — que cresce exponencialmente com o tempo de divergência — é eliminado.
Segundo, permite rollback em segundos. Se uma feature ativada em produção causa problemas, desligar o flag é instantâneo. Não é preciso reverter commit, não é preciso rodar novo deploy, não é preciso aguardar pipeline de CI/CD. O flag vai para false e a feature desaparece imediatamente.
Terceiro, permite testar em produção com usuários reais antes do release completo. "Internal" flags para funcionários da empresa, canary flags para 1% dos usuários, flags por cohort de usuário beta — todos permitem validação com tráfego real antes do release amplo.
Feature flags acumulam dívida técnica se não forem removidos após o release. Um codebase com 200 flags antigos é difícil de raciocinar, difícil de testar (2^n combinações), e caro de manter. A disciplina de lifecycle de flags é tão importante quanto a disciplina de criá-los: defina data de expiração no momento da criação, revise flags mensalmente, remova flags de features completamente deployadas.
Ops toggles — degradação proativa em runtime
Ops toggles diferem de release toggles em propósito e lifecycle. Um release toggle é temporário — existe enquanto o release está em progresso, depois é removido. Um ops toggle pode ser permanente — é um mecanismo de controle operacional que pode permanecer no sistema indefinidamente como kill switch.
Os casos de uso de ops toggles são diretamente relevantes para resiliência:
Kill switches: um flag que desliga uma feature pesada durante picos de tráfego. "Recomendações personalizadas ligadas" pode ser desligado como preparação para Black Friday, reduzindo carga no serviço de ML que provê as recomendações. O flag é religado após o pico.
Circuit breakers manuais: quando um serviço dependente está degradado e o circuit breaker automático não foi ativado ainda (por estar abaixo do threshold), um operador pode ligar manualmente um flag que força o fallback para toda a feature que depende daquele serviço.
Degradação preventiva antes de manutenção: antes de uma janela de manutenção planejada no serviço de analytics, ligar um flag que desabilita todos os eventos de analytics sendo enviados. Os eventos se acumulam em buffer ou são descartados — dependendo da política — sem nenhum erro para o usuário.
Progressive delivery — canary e blue-green
Progressive delivery é o conjunto de técnicas para ativar uma mudança gradualmente em produção, observando métricas em cada etapa e revertendo antes que o problema se propague. Feature flags são o mecanismo; progressive delivery é o processo.
Canary release: a feature (ou versão nova do serviço) é ativada para uma pequena porcentagem do tráfego — tipicamente 1%, depois 5%, depois 10%, depois 50%, depois 100%. Em cada etapa, métricas (taxa de erro, latência, SLO consumption) são comparadas com o baseline. Se as métricas pioram além de um threshold, o canary é abortado e o tráfego volta 100% para a versão anterior. A metáfora é literal: o canário vai à frente na mina — se morrer, os mineiros recuam.
Blue-green deployment: em vez de redirecionar tráfego gradualmente, o sistema mantém duas versões em paralelo (blue = produção atual, green = nova versão). O tráfego é 100% blue até que green esteja pronto — então o switch é instantâneo (load balancer redireciona tudo para green). Rollback é igualmente instantâneo (switch de volta para blue). Blue-green é mais simples de implementar que canary mas tem custo maior: dois ambientes completos em paralelo durante o switchover.
Feature flag com percentual: mais granular que blue-green, mais simples de implementar que canary de infra. O código novo está em produção em todas as instâncias, mas só ativa para X% dos usuários. Os usuários no grupo "control" continuam vendo o comportamento antigo; os do grupo "treatment" veem o novo. Métricas por cohort permitem comparar.
| Técnica | Granularidade | Rollback | Custo de infra | Quando usar |
|---|---|---|---|---|
| Feature flag percentual | Por usuário/request | Instantâneo (flip flag) | Baixo | Features de produto, mudanças de comportamento |
| Canary release | Por instância/serviço | Redirecionar tráfego | Médio | Mudanças de infra, versões de serviço |
| Blue-green | Ambiente completo | Instantâneo (switch LB) | Alto (2 ambientes) | Deploys com zero downtime, migração de banco |
Plataformas de feature flags
LaunchDarkly: a plataforma comercial mais estabelecida. SDK em dezenas de linguagens, avaliação de flags em memória (sem latência de rede por flag), targeting por atributos de usuário (plano, país, cohort), auditoria de quem mudou qual flag quando. Caro para times pequenos, mas amortiza rápido para times que fazem deploys frequentes.
OpenFeature: especificação aberta (CNCF, 2022) que padroniza a interface de SDK de feature flags, agnóstica de provider. Com OpenFeature, o código usa uma API unificada e o provider pode ser trocado (LaunchDarkly, Flagsmith, Unleash) sem mudar o código. Resolve o lock-in de plataforma.
Flagsmith: open-source, self-hosted ou cloud, com SDK para as principais linguagens. Bom para times que precisam de controle de dados (conformidade, regulatório) ou que querem evitar dependência de terceiro.
Unleash: open-source, focado em progressive delivery. Feature ativa/inativa + estratégias de targeting (porcentagem, usuário, ambiente). Bem integrado com Kubernetes via operador.
// setup — inicialização única no startup
using OpenFeature;
using OpenFeature.Contrib.Providers.LaunchDarkly;
var provider = new LaunchDarklyProvider(
sdkKey: config["LaunchDarkly:SdkKey"]);
await Api.Instance.SetProviderAsync(provider);
// avaliação de flag em qualquer serviço
public class CheckoutService
{
private readonly IFeatureClient _flags;
public CheckoutService(IFeatureClient flags)
=> _flags = flags;
public async Task<OrderResult> PlaceOrderAsync(
Order order, Customer customer, CancellationToken ct)
{
var ctx = EvaluationContext.Builder()
.Set("userId", customer.Id)
.Set("plan", customer.Plan.ToString())
.Set("country", customer.Country)
.Build();
// Ops toggle — degradar recomendações proativamente
var withRecs = await _flags.GetBooleanValueAsync(
"checkout.show_recommendations", false, ctx, ct);
// Release toggle — nova experiência para 10% dos usuários
var newCheckout = await _flags.GetBooleanValueAsync(
"checkout.new_flow_v2", false, ctx, ct);
return newCheckout
? await ProcessNewFlowAsync(order, withRecs, ct)
: await ProcessLegacyFlowAsync(order, withRecs, ct);
}
}
OpenFeature tem SDK oficial em .NET: OpenFeature NuGet. O contexto de avaliação permite targeting por atributos do usuário sem amarrar a lógica de targeting ao código do serviço.
from flagsmith import Flagsmith
from flagsmith.models import Flags
# inicialização — uma vez no startup
flagsmith = Flagsmith(
environment_key=settings.FLAGSMITH_KEY,
enable_local_evaluation=True, # avaliação offline em cache
environment_refresh_interval_seconds=60,
)
class CheckoutService:
def __init__(self, flagsmith: Flagsmith):
self._flags = flagsmith
async def place_order(
self, order: Order, customer: Customer
) -> OrderResult:
# Flags avaliadas por identidade de usuário
flags: Flags = await asyncio.to_thread(
self._flags.get_identity_flags,
identifier=str(customer.id),
traits={"plan": customer.plan, "country": customer.country},
)
# Ops toggle — kill switch para recomendações
show_recs = flags.is_feature_enabled("checkout_recommendations")
# Release toggle — nova versão do checkout
new_flow = flags.is_feature_enabled("checkout_new_flow_v2")
if new_flow:
return await self._process_new_flow(order, show_recs)
return await self._process_legacy(order, show_recs)
async def _process_new_flow(self, order, show_recs):
# implementação do novo checkout
...
enable_local_evaluation=True baixa as regras de targeting e avalia localmente — latência de flag <1ms, sem depender de rede. Imprescindível para flags em hot paths.
import unleash "github.com/Unleash/unleash-client-go/v4"
func init() {
unleash.Initialize(
unleash.WithUrl(os.Getenv("UNLEASH_URL")),
unleash.WithAppName("checkout-service"),
unleash.WithCustomHeaders(http.Header{
"Authorization": {os.Getenv("UNLEASH_TOKEN")},
}),
unleash.WithRefreshInterval(30*time.Second),
// cache local — avaliação sem rede
)
unleash.WaitForReady()
}
type CheckoutService struct{}
func (s *CheckoutService) PlaceOrder(
ctx context.Context,
order Order,
customer Customer,
) (*OrderResult, error) {
ctx2 := unleash.WithContext(ctx, unleash.Context{
UserId: customer.ID,
Properties: map[string]string{
"plan": customer.Plan,
"country": customer.Country,
},
})
showRecs := unleash.IsEnabled("checkout.recommendations",
unleash.WithContext(ctx2))
newFlow := unleash.IsEnabled("checkout.new_flow_v2",
unleash.WithContext(ctx2))
if newFlow {
return s.processNewFlow(ctx, order, showRecs)
}
return s.processLegacy(ctx, order, showRecs)
}
Unleash com SDK Go avalia flags localmente com regras sincronizadas periodicamente. WaitForReady() no init garante que o cache está populado antes de servir tráfego.
Feature flags e testes
Feature flags complicam testes: agora o comportamento de uma função muda dependendo de qual flag está ativo. A estratégia mais simples é isolar o ponto de decisão de flag em uma camada fina e injetável, e testar cada branch separadamente — sem depender de infraestrutura de flag nos testes unitários.
// Teste sem dependência de flag infrastructure
[Fact]
public async Task PlaceOrder_NewFlow_UsesNewLogic()
{
var flags = new Mock<IFeatureClient>();
flags.Setup(f => f.GetBooleanValueAsync("checkout.new_flow_v2",
false, It.IsAny<EvaluationContext>(), default))
.ReturnsAsync(true);
var svc = new CheckoutService(flags.Object, ...);
var result = await svc.PlaceOrderAsync(order, customer);
Assert.Equal("new_flow", result.ProcessedBy);
}
Kill switches e responsabilidade operacional
Todo ops toggle que pode degradar o sistema precisa de documentação operacional: quem pode ligar, quem pode desligar, o que acontece quando o flag muda em cada direção, e qual é o procedimento de rollback. Um kill switch não documentado é um risco operacional — em incidentes de alta pressão, operadores ativam flags sem entender as consequências.
A melhor prática é manter um runbook de flags operacionais: nome do flag, propósito, efeito quando ativado, efeito quando desativado, owner, data de revisão. Isso transforma o flag de "segredo técnico" em ferramenta operacional compartilhada.
Como praticar
- Implementar um kill switch operacional. Escolha uma feature que você quer poder desligar em segundos em caso de problema. Implemente um ops toggle usando OpenFeature + um provider leve (Flagsmith ou Unleash self-hosted). Documente o runbook do flag: o que acontece quando ligado vs desligado, quem pode mudar, como verificar que o toggle está funcionando. Teste o rollback em staging.
- Simular um canary release. Em staging, configure uma feature com flag percentual: 0% → 10% → 50% → 100%. Para cada etapa, defina uma métrica de saúde (taxa de erro, latência P99) e um threshold de abort. Execute as etapas manualmente, injetando um erro na feature no passo de 10% para verificar que o rollback funciona.
- Auditar flags existentes. Liste todos os feature flags no codebase (grep de flags hardcoded, variáveis de config, toggles em banco). Classifique: release toggle (temporário), ops toggle (permanente), ou flag obsoleto (para remover). Para cada flag obsoleto, crie task de remoção. Isso calibra a intuição sobre dívida técnica de flags.
Referências para aprofundar
- artigo Feature Toggles (aka Feature Flags) — Pete Hodgson (martinfowler.com, 2017).
- artigo 10+ Deploys a Day: Dev and Ops Cooperation at Flickr — Allspaw & Hammond (Velocity, 2009).
- docs OpenFeature Specification — CNCF, openfeature.dev.
- docs LaunchDarkly — Feature Management — launchdarkly.com/docs.
- artigo Progressive Delivery Explained — James Governor (RedMonk, 2018).
- livro Continuous Delivery — Jez Humble & David Farley (Addison-Wesley, 2010).
- artigo The Feature Flag Lifecycle — Honeycomb Engineering Blog, 2022.
- artigo How Etsy Deploys 50+ Times per Day — Etsy Engineering Blog.
- vídeo Feature Flags at Scale — GOTO Conferences, 2021.
- docs Unleash — Feature Toggle Service — docs.getunleash.io.
- artigo Testing with Feature Flags — ThoughtWorks, 2021.
- artigo Blue-Green Deployments — Martin Fowler (martinfowler.com, 2010).