Feature Flags em Serviços Go
Feature flags permitem que equipes Go implantem código em produção mantendo o novo comportamento desativado até que a validação seja concluída - ou alternem um kill switch sem recompilar o binário.
Busque em todas as páginas da documentação
Feature flags permitem que equipes Go implantem código em produção mantendo o novo comportamento desativado até que a validação seja concluída - ou alternem um kill switch sem recompilar o binário.
Uma feature flag é uma decisão em tempo de execução: dada uma chave de flag e um contexto de avaliação (usuário, tenant, região), retorna habilitado ou desabilitado.
Serviços Go avaliam flags em handlers, workers e interceptores gRPC.
OpenFeature fornece um SDK neutro em relação ao fornecedor; LaunchDarkly, Flagsmith e serviços de configuração internos se conectam como provedores.
Flags complementam deploys canary: canary muda o tráfego entre binários; flags mudam o comportamento dentro do mesmo binário.
Cartão de receita de referência rápida - pronto para copiar e colar.
import (
"context"
"github.com/open-feature/go-sdk/openfeature"
)
func checkoutHandler(client *openfeature.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
enabled, _ := client.BooleanValue(r.Context(), "new-checkout", false, openfeature.EvaluationContext{})
if enabled {
newCheckout(w, r)
return
}
legacyCheckout(w, r)
}
}Quando usar isso:
package main
import (
"context"
"log/slog"
"net/http"
"os"
"sync"
"time"
)
// InMemoryProvider é um backend de flag mínimo para demonstrações e testes.
type InMemoryProvider struct {
mu sync.RWMutex
flags map[string]bool
}
func NewInMemoryProvider() *InMemoryProvider {
return &InMemoryProvider{flags: map[string]bool{}}
}
func (p *InMemoryProvider) Set(key string, val bool) {
p.mu.Lock()
p.flags[key] = val
p.mu.Unlock()
}
func (p *InMemoryProvider) BooleanEvaluation(_ context.Context, flag string, defaultValue bool, _ map[string]interface{}) (bool, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if v, ok := p.flags[flag]; ok {
return v, nil
}
return defaultValue, nil
}
type FlagClient struct {
provider *InMemoryProvider
}
func (c *FlagClient) Enabled(ctx context.Context, key string, def bool) bool {
val, err := c.provider.BooleanEvaluation(ctx, key, def, nil)
if err != nil {
slog.Warn("erro na avaliação da flag", "key", key, "err", err)
return def
}
return val
}
func main() {
provider := NewInMemoryProvider()
// Desligado por padrão - dark launch
provider.Set("new-pricing", false)
flags := &FlagClient{provider: provider}
mux := http.NewServeMux()
mux.HandleFunc("GET /price", func(w http.ResponseWriter, r *http.Request) {
if flags.Enabled(r.Context(), "new-pricing", false) {
w.Write([]byte(`{"amount":99,"engine":"v2"}`))
return
}
w.Write([]byte(`{"amount":100,"engine":"v1"}`))
})
mux.HandleFunc("POST /admin/flags/{key}", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Admin-Token") != os.Getenv("ADMIN_TOKEN") {
http.Error(w, "proibido", http.StatusForbidden)
return
}
key := r.PathValue("key")
provider.Set(key, r.URL.Query().Get("value") == "true")
w.WriteHeader(http.StatusNoContent)
})
srv := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
slog.Info("escutando", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil {
slog.Error("servidor parado", "err", err)
os.Exit(1)
}
}O que isso demonstra:
false para novas flags - postura segura de dark launch.BooleanValue / StringValue em pontos de decisão com um contexto de avaliação (ID do usuário, organização, região).| Campo | Propósito | Exemplo |
|---|---|---|
targetingKey | ID estável de usuário/tenant | org_4821 |
region | Rollout geográfico | eu-west-1 |
tier | Recursos baseados em plano | enterprise |
Passe o contexto de claims JWT ou chaves de API - nunca avalie flags sem identidade para comportamento voltado ao usuário.
| Abordagem | Força | Contrapartida |
|---|---|---|
| OpenFeature | Troque provedores sem alteração de código | Camada de abstração extra |
| SDK LaunchDarkly | UI de segmentação rica, experimentos | Dependência do fornecedor (vendor lock-in) |
| Env / ConfigMap | Dependência zero | Sem rollouts de porcentagem dinâmicos |
// Avalie uma vez por requisição, passe a decisão adiante - evite chamadas N+1 ao provedor
func middleware(flags *FlagClient) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), pricingFlagKey,
flags.Enabled(r.Context(), "new-pricing", false))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}*openfeature.Client ou sua interface em main - teste com um provedor falso.context.Context.true para novas flags - uma interrupção do provedor habilita código arriscado para todos. Correção: padrão false; opt-in explícito por ambiente.payments-v2. Correção: use logs de auditoria do fornecedor ou GitOps para configuração de flags.if flag se tornam espaguete. Correção: tickets de aposentadoria de flags; exclua caminhos mortos após o rollout completo.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Feature flags | Alternar comportamento sem reimplantar | Configurações simples que nunca mudam em tempo de execução |
| Canary deploy | Comparar o desempenho do binário completo | Necessidade de habilitação por tenant |
| Variáveis de ambiente de configuração (Config env vars) | Configurações de serviço raramente alteradas | Rollouts de porcentagem ou kill switches |
Versionamento de API (/v2) | Alterações de contrato que quebram compatibilidade | Pequenas alterações de comportamento interno |
Comece com OpenFeature e o provedor LaunchDarkly se você já paga pelo LaunchDarkly.
OpenFeature mantém os caminhos de migração abertos caso você mude de fornecedor.
Implante canary com a flag desativada para todos os usuários.
Habilite a flag apenas para a coorte canary por meio de regras de segmentação, depois expanda.
Separa a saúde do binário do risco da lógica do recurso.
Sim para equipes pequenas - monte JSON e monitore com fsnotify.
Sem segmentação por porcentagem ou UI de auditoria - serve apenas para kill switches.
Injete um provedor falso nos testes.
Teste em tabela ambos os ramos habilitados e desabilitados; afirme métricas e corpos de resposta.
Sim - use metadados do job (ID do tenant) como contexto de avaliação.
Armazene em cache o estado da flag por lote de jobs para limitar chamadas ao provedor.
Use StringValue ou flags JSON estruturadas para configuração dinâmica (por exemplo, números de limite de taxa).
Mantenha o esquema versionado para evitar pânicos de análise - valide antes de usar.
Alvo de sub-milissegundo com cache local.
Em caso de timeout do provedor, retorne o padrão seguro e incremente uma métrica flag_eval_errors.
Acompanhe a idade e a propriedade das flags.
Aposente flags dentro de 90 dias após o rollout completo; centenas de flags obsoletas confundem a equipe de plantão.
Use chaves de segmentação opacas, não e-mail nos campos de contexto.
Documente a avaliação de flags em avisos de privacidade se usadas para experimentos.
Flagsmith, Unleash e GO Feature Flag (github.com/thomaspoignant/go-feature-flag) são comuns.
Auto-hospede quando regras de residência de dados bloquearem SaaS.
Versões da Stack: Esta página foi escrita para Go 1.26.x (padrão GC Green Tea, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026