Getting started
Install relay, build a client, and send your first resilient request in a few minutes.
Requirements
relay targets Go 1.21 and newer. It has no third-party dependencies, so a standard toolchain is all you need. Verify your version:
go version
Installation
Add the module to your project:
go get relay.example/relay@latest
Pin a specific release if you prefer reproducible builds:
go get relay.example/relay@v1.2.1
relay.example/relay is a documentation
placeholder import path. Use the module path of your own checkout. Nothing
in relay contacts the network except the requests you make.
Your first request
The core type is relay.Client. Build one with
relay.New and a set of options, then use it like the standard
client. Every method takes a context.Context as its first
argument.
package main
import (
"context"
"fmt"
"io"
"time"
"relay.example/relay"
)
func main() {
client := relay.New(
relay.WithTimeout(time.Second),
relay.WithRetry(relay.RetryPolicy{MaxAttempts: 3}),
)
ctx := context.Background()
resp, err := client.Get(ctx, "http://example.internal/v1/status")
if err != nil {
panic(err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(b))
}
How the pieces fit
A single call to Do flows through three layers, outermost
first:
- Circuit breaker. If the breaker is open, the call
returns
relay.ErrCircuitOpenimmediately without touching the network. - Retry loop. While attempts remain and the parent context is still valid, relay sends the request. Failures classified as retryable trigger a backoff sleep before the next attempt.
- Per-attempt timeout. Each individual attempt runs under
a derived context bounded by
WithTimeout, so one stalled call cannot consume the whole budget.
The parent context you pass always wins: if it is cancelled or its deadline passes, relay stops retrying and returns the context error.
Reuse one client
Create a client once and share it. It holds a connection pool through the underlying transport and a breaker whose state is meaningful only when shared across requests to the same upstream.
// good: one client per upstream, stored on your service struct
type Service struct {
billing *relay.Client
}
func NewService() *Service {
return &Service{
billing: relay.New(relay.WithTimeout(2 * time.Second)),
}
}
Creating a fresh client per request defeats both connection pooling and the circuit breaker, since each new breaker starts closed with no failure history.
Next steps
- Tune behaviour in Configuration.
- Look up exact signatures in the API reference.
- Copy a pattern from Examples.