go 35 lines · 1 tab

Circuit breaker around flaky dependencies

Leah Thompson Jan 2026
1 tab
package deps

import (
  "context"
  "net/http"
  "time"

  "github.com/sony/gobreaker"
)

type Client struct {
  http *http.Client
  cb   *gobreaker.CircuitBreaker
}

func NewClient(httpClient *http.Client) *Client {
  st := gobreaker.Settings{
    Name:        "billing-api",
    MaxRequests: 5,
    Interval:    30 * time.Second,
    Timeout:     10 * time.Second,
    ReadyToTrip: func(c gobreaker.Counts) bool { return c.ConsecutiveFailures >= 5 },
  }
  return &Client{http: httpClient, cb: gobreaker.NewCircuitBreaker(st)}
}

func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
  v, err := c.cb.Execute(func() (any, error) {
    return c.http.Do(req.WithContext(ctx))
  })
  if err != nil {
    return nil, err
  }
  return v.(*http.Response), nil
}
1 file · go Explain with highlit

Retries alone can make an outage worse: if a dependency is hard failing, retries just add load. A circuit breaker adds a simple state machine: closed (normal), open (fail fast), and half-open (probe). I like gobreaker because it’s small and predictable. The important detail is what counts as a failure: timeouts and 5xx responses should trip the breaker; validation errors should not. I also emit breaker state changes as logs/metrics so you can see when the system is protecting itself. The result is better tail latency and less cascading failure, especially for high QPS services that call a single upstream. Pair it with request-level context.WithTimeout and you get a clean “fail fast” behavior.


Related snips

Share this code

Here's the card — post it anywhere.

Circuit breaker around flaky dependencies — share card
Link copied