ambatukam

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 27, 2026 License: MIT Imports: 18 Imported by: 0

README

Ambatukam Go

Ambatukam Go

Your Go HTTP client just got superpowers.
One line of code. Zero dependencies. Production-grade resilience.

CI Go Reference Go Report Card License Stars

Stop writing retry loops. Stop handling timeouts manually.
Stop panicking when Stripe goes down at 3 AM.
Ambatukam Go handles all of it — automatically.


😤 The Problem

Every Go backend that calls external services ends up like this:

// Your code at 3 AM when Stripe is down
resp, err := http.Get("https://api.stripe.com/charges")
if err != nil {
    // retry? how many times? with what backoff?
    // what if it keeps failing? circuit break?
    // what about rate limits? timeouts?
    // TODO: fix this later (you never will)
}

You end up stitching together 3-5 libraries, writing glue code nobody owns, and debugging resilience bugs at 3 AM.

😎 The Solution

// Your code with Ambatukam Go
client := ambatukam.New(
    ambatukam.WithRetry(ambatukam.RetryConfig{MaxRetries: 3}),
    ambatukam.WithCircuitBreaker(ambatukam.CircuitConfig{FailureThreshold: 5}),
    ambatukam.WithTimeout(ambatukam.TimeoutConfig{Timeout: 2 * time.Second}),
)
resp, err := client.Get(ctx, "https://api.stripe.com/charges")
// ✅ Auto-retry with exponential backoff
// ✅ Circuit opens when Stripe is down
// ✅ Timeout per attempt
// ✅ You sleep at 3 AM

10 lines. Zero dependencies. Production-grade.


⚡ Why Ambatukam Go?

Feature Ambatukam Go Other Libraries
Retry with backoff + jitter ✅ Built-in Need cenkalti/backoff
Circuit breaker ✅ Built-in Need sony/gobreaker
Bulkhead (concurrency limit) ✅ Built-in DIY or slok/goresilience
Rate limiter ✅ Built-in Need golang.org/x/time/rate
Per-attempt timeout ✅ Built-in Manual
Fallback strategy ✅ Built-in DIY
Singleflight (dedup) ✅ Built-in Need golang.org/x/sync
Health check endpoint ✅ Built-in DIY
Metrics interface ✅ Built-in DIY
Body buffering for POST retry ✅ Automatic Often broken
Retry-After header ✅ Automatic Most skip
Generic JSON helpers ✅ Built-in DIY
Request ID propagation ✅ Built-in DIY
Hooks (auth, logging, metrics) ✅ 4 callbacks Varies
Composable policies Chain() Manual
Zero dependencies None 3-5 deps

🚀 Install

go get github.com/farhanturu/ambatukam-go

Requires Go 1.21+. Zero external dependencies.


🎯 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   │  │        │  │          │   │
│   └────────┘  └──────────┘  └────────┘  └──────────┘   │
│        ↓           ↓             ↓            ↓         │
│   ┌────────┐  ┌──────────┐  ┌──────────┐               │
│   │Fallback│  │Singleflight│ │  Timeout │               │
│   └────────┘  └──────────┘  └──────────┘               │
│        ↓           ↓             ↓                      │
│   ┌──────────────────────────────────────────────────┐  │
│   │     Request ID · Hooks · Metrics                 │  │
│   └──────────────────────────────────────────────────┘  │
│                         ↓                                │
│                   http.Client.Do()                       │
└──────────────────────────────────────────────────────────┘

Policies are composable middleware — outer-to-inner order: retry → circuit → fallback → 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 retry by default; opt in for POST with 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 with sync.RWMutex for better read concurrency.

🚧 Bulkhead

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

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

🚦 Rate Limiter

ambatukam.WithRateLimit(ambatukam.RateLimitConfig{
    Rate:        10,
    Burst:       5,
    WaitTimeout: 100 * time.Millisecond,
})

Token bucket. Rate <= 0 denies all requests.

⏱️ Timeout

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

Per-attempt deadline. Parent ctx cancellation takes precedence.

🛟 Fallback

ambatukam.WithFallback(ambatukam.FallbackConfig{
    Handler: func(req *http.Request, err error) (*http.Response, error) {
        return cachedResponse, nil
    },
})

Return custom response when everything fails. Never leave your users hanging.

🔗 Singleflight

ambatukam.WithSingleflight()

Deduplicate identical concurrent requests. 10 goroutines requesting the same data = 1 HTTP call.

🏷️ Request ID

ambatukam.WithRequestID("X-Request-ID")

Auto-generates 12-byte hex ID per request, or propagates 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)", req.URL, attempt)
    },
    OnStateChange: func(name string, from, to ambatukam.State) {
        metrics.Gauge("circuit_state").Set(string(to))
    },
    OnFallback: func(req *http.Request, err error) {
        log.Printf("fallback triggered: %v", err)
    },
})

Five callbacks: BeforeRequest, AfterResponse, OnRetry, OnStateChange, OnFallback.

📊 Metrics

ambatukam.WithMetrics(myPrometheusRecorder)

Implement MetricsRecorder interface for Prometheus, Datadog, or any metrics system.

🏥 Health Check

hc := client.HealthChecker()
http.Handle("/health", hc.Handler())

Returns JSON with policy status, memory stats, and uptime.

{
  "status": "healthy",
  "timestamp": "2026-06-26T20:00:00Z",
  "uptime": "5m30s",
  "policies": {
    "circuit_breaker": "closed",
    "bulkhead_in_flight": "3",
    "bulkhead_denied": "0"
  },
  "memory": {
    "alloc_bytes": 1234567,
    "num_gc": 5
  }
}

📦 Generic JSON Helpers

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)

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


🎯 Preset Configs

Don't want to tune? Use presets:

// 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

💡 Real-World 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}),
    ambatukam.WithFallback(ambatukam.FallbackConfig{
        Handler: func(req *http.Request, err error) (*http.Response, error) {
            return nil, errors.New("payment service unavailable, please retry later")
        },
    }),
)

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.WithSingleflight(),
    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}),
    ambatukam.WithFallback(ambatukam.FallbackConfig{
        Handler: func(req *http.Request, err error) (*http.Response, error) {
            return getCachedData(req.URL.String())
        },
    }),
)

🚨 Error Handling

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.ErrFallback):       // fallback failed
case errors.Is(err, ambatukam.ErrNilRequest):     // programming error
}

For full context:

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

📈 Benchmarks

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

Zero overhead when no policies enabled. Full stack costs ~2.4x vs raw stdlib.


🔄 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, fallback, singleflight, hooks, and request ID.


📚 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.
Circuit opens too often Lower FailureThreshold or increase OpenDuration.
Bulkhead denies immediately Increase MaxQueue or MaxConcurrent.
Rate limit denies unexpectedly Rate == 0 = disabled, Rate < 0 = deny all.
Need debug logging Use ambatukam.WithDebug().

🗺️ Roadmap

v1.1 (current)

Retry, circuit breaker, bulkhead, rate limiter, timeout, request ID, hooks, generic JSON helpers, permanent errors, preset configs, fallback strategy, singleflight, health check, metrics interface.

v2.0 (next)

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
Stop writing resilience code. Start shipping features.

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 Counter added in v1.2.0

type Counter interface {
	Inc()
	Add(float64)
}

type CounterVec added in v1.2.0

type CounterVec interface {
	WithLabelValues(lvs ...string) Counter
}

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 Gauge added in v1.2.0

type Gauge interface {
	Set(float64)
	Inc()
	Dec()
}

type GaugeVec added in v1.2.0

type GaugeVec interface {
	WithLabelValues(lvs ...string) Gauge
}

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 Histogram added in v1.2.0

type Histogram interface {
	Observe(float64)
}

type HistogramVec added in v1.2.0

type HistogramVec interface {
	WithLabelValues(lvs ...string) Histogram
}

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 Logger added in v1.2.0

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

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 WithCustomLogger added in v1.2.0

func WithCustomLogger(l Logger) 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

func WithTimeoutMap added in v1.2.0

func WithTimeoutMap(rules map[string]time.Duration) 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 PrometheusConfig added in v1.2.0

type PrometheusConfig struct {
	RequestsTotal      CounterVec
	RetriesTotal       CounterVec
	CircuitState       GaugeVec
	RequestDuration    HistogramVec
	BulkheadDenied     CounterVec
	RateLimitDenied    CounterVec
	FallbacksTotal     CounterVec
	TimeoutsTotal      CounterVec
	CircuitTransitions CounterVec
}

type PrometheusRecorder added in v1.2.0

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

func NewPrometheusRecorder added in v1.2.0

func NewPrometheusRecorder(cfg PrometheusConfig) *PrometheusRecorder

func (*PrometheusRecorder) RecordBulkheadDenied added in v1.2.0

func (r *PrometheusRecorder) RecordBulkheadDenied(method, url string)

func (*PrometheusRecorder) RecordCircuitStateChange added in v1.2.0

func (r *PrometheusRecorder) RecordCircuitStateChange(name string, from, to State)

func (*PrometheusRecorder) RecordFallback added in v1.2.0

func (r *PrometheusRecorder) RecordFallback(method, url string)

func (*PrometheusRecorder) RecordRateLimitDenied added in v1.2.0

func (r *PrometheusRecorder) RecordRateLimitDenied(method, url string)

func (*PrometheusRecorder) RecordRequest added in v1.2.0

func (r *PrometheusRecorder) RecordRequest(method, url string, status int, duration time.Duration)

func (*PrometheusRecorder) RecordRetry added in v1.2.0

func (r *PrometheusRecorder) RecordRetry(method, url string, attempt int)

func (*PrometheusRecorder) RecordTimeout added in v1.2.0

func (r *PrometheusRecorder) RecordTimeout(method, url string)

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 TimeoutMapPolicy added in v1.2.0

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

func NewTimeoutMap added in v1.2.0

func NewTimeoutMap(rules map[string]time.Duration) *TimeoutMapPolicy

func (*TimeoutMapPolicy) Execute added in v1.2.0

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

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