Para consumir APIs de CPF com resiliência em Go, implemente o padrão circuit breaker usando sync.Mutex para controle de estado ou a biblioteca sony/gobreaker para uma solução pronta para produção. O circuit breaker monitora a taxa de falhas e, ao atingir o limiar configurado, interrompe temporariamente as chamadas à API — retornando uma resposta de fallback ou dado de cache — para evitar que uma falha externa derrube toda a aplicação.
Introdução
Quando uma API externa fica indisponível, continuar enviando requisições não apenas desperdiça recursos, mas pode causar falhas em cascata que derrubam toda a aplicação. O padrão Circuit Breaker resolve esse problema interrompendo temporariamente as chamadas à API quando detecta uma taxa elevada de falhas, permitindo que o sistema se recupere e que a API tenha tempo para se restabelecer.
Como funciona o circuit breaker
O circuit breaker opera em três estados, alternando entre eles conforme o comportamento da API.
| Estado | Comportamento | Transição |
|---|---|---|
| Fechado (Closed) | Requisições passam normalmente | Abre se falhas excederem o limiar |
| Aberto (Open) | Requisições são rejeitadas imediatamente | Muda para half-open após timeout |
| Semi-aberto (Half-Open) | Permite uma requisição de teste | Fecha se sucesso, abre se falha |
Fechado -- o circuito permite todas as requisições e monitora a taxa de falhas.
Aberto -- o circuito rejeita requisições sem tentar, retornando um erro imediato ou resposta de fallback.
Semi-aberto -- o circuito permite uma requisição teste; se bem-sucedida, retorna ao estado fechado.
Implementação manual do circuit breaker
Uma implementação manual oferece controle total sobre o comportamento do circuit breaker.
package circuitbreaker
import (
"errors"
"sync"
"time"
)
type Estado int
const (
Fechado Estado = iota
Aberto
SemiAberto
)
func (e Estado) String() string {
nomes := [...]string{"Fechado", "Aberto", "SemiAberto"}
return nomes[e]
}
var (
ErrCircuitoAberto = errors.New("circuito aberto: requisicoes bloqueadas")
)
type CircuitBreaker struct {
mu sync.Mutex
estado Estado
falhasConsecutivas int
limiarFalhas int
timeoutAberto time.Duration
ultimaFalha time.Time
totalFalhas int64
totalSucessos int64
}
func New(limiarFalhas int, timeoutAberto time.Duration) *CircuitBreaker {
return &CircuitBreaker{
estado: Fechado,
limiarFalhas: limiarFalhas,
timeoutAberto: timeoutAberto,
}
}
func (cb *CircuitBreaker) Executar(fn func() error) error {
cb.mu.Lock()
switch cb.estado {
case Aberto:
if time.Since(cb.ultimaFalha) > cb.timeoutAberto {
cb.estado = SemiAberto
cb.mu.Unlock()
return cb.tentarExecucao(fn)
}
cb.mu.Unlock()
return ErrCircuitoAberto
case SemiAberto:
cb.mu.Unlock()
return cb.tentarExecucao(fn)
default: // Fechado
cb.mu.Unlock()
return cb.tentarExecucao(fn)
}
}
func (cb *CircuitBreaker) tentarExecucao(fn func() error) error {
err := fn()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.falhasConsecutivas++
cb.totalFalhas++
cb.ultimaFalha = time.Now()
if cb.falhasConsecutivas >= cb.limiarFalhas {
cb.estado = Aberto
}
return err
}
cb.falhasConsecutivas = 0
cb.totalSucessos++
if cb.estado == SemiAberto {
cb.estado = Fechado
}
return nil
}
func (cb *CircuitBreaker) Estado() Estado {
cb.mu.Lock()
defer cb.mu.Unlock()
return cb.estado
}
func (cb *CircuitBreaker) Estatisticas() map[string]interface{} {
cb.mu.Lock()
defer cb.mu.Unlock()
return map[string]interface{}{
"estado": cb.estado.String(),
"falhas_consecutivas": cb.falhasConsecutivas,
"total_falhas": cb.totalFalhas,
"total_sucessos": cb.totalSucessos,
}
}
Integração com o cliente de CPF
Integre o circuit breaker ao cliente HTTP que consulta a API de CPF.
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
"seu-projeto/circuitbreaker"
)
type CPFClient struct {
httpClient *http.Client
apiKey string
cb *circuitbreaker.CircuitBreaker
}
type ResultadoCPF struct {
Sucesso bool `json:"sucesso"`
Nome string `json:"nome,omitempty"`
Fonte string `json:"fonte"`
Erro string `json:"erro,omitempty"`
}
func NewCPFClient(apiKey string) *CPFClient {
return &CPFClient{
httpClient: &http.Client{Timeout: 10 * time.Second},
apiKey: apiKey,
cb: circuitbreaker.New(5, 30*time.Second),
}
}
func (c *CPFClient) Consultar(cpf string) ResultadoCPF {
var resultado ResultadoCPF
err := c.cb.Executar(func() error {
url := fmt.Sprintf("https://api.cpfhub.io/cpf/%s", cpf)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("x-api-key", c.apiKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return fmt.Errorf("servidor retornou %d", resp.StatusCode)
}
var apiResp struct {
Success bool `json:"success"`
Data struct {
Name string `json:"name"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&apiResp)
resultado = ResultadoCPF{
Sucesso: apiResp.Success,
Nome: apiResp.Data.Name,
Fonte: "api",
}
return nil
})
if err != nil {
if err == circuitbreaker.ErrCircuitoAberto {
return ResultadoCPF{
Sucesso: false,
Fonte: "circuit_breaker",
Erro: "Servico temporariamente indisponivel",
}
}
return ResultadoCPF{
Sucesso: false,
Fonte: "erro",
Erro: err.Error(),
}
}
return resultado
}
Usando a biblioteca gobreaker
A biblioteca sony/gobreaker oferece uma implementação pronta e testada. O padrão circuit breaker é amplamente documentado em arquitetura de microsserviços — o OWASP recomenda sua adoção como controle de resiliência para dependências externas.
package main
import (
"fmt"
"time"
"github.com/sony/gobreaker"
)
func criarCircuitBreaker() *gobreaker.CircuitBreaker {
config := gobreaker.Settings{
Name: "cpfhub-api",
MaxRequests: 3, // Requisições em half-open
Interval: 60 * time.Second, // Intervalo para resetar contadores
Timeout: 30 * time.Second, // Tempo no estado aberto
ReadyToTrip: func(counts gobreaker.Counts) bool {
taxaFalha := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 10 && taxaFalha >= 0.6
},
OnStateChange: func(name string, from, to gobreaker.State) {
fmt.Printf("[CircuitBreaker] %s: %s -> %s\n", name, from, to)
},
}
return gobreaker.NewCircuitBreaker(config)
}
| Parâmetro | Valor | Descrição |
|---|---|---|
| MaxRequests | 3 | Requisições permitidas em half-open |
| Interval | 60s | Intervalo para resetar contadores em closed |
| Timeout | 30s | Tempo que permanece em open |
| Limiar de trip | 60% de falha em 10+ requests | Quando abrir o circuito |
Fallback e degradação graceful
Quando o circuito está aberto, ofereça respostas de fallback ao invés de simplesmente retornar erro.
func (c *CPFClient) ConsultarComFallback(cpf string) ResultadoCPF {
resultado := c.Consultar(cpf)
if resultado.Fonte == "circuit_breaker" {
// Tentar cache local
if dadosCache, ok := c.buscarCache(cpf); ok {
return ResultadoCPF{
Sucesso: true,
Nome: dadosCache.Nome,
Fonte: "cache_fallback",
}
}
// Retornar resposta degradada
return ResultadoCPF{
Sucesso: false,
Fonte: "degradado",
Erro: "Servico de validacao temporariamente indisponivel. " +
"O CPF sera validado posteriormente.",
}
}
return resultado
}
Cache como fallback -- servir dados previamente obtidos quando o circuito está aberto.
Resposta degradada -- informar o usuário que a validação ocorrerá posteriormente.
Fila de reprocessamento -- enfileirar CPFs que não puderam ser validados para reprocessar quando a API voltar.
Perguntas frequentes
Qual limiar de falhas configurar no circuit breaker para a API de CPF?
Para a CPFHub.io, um limiar de 5 falhas consecutivas com timeout de 30 segundos é um bom ponto de partida. Em produção, calibre com base no volume de requisições: se o sistema faz menos de 10 chamadas por minuto, falhas consecutivas são um indicador mais confiável que taxa percentual. Para volumes maiores, use a abordagem gobreaker com ReadyToTrip baseado em taxa (ex.: 60% de falha em 10+ requests).
O circuit breaker deve ser compartilhado entre goroutines?
Sim, e é exatamente por isso que a implementação usa sync.Mutex. Uma única instância de CircuitBreaker deve ser criada na inicialização e compartilhada por todas as goroutines que consultam a API. Criar uma instância por goroutine anula o propósito do padrão, já que cada instância teria seu próprio contador de falhas independente.
Como testar o circuit breaker em ambiente de desenvolvimento?
Use um mock server que retorna erros HTTP 500 para simular indisponibilidade da API. Configure o limiar de falhas para um valor baixo (ex.: 2 falhas) no ambiente de teste, dispare as requisições em sequência e verifique que a terceira retorna ErrCircuitoAberto sem chamar o mock server. O httptest.NewServer do Go facilita essa abordagem.
O que acontece com as requisições quando o circuito está semi-aberto?
No estado semi-aberto, apenas uma requisição de teste é permitida. Se ela for bem-sucedida, o circuito fecha e o tráfego normal é retomado. Se falhar, o circuito volta para aberto e reinicia o timeout. Isso evita sobrecarregar uma API que acabou de se recuperar com o volume total de requisições acumulado durante o período de indisponibilidade.
Conclusão
O padrão circuit breaker é essencial para qualquer aplicação que depende de APIs externas como a de CPF. Em Go, a implementação é direta graças às goroutines e mutexes, e bibliotecas como sony/gobreaker oferecem soluções prontas para produção. Combinado com fallbacks e degradação graceful, o circuit breaker garante que uma falha na API de CPF não derrube toda a aplicação.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e integre a API de CPF com resiliência na sua aplicação Go.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



