Getting started¶
Install¶
The simplest path (facade)¶
Use recourse.Do / recourse.DoValue for the “zero config” path:
user, err := recourse.DoValue[User](ctx, "user-service.GetUser", func(ctx context.Context) (User, error) {
return client.GetUser(ctx, userID)
})
Keys must be low-cardinality (stable across requests). Good: "payments.Charge". Bad: "GET /users/123".
Getting a timeline¶
// Start timeline capture
ctx, capture := observe.RecordTimeline(ctx)
user, err := recourse.DoValue(ctx, "user-service.GetUser", op)
// Access timeline (safe even after function returns)
tl := capture.Timeline()
for _, a := range tl.Attempts {
// inspect outcome, error, backoff, budget gating, timings
}
Standard usage (custom defaults)¶
For most applications, you want standard defaults (HTTP classification, unlimited budgets, p99 hedging) but with your own instance.
// Create a pre-configured executor
exec := retry.NewDefaultExecutor()
// Use it
user, err := retry.DoValue[User](ctx, exec, "user-service.GetUser", op)
NewDefaultExecutor comes with:
- Classifiers:
AutoClassifier(handles HTTP, generic errors, and registered integrations like gRPC) - Budgets:
UnlimitedBudgetregistered as"unlimited" - Hedging:
FixedDelayandLatency(p90,p99) triggers registered
Explicit wiring (advanced)¶
If you want to supply policies, classifiers, and budgets explicitly, build a retry.Executor and either use it directly or initialize the facade:
budgets := budget.NewRegistry()
if err := budgets.Register("global", budget.NewTokenBucketBudget(100, 50)); err != nil { // capacity=100, refill=50 tokens/sec
panic(err)
}
pol := policy.New("user-service.GetUser",
policy.MaxAttempts(3),
policy.Classifier(classify.ClassifierHTTP),
policy.Budget("global"),
// Enable Hedging (fixed delay)
policy.EnableHedging(),
policy.HedgeDelay(10*time.Millisecond),
policy.HedgeMaxAttempts(2),
)
// Enable Circuit Breaking
pol.Circuit = policy.CircuitPolicy{
Enabled: true,
Threshold: 5,
Cooldown: 10 * time.Second,
}
key := pol.Key
provider := &controlplane.StaticProvider{
Policies: map[policy.PolicyKey]policy.EffectivePolicy{
pol.Key: pol,
},
}
exec := retry.NewExecutor(
retry.WithProvider(provider),
retry.WithBudgetRegistry(budgets),
)
recourse.Init(exec)
user, err := recourse.DoValue[User](ctx, key.String(), op)
// Or call retry directly when you want to pass the executor explicitly:
// user, err := retry.DoValue[User](ctx, exec, key, op)