Como Implementar Circuit Breaker ao Consumir APIs de CPF em Go

Aprenda a implementar o padrão circuit breaker em Go para consumir APIs de CPF com resiliência, evitando falhas em cascata e degradação graceful.

Redação CPFHub.io
Redação CPFHub.io
··8 min de leitura
Como Implementar Circuit Breaker ao Consumir APIs de CPF em Go

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.

EstadoComportamentoTransição
Fechado (Closed)Requisições passam normalmenteAbre se falhas excederem o limiar
Aberto (Open)Requisições são rejeitadas imediatamenteMuda para half-open após timeout
Semi-aberto (Half-Open)Permite uma requisição de testeFecha 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âmetroValorDescrição
MaxRequests3Requisições permitidas em half-open
Interval60sIntervalo para resetar contadores em closed
Timeout30sTempo que permanece em open
Limiar de trip60% de falha em 10+ requestsQuando 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.

Redação CPFHub.io

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.

WhatsAppFale conosco via WhatsApp