ambatukam

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 17 Imported by: 0

README ΒΆ

Ambatukam Go

Ambatukam Go

Composable, idiomatic Go HTTP resilience.
Retry Β· Circuit Breaker Β· Bulkhead Β· Rate Limiter Β· Timeout Β· Hooks

CI Go Reference Go Report Card License Stars

One library. One API. Zero dependencies. Production-grade resilience in 10 lines.


Why Ambatukam Go?

Every Go backend that calls external services needs the same five things: retry on transient failures, circuit breaker to fail fast when downstream is down, bulkhead to limit concurrency, rate limiting to respect API quotas, and per-attempt timeout to bound latency.

Most teams stitch together 3–5 different libraries and write glue code nobody owns.

Ambatukam Go is one library with one API.

Feature Ambatukam Go Stitched Stack
Retry with backoff + jitter βœ… cenkalti/backoff
Circuit breaker (closed/open/half-open) βœ… sony/gobreaker
Bulkhead (concurrency limit) βœ… DIY or slok/goresilience
Rate limiter (token bucket) βœ… golang.org/x/time/rate
Per-attempt timeout βœ… manual
Body buffering for safe POST retry βœ… DIY (often broken)
Retry-After header support βœ… most libs skip
Generic JSON helpers βœ… DIY
Request ID propagation βœ… DIY
Hooks (auth, logging, metrics) βœ… varies
Composable policies (Chain) βœ… manual
Zero dependencies βœ… n deps

Install

go get github.com/farhanturu/ambatukam-go

Requires Go 1.21+ (uses generics, slog, atomic.Int64).


Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/farhanturu/ambatukam-go"
)

func main() {
    client := ambatukam.New(
        ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 2 * time.Second}),
        ambatukam.WithRetry(ambatukam.RetryConfig{MaxRetries: 3}),
        ambatukam.WithCircuitBreaker(ambatukam.CircuitConfig{FailureThreshold: 5}),
        ambatukam.WithBulkhead(ambatukam.BulkheadConfig{MaxConcurrent: 10}),
        ambatukam.WithRateLimit(ambatukam.RateLimitConfig{Rate: 10, Burst: 5}),
    )
    defer client.Close()

    resp, err := client.Get(context.Background(), "https://api.example.com/users")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.StatusCode)
}

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Client.Do()                         β”‚
β”‚                                                          β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚ Retry  β”‚β†’β”‚ Circuit   β”‚β†’β”‚Bulkheadβ”‚β†’β”‚Rate Limitβ”‚   β”‚
β”‚   β”‚        β”‚  β”‚ Breaker   β”‚  β”‚        β”‚  β”‚          β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚        ↓           ↓             ↓            ↓         β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚   β”‚     Timeout Β· Request ID Β· Hooks                 β”‚  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                         ↓                                β”‚
β”‚                   http.Client.Do()                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Policies are composable middleware β€” outer-to-inner order: retry β†’ circuit β†’ timeout β†’ HTTP.


Features

πŸ”„ Retry with Backoff

ambatukam.WithRetry(ambatukam.RetryConfig{
    MaxRetries:     3,
    InitialBackoff: 100 * time.Millisecond,
    MaxBackoff:     5 * time.Second,
    Multiplier:     2.0,
    Jitter:         0.2,
})

Three strategies: ExponentialBackoff, ConstantBackoff, LinearBackoff.

Body buffering is automatic β€” POST bodies are read once and replayed on each retry. Only idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) retry by default; opt in for POST with a custom ShouldRetry.

⚑ Circuit Breaker

ambatukam.WithCircuitBreaker(ambatukam.CircuitConfig{
    FailureThreshold: 5,
    OpenDuration:     30 * time.Second,
    HalfOpenMaxReqs:  1,
})

Three-state machine: closed β†’ open β†’ half-open. Race-safe under concurrent load with atomic generation counters.

🚧 Bulkhead (Concurrency Limit)

ambatukam.WithBulkhead(ambatukam.BulkheadConfig{
    MaxConcurrent: 10,
    MaxQueue:      100,
    QueueTimeout:  50 * time.Millisecond,
})

Limits in-flight requests to downstream. Optional bounded queue with timeout.

🚦 Rate Limiter

ambatukam.WithRateLimit(ambatukam.RateLimitConfig{
    Rate:        10,                     // tokens per second
    Burst:       5,                      // bucket capacity
    WaitTimeout: 100 * time.Millisecond, // 0 = fail fast
})

Token bucket. Rate <= 0 denies all requests (fail-closed).

⏱️ Timeout

ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 2 * time.Second})

Per-attempt deadline. Parent ctx cancellation takes precedence.

🏷️ Request ID Propagation

ambatukam.WithRequestID("X-Request-ID") // empty = default header

Auto-generates a 12-byte hex ID per request, or propagates an existing one.

πŸͺ Hooks

ambatukam.WithHooks(ambatukam.Hooks{
    BeforeRequest: func(req *http.Request) error {
        req.Header.Set("Authorization", "Bearer "+token)
        return nil
    },
    OnRetry: func(req *http.Request, attempt int, nextDelay time.Duration) {
        log.Printf("retrying %s (attempt %d, delay %v)", req.URL, attempt, nextDelay)
    },
    OnStateChange: func(name string, from, to ambatukam.State) {
        metrics.Gauge("circuit_state").Set(string(to))
    },
})

Four callbacks: BeforeRequest, AfterResponse, OnRetry, OnStateChange. All optional.

πŸ“¦ Generic JSON Helpers

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

u, err := ambatukam.GetJSON[User](client, ctx, "https://api.example.com/users/1")

created, err := ambatukam.PostJSON[User](client, ctx, "https://api.example.com/users", User{Name: "bob"})

Auto-handles JSON encode/decode, content-type, and 4xx/5xx errors as RequestError.


Preset Configs

Ready-to-use configurations for common scenarios:

// Balanced production defaults
client := ambatukam.New(ambatukam.ProductionConfig()...)

// Strict, fast-fail for fragile downstreams
client := ambatukam.New(ambatukam.AggressiveConfig()...)

// Generous config for critical services
client := ambatukam.New(ambatukam.ConservativeConfig()...)
Preset Retries Timeout Circuit Threshold Bulkhead
Production 3 30s 5 failures NumCPUΓ—4
Aggressive 1 5s 3 failures NumCPUΓ—2
Conservative 5 60s 20 failures NumCPUΓ—8

Patterns

Stripe / Payment Gateway

client := ambatukam.New(
    ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 10 * time.Second}),
    ambatukam.WithRetry(ambatukam.RetryConfig{
        MaxRetries: 3,
        Backoff:    ambatukam.ConstantBackoff(500 * time.Millisecond),
    }),
    ambatukam.WithCircuitBreaker(ambatukam.CircuitConfig{FailureThreshold: 5}),
)

Microservice with Auth + Tracing

client := ambatukam.New(
    ambatukam.WithRequestID("X-Request-ID"),
    ambatukam.WithHooks(ambatukam.Hooks{
        BeforeRequest: func(req *http.Request) error {
            req.Header.Set("Authorization", "Bearer "+getToken())
            return nil
        },
    }),
    ambatukam.WithRetry(ambatukam.DefaultRetryConfig()),
)

Third-Party API with Rate Limit

client := ambatukam.New(
    ambatukam.WithRateLimit(ambatukam.RateLimitConfig{
        Rate:        5,
        Burst:       10,
        WaitTimeout: 2 * time.Second,
    }),
    ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 30 * time.Second}),
)

Custom Composition Order

client := ambatukam.New(ambatukam.WithPolicy(ambatukam.Chain(
    ambatukam.NewRetry(ambatukam.DefaultRetryConfig()),
    ambatukam.NewCircuitBreaker(ambatukam.DefaultCircuitConfig()),
    ambatukam.NewTimeout(ambatukam.TimeoutConfig{Timeout: 5 * time.Second}),
)))

Order matters: outer-to-inner is [retry [circuit [timeout [HTTP]]]].


Error Handling

Use errors.Is to distinguish error types:

resp, err := client.Get(ctx, url)
switch {
case errors.Is(err, ambatukam.ErrCircuitOpen):    // downstream is down
case errors.Is(err, ambatukam.ErrMaxRetries):     // gave up after N attempts
case errors.Is(err, ambatukam.ErrTimeout):        // attempt hit its deadline
case errors.Is(err, ambatukam.ErrBulkheadFull):   // at concurrency cap
case errors.Is(err, ambatukam.ErrRateLimited):    // rate-limited
case errors.Is(err, ambatukam.ErrNilRequest):     // programming error
case errors.Is(err, context.Canceled):            // ctx was canceled
}

For full context (method, URL, status, attempts):

var reqErr *ambatukam.RequestError
if errors.As(err, &reqErr) {
    log.Printf("%s %s returned %d after %d attempts",
        reqErr.Method, reqErr.URL, reqErr.Status, reqErr.Attempts)
}

Mark errors as non-retryable:

resp, err := client.Get(ctx, url)
if err != nil {
    return ambatukam.Permanent(err) // skip retry
}

Benchmarks

Measured on Intel Core i5-8250U @ 1.60GHz, Linux, Go 1.21 (go test -bench=. -benchmem -benchtime=2s).

Setup ns/op B/op allocs/op
http.Client (raw stdlib) 98,785 5,106 63
Ambatukam Go (no policies) 98,325 4,466 57
Ambatukam Go (retry=3) 96,879 4,505 58
Ambatukam Go (full stack) 235,341 16,757 120
Ambatukam Go (parallel) 26,064 8,214 77

Run locally: go test -bench=. -benchmem -benchtime=2s ./...


Migration

From cenkalti/backoff + sony/gobreaker
// Before: two libraries, manual wiring
import (
    "github.com/cenkalti/backoff/v4"
    "github.com/sony/gobreaker"
)

// After: one library, one config
import "github.com/farhanturu/ambatukam-go"

client := ambatukam.New(
    ambatukam.WithRetry(ambatukam.RetryConfig{MaxRetries: 3}),
    ambatukam.WithCircuitBreaker(ambatukam.CircuitConfig{FailureThreshold: 5}),
)
From hashicorp/go-retryablehttp

Ambatukam Go's *Client is a drop-in *http.Client. Wrap your existing transport via WithHTTPClient, or use Do/Get/Post directly. Adds circuit breaker, bulkhead, rate limit, hooks, and request ID.

From slok/goresilience

Both use a runner/middleware pattern. See MIGRATION.md for a detailed guide.


Documentation

Document Description
README.md You are here
COOKBOOK.md Recipes for common patterns
FAQ.md Frequently asked questions
MIGRATION.md Migrating from other libraries
CONTRIBUTING.md How to contribute
SECURITY.md Security policy

Troubleshooting

Problem Solution
POST isn't being retried Only idempotent methods retry by default. Use custom ShouldRetry or idempotency-key header.
Circuit opens too often Lower FailureThreshold or increase OpenDuration. Use OnStateChange hook to monitor.
Bulkhead denies immediately Increase MaxQueue or MaxConcurrent. See COOKBOOK.
Rate limit denies unexpectedly Rate == 0 = disabled, Rate < 0 = deny all. Verify your config value.
Need debug logging Use ambatukam.WithDebug() for verbose logging.

Roadmap

v1.0 (current)

Retry, circuit breaker, bulkhead, rate limiter, timeout, request ID, hooks, generic JSON helpers, permanent errors, preset configs.

v1.1 (next)

Hedged requests (parallel speculative retries), fallback strategy (return stale data on failure), request deduplication (singleflight).

v2.0

OpenTelemetry tracing + Prometheus metrics, adaptive timeout (based on p99 latency), distributed (Redis-backed) circuit breaker, gRPC support.


Contributing

PRs welcome. Run go test ./... and go vet ./... before submitting; add a test for any new behavior. See CONTRIBUTING.md.


License

MIT β€” see LICENSE.


Ambatukam Go
Built with ❀️ by farhanturu

Documentation ΒΆ

Index ΒΆ

Examples ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	ErrCircuitOpen  = errors.New("ambatukam: circuit breaker is open")
	ErrMaxRetries   = errors.New("ambatukam: max retries exceeded")
	ErrNilRequest   = errors.New("ambatukam: nil request")
	ErrTimeout      = errors.New("ambatukam: per-attempt timeout exceeded")
	ErrBulkheadFull = errors.New("ambatukam: bulkhead full")
	ErrRateLimited  = errors.New("ambatukam: rate limited")
	ErrFallback     = errors.New("ambatukam: fallback failed")
)

Functions ΒΆ

func GetJSON ΒΆ

func GetJSON[T any](c *Client, ctx context.Context, url string) (T, error)
Example ΒΆ

ExampleGetJSON demonstrates the typed JSON helper.

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	fmt.Fprint(w, `{"name":"alice","age":30}`)
}))
defer srv.Close()

client := ambatukam.New()
defer client.Close()

u, err := ambatukam.GetJSON[testUser](client, context.Background(), srv.URL)
if err != nil {
	fmt.Println("error:", err)
	return
}
fmt.Println(u.Name, u.Age)
Output:
alice 30

func Permanent ΒΆ

func Permanent(err error) error
Example ΒΆ

ExamplePermanent demonstrates marking an error as non-retryable.

package main

import (
	"errors"
	"fmt"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	err := ambatukam.Permanent(errors.New("do not retry me"))
	fmt.Println(err)
}
Output:
permanent: do not retry me

func PostJSON ΒΆ

func PostJSON[T any](c *Client, ctx context.Context, url string, body any) (T, error)
Example ΒΆ

ExamplePostJSON demonstrates the typed JSON POST helper.

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(201)
	fmt.Fprint(w, `{"name":"bob","age":25}`)
}))
defer srv.Close()

client := ambatukam.New()
defer client.Close()

out, err := ambatukam.PostJSON[testUser](client, context.Background(), srv.URL, testUser{Name: "alice", Age: 30})
if err != nil {
	fmt.Println("error:", err)
	return
}
fmt.Println(out.Name, out.Age)
Output:
bob 25

Types ΒΆ

type Backoff ΒΆ

type Backoff interface {
	NextDelay(attempt int) time.Duration
}

func ConstantBackoff ΒΆ

func ConstantBackoff(d time.Duration) Backoff

func ExponentialBackoff ΒΆ

func ExponentialBackoff(initial, max time.Duration, multiplier float64) Backoff

func LinearBackoff ΒΆ

func LinearBackoff(initial, max, step time.Duration) Backoff

type BulkheadConfig ΒΆ

type BulkheadConfig struct {
	MaxConcurrent uint32
	MaxQueue      uint32
	QueueTimeout  time.Duration
}

type BulkheadPolicy ΒΆ

type BulkheadPolicy struct {
	// contains filtered or unexported fields
}

func NewBulkhead ΒΆ

func NewBulkhead(cfg BulkheadConfig) *BulkheadPolicy
Example ΒΆ

ExampleNewBulkhead demonstrates configuring a bulkhead.

package main

import (
	"fmt"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	cfg := ambatukam.BulkheadConfig{
		MaxConcurrent: 5,
		MaxQueue:      10,
		QueueTimeout:  100 * time.Millisecond,
	}

	client := ambatukam.New(ambatukam.WithBulkhead(cfg))
	defer client.Close()

	fmt.Println("max concurrent:", cfg.MaxConcurrent)
}
Output:
max concurrent: 5

func (*BulkheadPolicy) Denied ΒΆ

func (b *BulkheadPolicy) Denied() uint64

func (*BulkheadPolicy) Execute ΒΆ

func (b *BulkheadPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*BulkheadPolicy) InFlight ΒΆ

func (b *BulkheadPolicy) InFlight() uint32

func (*BulkheadPolicy) WithLogger ΒΆ

func (b *BulkheadPolicy) WithLogger(l *slog.Logger) *BulkheadPolicy

type CircuitBreakerPolicy ΒΆ

type CircuitBreakerPolicy struct {
	// contains filtered or unexported fields
}

func NewCircuitBreaker ΒΆ

func NewCircuitBreaker(cfg CircuitConfig) *CircuitBreakerPolicy
Example ΒΆ

ExampleNewCircuitBreaker demonstrates configuring the circuit breaker.

package main

import (
	"fmt"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	cfg := ambatukam.DefaultCircuitConfig()
	cfg.FailureThreshold = 3
	cfg.OpenDuration = 10 * time.Second

	client := ambatukam.New(ambatukam.WithCircuitBreaker(cfg))
	defer client.Close()

	fmt.Println("failure threshold:", cfg.FailureThreshold)
}
Output:
failure threshold: 3

func (*CircuitBreakerPolicy) Execute ΒΆ

func (cb *CircuitBreakerPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*CircuitBreakerPolicy) State ΒΆ

func (cb *CircuitBreakerPolicy) State() State

func (*CircuitBreakerPolicy) WithHooks ΒΆ

func (*CircuitBreakerPolicy) WithLogger ΒΆ

func (*CircuitBreakerPolicy) WithName ΒΆ

func (cb *CircuitBreakerPolicy) WithName(name string) *CircuitBreakerPolicy

type CircuitConfig ΒΆ

type CircuitConfig struct {
	FailureThreshold uint32
	OpenDuration     time.Duration
	HalfOpenMaxReqs  uint32
	ShouldTrip       func(resp *http.Response, err error) bool
}

func DefaultCircuitConfig ΒΆ

func DefaultCircuitConfig() CircuitConfig

type Client ΒΆ

type Client struct {
	// contains filtered or unexported fields
}

func New ΒΆ

func New(opts ...Option) *Client

func NewDefaultClient ΒΆ

func NewDefaultClient() *Client
Example ΒΆ

ExampleNewDefaultClient demonstrates the one-call production-default client.

package main

import (
	"fmt"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.NewDefaultClient()
	defer client.Close()
	fmt.Println("ok")
}
Output:
ok

func (*Client) Close ΒΆ

func (c *Client) Close() error

func (*Client) Do ΒΆ

func (c *Client) Do(req *http.Request) (*http.Response, error)

func (*Client) DoWithContext ΒΆ

func (c *Client) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error)

func (*Client) Get ΒΆ

func (c *Client) Get(ctx context.Context, url string) (*http.Response, error)
Example ΒΆ

ExampleClient_Get demonstrates a basic GET request with all three policies.

package main

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

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, `{"hello":"world"}`)
	}))
	defer srv.Close()

	client := ambatukam.New(
		ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 2 * time.Second}),
		ambatukam.WithRetry(ambatukam.DefaultRetryConfig()),
		ambatukam.WithCircuitBreaker(ambatukam.DefaultCircuitConfig()),
	)
	defer client.Close()

	resp, err := client.Get(context.Background(), srv.URL)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Println("status:", resp.StatusCode)
}
Output:
status: 200

func (*Client) HealthChecker ΒΆ added in v1.1.0

func (c *Client) HealthChecker() *HealthChecker

func (*Client) Post ΒΆ

func (c *Client) Post(ctx context.Context, url, contentType string, body io.Reader) (*http.Response, error)
Example ΒΆ

ExampleClient_Post demonstrates a POST with a JSON body.

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)
		fmt.Println("server received:", string(body))
		w.WriteHeader(http.StatusCreated)
	}))
	defer srv.Close()

	client := ambatukam.New()
	defer client.Close()

	resp, err := client.Post(
		context.Background(),
		srv.URL+"/users",
		"application/json",
		io.NopCloser(strings.NewReader(`{"name":"alice"}`)),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Println("status:", resp.StatusCode)
}
Output:
server received: {"name":"alice"}
status: 201

func (*Client) RoundTrip ΒΆ

func (c *Client) RoundTrip(req *http.Request) (*http.Response, error)
Example ΒΆ

ExampleClient_RoundTrip shows how to use an amba *Client as the Transport of a standard *http.Client.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
	}))
	defer srv.Close()

	amba := ambatukam.New(ambatukam.WithRetry(ambatukam.RetryConfig{MaxRetries: 2}))
	defer amba.Close()

	hc := &http.Client{Transport: amba.Transport()}
	resp, err := hc.Get(srv.URL)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Println("status:", resp.StatusCode)
}
Output:
status: 200

func (*Client) Transport ΒΆ

func (c *Client) Transport() http.RoundTripper

type FallbackConfig ΒΆ added in v1.1.0

type FallbackConfig struct {
	Handler func(req *http.Request, err error) (*http.Response, error)
}

type FallbackPolicy ΒΆ added in v1.1.0

type FallbackPolicy struct {
	// contains filtered or unexported fields
}

func NewFallback ΒΆ added in v1.1.0

func NewFallback(cfg FallbackConfig) *FallbackPolicy

func (*FallbackPolicy) Execute ΒΆ added in v1.1.0

func (f *FallbackPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*FallbackPolicy) WithHooks ΒΆ added in v1.1.0

func (f *FallbackPolicy) WithHooks(h Hooks) *FallbackPolicy

type HealthChecker ΒΆ added in v1.1.0

type HealthChecker struct {
	// contains filtered or unexported fields
}

func NewHealthChecker ΒΆ added in v1.1.0

func NewHealthChecker(c *Client) *HealthChecker

func (*HealthChecker) Handler ΒΆ added in v1.1.0

func (h *HealthChecker) Handler() http.HandlerFunc

type HealthStatus ΒΆ added in v1.1.0

type HealthStatus struct {
	Status    string            `json:"status"`
	Timestamp time.Time         `json:"timestamp"`
	Uptime    time.Duration     `json:"uptime"`
	Policies  map[string]string `json:"policies"`
	Memory    MemoryStats       `json:"memory"`
}

type Hooks ΒΆ

type Hooks struct {
	BeforeRequest func(ctx *http.Request) error
	AfterResponse func(req *http.Request, resp *http.Response, err error)
	OnRetry       func(req *http.Request, attempt int, nextDelay time.Duration)
	OnStateChange func(name string, from, to State)
	OnFallback    func(req *http.Request, err error)
}

type MemoryStats ΒΆ added in v1.1.0

type MemoryStats struct {
	Alloc      uint64 `json:"alloc_bytes"`
	TotalAlloc uint64 `json:"total_alloc_bytes"`
	Sys        uint64 `json:"sys_bytes"`
	NumGC      uint32 `json:"num_gc"`
}

type MetricsRecorder ΒΆ added in v1.1.0

type MetricsRecorder interface {
	RecordRequest(method, url string, status int, duration time.Duration)
	RecordRetry(method, url string, attempt int)
	RecordCircuitStateChange(name string, from, to State)
	RecordBulkheadDenied(method, url string)
	RecordRateLimitDenied(method, url string)
	RecordFallback(method, url string)
	RecordTimeout(method, url string)
}

func NewNoopMetricsRecorder ΒΆ added in v1.1.0

func NewNoopMetricsRecorder() MetricsRecorder

type Option ΒΆ

type Option func(*Client)

func AggressiveConfig ΒΆ

func AggressiveConfig() []Option

func ConservativeConfig ΒΆ

func ConservativeConfig() []Option

func DefaultConfig ΒΆ

func DefaultConfig() []Option

func ProductionConfig ΒΆ

func ProductionConfig() []Option

func WithBulkhead ΒΆ

func WithBulkhead(cfg BulkheadConfig) Option

func WithCircuitBreaker ΒΆ

func WithCircuitBreaker(cfg CircuitConfig) Option

func WithDebug ΒΆ

func WithDebug() Option

func WithFallback ΒΆ added in v1.1.0

func WithFallback(cfg FallbackConfig) Option

func WithHTTPClient ΒΆ

func WithHTTPClient(hc *http.Client) Option
Example ΒΆ

ExampleWithHTTPClient demonstrates swapping in a custom *http.Client.

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.New(
		ambatukam.WithHTTPClient(&http.Client{Timeout: 5 * time.Second}),
	)
	defer client.Close()
	fmt.Println("custom http.Client")
}
Output:
custom http.Client

func WithHooks ΒΆ

func WithHooks(h Hooks) Option

func WithLogger ΒΆ

func WithLogger(l *slog.Logger) Option

func WithMetrics ΒΆ added in v1.1.0

func WithMetrics(r MetricsRecorder) Option

func WithPolicy ΒΆ

func WithPolicy(p Policy) Option

func WithRateLimit ΒΆ

func WithRateLimit(cfg RateLimitConfig) Option

func WithRequestID ΒΆ

func WithRequestID(header string) Option

func WithRequestIDPolicy ΒΆ

func WithRequestIDPolicy(p *RequestIDPolicy) Option

func WithRetry ΒΆ

func WithRetry(cfg RetryConfig) Option

func WithSingleflight ΒΆ added in v1.1.0

func WithSingleflight() Option

func WithTimeout ΒΆ

func WithTimeout(cfg TimeoutConfig) Option

type PermanentError ΒΆ

type PermanentError struct{ Err error }

func (*PermanentError) Error ΒΆ

func (e *PermanentError) Error() string

func (*PermanentError) Unwrap ΒΆ

func (e *PermanentError) Unwrap() error

type Policy ΒΆ

type Policy interface {
	Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)
}

func Chain ΒΆ

func Chain(policies ...Policy) Policy
Example ΒΆ

ExampleChain demonstrates manual composition of policies.

package main

import (
	"fmt"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.New(ambatukam.WithPolicy(ambatukam.Chain(
		ambatukam.NewTimeout(ambatukam.TimeoutConfig{Timeout: 2 * time.Second}),
		ambatukam.NewRetry(ambatukam.DefaultRetryConfig()),
		ambatukam.NewCircuitBreaker(ambatukam.DefaultCircuitConfig()),
	)))
	defer client.Close()

	fmt.Println("chained: ok")
}
Output:
chained: ok

type PolicyFunc ΒΆ

type PolicyFunc func(ctx context.Context, req *http.Request) (*http.Response, error)

func (PolicyFunc) Execute ΒΆ

func (f PolicyFunc) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

type RateLimitConfig ΒΆ

type RateLimitConfig struct {
	Rate        float64
	Burst       uint32
	WaitTimeout time.Duration
}

type RateLimitPolicy ΒΆ

type RateLimitPolicy struct {
	// contains filtered or unexported fields
}

func NewRateLimit ΒΆ

func NewRateLimit(cfg RateLimitConfig) *RateLimitPolicy
Example ΒΆ

ExampleNewRateLimit demonstrates building a rate-limit policy directly.

package main

import (
	"fmt"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.New(ambatukam.WithRateLimit(ambatukam.RateLimitConfig{
		Rate:  10,
		Burst: 5,
	}))
	defer client.Close()
	fmt.Println("rate limited client ready")
}
Output:
rate limited client ready

func (*RateLimitPolicy) Execute ΒΆ

func (r *RateLimitPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*RateLimitPolicy) WithLogger ΒΆ

func (r *RateLimitPolicy) WithLogger(l *slog.Logger) *RateLimitPolicy

type RequestError ΒΆ

type RequestError struct {
	Method   string
	URL      string
	Status   int
	Attempts int
	Policy   string
	Err      error
}

func (*RequestError) Error ΒΆ

func (e *RequestError) Error() string

func (*RequestError) Unwrap ΒΆ

func (e *RequestError) Unwrap() error

type RequestIDPolicy ΒΆ

type RequestIDPolicy struct {
	// contains filtered or unexported fields
}

func NewRequestIDPolicy ΒΆ

func NewRequestIDPolicy() *RequestIDPolicy
Example ΒΆ

ExampleNewRequestIDPolicy demonstrates registering a request-ID policy.

package main

import (
	"fmt"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.New(ambatukam.WithRequestIDPolicy(ambatukam.NewRequestIDPolicy()))
	defer client.Close()
	fmt.Println("request ID policy enabled")
}
Output:
request ID policy enabled

func (*RequestIDPolicy) Execute ΒΆ

func (r *RequestIDPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*RequestIDPolicy) WithGenerator ΒΆ

func (r *RequestIDPolicy) WithGenerator(gen func() string) *RequestIDPolicy

func (*RequestIDPolicy) WithHeader ΒΆ

func (r *RequestIDPolicy) WithHeader(name string) *RequestIDPolicy

type RetryConfig ΒΆ

type RetryConfig struct {
	MaxRetries     int
	InitialBackoff time.Duration
	MaxBackoff     time.Duration
	Multiplier     float64
	Jitter         float64
	Backoff        Backoff
	ShouldRetry    func(resp *http.Response, err error) bool
}

func DefaultRetryConfig ΒΆ

func DefaultRetryConfig() RetryConfig

type RetryPolicy ΒΆ

type RetryPolicy struct {
	// contains filtered or unexported fields
}

func NewRetry ΒΆ

func NewRetry(cfg RetryConfig) *RetryPolicy
Example ΒΆ

ExampleNewRetry demonstrates configuring the retry policy.

package main

import (
	"fmt"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	cfg := ambatukam.DefaultRetryConfig()
	cfg.MaxRetries = 5
	cfg.Backoff = ambatukam.ExponentialBackoff(50*time.Millisecond, time.Second, 2.0)

	client := ambatukam.New(ambatukam.WithRetry(cfg))
	defer client.Close()

	fmt.Println("max retries:", cfg.MaxRetries)
}
Output:
max retries: 5

func (*RetryPolicy) Execute ΒΆ

func (r *RetryPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

func (*RetryPolicy) WithHooks ΒΆ

func (r *RetryPolicy) WithHooks(h Hooks) *RetryPolicy

func (*RetryPolicy) WithLogger ΒΆ

func (r *RetryPolicy) WithLogger(l *slog.Logger) *RetryPolicy

type SingleflightConfig ΒΆ added in v1.1.0

type SingleflightConfig struct {
	Enabled bool
}

type SingleflightPolicy ΒΆ added in v1.1.0

type SingleflightPolicy struct {
	// contains filtered or unexported fields
}

func NewSingleflight ΒΆ added in v1.1.0

func NewSingleflight() *SingleflightPolicy

func (*SingleflightPolicy) Execute ΒΆ added in v1.1.0

func (sf *SingleflightPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

type State ΒΆ

type State string
const (
	StateClosed   State = "closed"
	StateOpen     State = "open"
	StateHalfOpen State = "half-open"
)

type TimeoutConfig ΒΆ

type TimeoutConfig struct {
	Timeout time.Duration
}

type TimeoutPolicy ΒΆ

type TimeoutPolicy struct {
	// contains filtered or unexported fields
}

func NewTimeout ΒΆ

func NewTimeout(cfg TimeoutConfig) *TimeoutPolicy
Example ΒΆ

ExampleNewTimeout demonstrates configuring the per-attempt timeout.

package main

import (
	"fmt"
	"time"

	"github.com/farhanturu/ambatukam-go"
)

func main() {
	client := ambatukam.New(
		ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 500 * time.Millisecond}),
	)
	defer client.Close()

	fmt.Println("timeout: ok")
}
Output:
timeout: ok

func (*TimeoutPolicy) Execute ΒΆ

func (t *TimeoutPolicy) Execute(ctx context.Context, req *http.Request, next PolicyFunc) (*http.Response, error)

Directories ΒΆ

Path Synopsis
examples
basic command
Package main is a runnable example for ambatukam.
Package main is a runnable example for ambatukam.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL