Loading documentation...
Loading documentation...
Loading documentation...
Import Path: github.com/kolosys/ion/circuit
The circuit breaker pattern prevents cascading failures in distributed systems by temporarily blocking requests to failing services, allowing them time to recover while providing fast-fail behavior to callers.
Circuit breakers are essential for building resilient microservices. They automatically detect failures and "trip" to prevent overwhelming a failing service, then automatically test recovery when appropriate.
The circuit breaker implements a three-state state machine:
┌─────────┐ failures ≥ threshold ┌─────────┐
│ Closed │ ──────────────────────> │ Open │
│ │ <───────────────────── │ │
└─────────┘ recovery timeout └─────────┘
▲ │
│ │
│ successes ≥ threshold │
└────────────────────────────────────┘
┌───────────┐
│ Half-Open │
└───────────┘Closed: Normal operation, requests pass through
Open: Circuit is tripped, requests fail fast
Half-Open: Testing recovery, limited requests allowed
The circuit breaker tracks consecutive failures and trips when the threshold is reached:
cb := circuit.New("payment-service",
circuit.WithFailureThreshold(5), // Trip after 5 consecutive failures
)After the recovery timeout, the circuit enters Half-Open state to test if the service has recovered:
cb := circuit.New("payment-service",
circuit.WithRecoveryTimeout(30*time.Second), // Wait 30s before testing
circuit.WithHalfOpenMaxRequests(3), // Allow 3 test requests
circuit.WithHalfOpenSuccessThreshold(2), // Need 2 successes to close
)Customize what counts as a failure:
cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count 5xx errors and timeouts as failures
// 4xx errors (client errors) should not trip the circuit
if err == nil {
return false
}
// In a real implementation, check HTTP status code
return true
}),
)Protect a payment processing service from cascading failures:
package main
import (
"context"
"errors"
"time"
"github.com/kolosys/ion/circuit"
)
type PaymentService struct {
circuit circuit.CircuitBreaker
}
func NewPaymentService() *PaymentService {
return &PaymentService{
circuit: circuit.New("payment-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
circuit.WithHalfOpenMaxRequests(2),
circuit.WithHalfOpenSuccessThreshold(1),
circuit.WithStateChangeCallback(func(from, to circuit.State) {
logStateChange("payment-service", from, to)
}),
),
}
}
func (ps *PaymentService) ProcessPayment(ctx context.Context, amount float64) error {
_, err := ps.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
// Call actual payment service
return callPaymentAPI(ctx, amount)
})
if err != nil {
if circuit.IsCircuitOpen(err) {
// Circuit is open - return user-friendly error
return errors.New("payment service temporarily unavailable, please try again later")
}
return err
}
return nil
}
func callPaymentAPI(ctx context.Context, amount float64) (string, error) {
// Actual API call implementation
return "payment-id", nil
}
func logStateChange(name string, from, to circuit.State) {
// Log state changes for monitoring
fmt.Printf("Circuit %s: %s -> %s
", name, from, to)
}Protect database operations from connection pool exhaustion:
package main
import (
"context"
"database/sql"
"time"
"github.com/kolosys/ion/circuit"
)
type ProtectedDB struct {
db *sql.DB
circuit circuit.CircuitBreaker
}
func NewProtectedDB(dsn string) (*ProtectedDB, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
return &ProtectedDB{
db: db,
circuit: circuit.New("database",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(10*time.Second),
circuit.WithFailurePredicate(func(err error) bool {
// Only count connection errors, not query errors
return err == sql.ErrConnDone || err == context.DeadlineExceeded
}),
),
}, nil
}
func (pdb *ProtectedDB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := pdb.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return pdb.db.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}Protect multiple services with separate circuit breakers:
package main
import (
"context"
"time"
"github.com/kolosys/ion/circuit"
)
type ServiceMesh struct {
userService circuit.CircuitBreaker
orderService circuit.CircuitBreaker
paymentService circuit.CircuitBreaker
}
func NewServiceMesh() *ServiceMesh {
return &ServiceMesh{
userService: circuit.New("user-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
),
orderService: circuit.New("order-service",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(20*time.Second),
),
paymentService: circuit.New("payment-service",
circuit.WithFailureThreshold(2), // More sensitive
circuit.WithRecoveryTimeout(60*time.Second), // Longer recovery
),
}
}
func (sm *ServiceMesh) ProcessOrder(ctx context.Context, orderID string) error {
// Check user service
_, err := sm.userService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateUser(ctx, orderID)
})
if err != nil {
return err
}
// Check order service
_, err = sm.orderService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateOrder(ctx, orderID)
})
if err != nil {
return err
}
// Process payment
_, err = sm.paymentService.Execute(ctx, func(ctx context.Context) (any, error) {
return processPayment(ctx, orderID)
})
return err
}Combine circuit breaker with retry logic:
package main
import (
"context"
"net/http"
"time"
"github.com/kolosys/ion/circuit"
)
type ResilientHTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func NewResilientHTTPClient() *ResilientHTTPClient {
return &ResilientHTTPClient{
client: &http.Client{
Timeout: 5 * time.Second,
},
circuit: circuit.New("http-client",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(15*time.Second),
),
}
}
func (c *ResilientHTTPClient) GetWithRetry(ctx context.Context, url string, maxRetries int) (*http.Response, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
})
if err == nil {
return result.(*http.Response), nil
}
// If circuit is open, don't retry
if circuit.IsCircuitOpen(err) {
return nil, err
}
lastErr = err
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, lastErr
}Ion provides preset configurations for common scenarios:
Fast to trip, fast to recover - suitable for non-critical operations:
cb := circuit.New("non-critical-service", circuit.QuickFailover()...)Slow to trip, slow to recover - suitable for critical operations:
cb := circuit.New("critical-service", circuit.Conservative()...)Quick to trip, slow to recover - suitable for protecting against cascading failures:
cb := circuit.New("protected-service", circuit.Aggressive()...)Circuit breakers integrate with Ion's observability system:
import (
"github.com/kolosys/ion/circuit"
"github.com/kolosys/ion/observe"
)
obs := observe.New().
WithLogger(myLogger).
WithMetrics(myMetrics).
WithTracer(myTracer)
cb := circuit.New("service",
circuit.WithObservability(obs),
)
// Metrics are automatically collected:
// - circuit.requests_total
// - circuit.requests_succeeded
// - circuit.requests_failed
// - circuit.requests_rejected
// - circuit.state_changes
// - circuit.request_durationAccess circuit breaker metrics:
metrics := cb.Metrics()
fmt.Printf("State: %s
", metrics.State)
fmt.Printf("Total Requests: %d
", metrics.TotalRequests)
fmt.Printf("Successes: %d
", metrics.TotalSuccesses)
fmt.Printf("Failures: %d
", metrics.TotalFailures)
fmt.Printf("Failure Rate: %.2f%%
", metrics.FailureRate()*100)
fmt.Printf("State Changes: %d
", metrics.StateChanges)Problem: Circuit trips on normal transient failures
// Too sensitive
cb := circuit.New("service", circuit.WithFailureThreshold(1))Solution: Use appropriate thresholds based on your service's failure characteristics
// Better
cb := circuit.New("service", circuit.WithFailureThreshold(5))Problem: Not handling circuit open errors appropriately
// Bad
result, err := cb.Execute(ctx, fn)
if err != nil {
return err // User sees circuit breaker error
}Solution: Provide user-friendly error messages
// Good
result, err := cb.Execute(ctx, fn)
if err != nil {
if circuit.IsCircuitOpen(err) {
return errors.New("service temporarily unavailable")
}
return err
}Problem: Client errors (4xx) trip the circuit
Solution: Use failure predicates to distinguish error types
cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count server errors (5xx) and timeouts
return isServerError(err) || isTimeout(err)
}),
)type HTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func (c *HTTPClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.client.Do(req.WithContext(ctx))
})
if err != nil {
return nil, err
}
return result.(*http.Response), nil
}func (c *gRPCClient) Call(ctx context.Context, method string, req, resp any) error {
_, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.conn.Invoke(ctx, method, req, resp)
})
return err
}func (db *DB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := db.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return db.conn.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}Import Path: github.com/kolosys/ion/circuit
The circuit breaker pattern prevents cascading failures in distributed systems by temporarily blocking requests to failing services, allowing them time to recover while providing fast-fail behavior to callers.
Circuit breakers are essential for building resilient microservices. They automatically detect failures and "trip" to prevent overwhelming a failing service, then automatically test recovery when appropriate.
The circuit breaker implements a three-state state machine:
┌─────────┐ failures ≥ threshold ┌─────────┐
│ Closed │ ──────────────────────> │ Open │
│ │ <───────────────────── │ │
└─────────┘ recovery timeout └─────────┘
▲ │
│ │
│ successes ≥ threshold │
└────────────────────────────────────┘
┌───────────┐
│ Half-Open │
└───────────┘Closed: Normal operation, requests pass through
Open: Circuit is tripped, requests fail fast
Half-Open: Testing recovery, limited requests allowed
The circuit breaker tracks consecutive failures and trips when the threshold is reached:
cb := circuit.New("payment-service",
circuit.WithFailureThreshold(5), // Trip after 5 consecutive failures
)After the recovery timeout, the circuit enters Half-Open state to test if the service has recovered:
cb := circuit.New("payment-service",
circuit.WithRecoveryTimeout(30*time.Second), // Wait 30s before testing
circuit.WithHalfOpenMaxRequests(3), // Allow 3 test requests
circuit.WithHalfOpenSuccessThreshold(2), // Need 2 successes to close
)Customize what counts as a failure:
cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count 5xx errors and timeouts as failures
// 4xx errors (client errors) should not trip the circuit
if err == nil {
return false
}
// In a real implementation, check HTTP status code
return true
}),
)Protect a payment processing service from cascading failures:
package main
import (
"context"
"errors"
"time"
"github.com/kolosys/ion/circuit"
)
type PaymentService struct {
circuit circuit.CircuitBreaker
}
func NewPaymentService() *PaymentService {
return &PaymentService{
circuit: circuit.New("payment-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
circuit.WithHalfOpenMaxRequests(2),
circuit.WithHalfOpenSuccessThreshold(1),
circuit.WithStateChangeCallback(func(from, to circuit.State) {
logStateChange("payment-service", from, to)
}),
),
}
}
func (ps *PaymentService) ProcessPayment(ctx context.Context, amount float64) error {
_, err := ps.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
// Call actual payment service
return callPaymentAPI(ctx, amount)
})
if err != nil {
if circuit.IsCircuitOpen(err) {
// Circuit is open - return user-friendly error
return errors.New("payment service temporarily unavailable, please try again later")
}
return err
}
return nil
}
func callPaymentAPI(ctx context.Context, amount float64) (string, error) {
// Actual API call implementation
return "payment-id", nil
}
func logStateChange(name string, from, to circuit.State) {
// Log state changes for monitoring
fmt.Printf("Circuit %s: %s -> %s
", name, from, to)
}Protect database operations from connection pool exhaustion:
package main
import (
"context"
"database/sql"
"time"
"github.com/kolosys/ion/circuit"
)
type ProtectedDB struct {
db *sql.DB
circuit circuit.CircuitBreaker
}
func NewProtectedDB(dsn string) (*ProtectedDB, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
return &ProtectedDB{
db: db,
circuit: circuit.New("database",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(10*time.Second),
circuit.WithFailurePredicate(func(err error) bool {
// Only count connection errors, not query errors
return err == sql.ErrConnDone || err == context.DeadlineExceeded
}),
),
}, nil
}
func (pdb *ProtectedDB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := pdb.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return pdb.db.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}Protect multiple services with separate circuit breakers:
package main
import (
"context"
"time"
"github.com/kolosys/ion/circuit"
)
type ServiceMesh struct {
userService circuit.CircuitBreaker
orderService circuit.CircuitBreaker
paymentService circuit.CircuitBreaker
}
func NewServiceMesh() *ServiceMesh {
return &ServiceMesh{
userService: circuit.New("user-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
),
orderService: circuit.New("order-service",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(20*time.Second),
),
paymentService: circuit.New("payment-service",
circuit.WithFailureThreshold(2), // More sensitive
circuit.WithRecoveryTimeout(60*time.Second), // Longer recovery
),
}
}
func (sm *ServiceMesh) ProcessOrder(ctx context.Context, orderID string) error {
// Check user service
_, err := sm.userService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateUser(ctx, orderID)
})
if err != nil {
return err
}
// Check order service
_, err = sm.orderService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateOrder(ctx, orderID)
})
if err != nil {
return err
}
// Process payment
_, err = sm.paymentService.Execute(ctx, func(ctx context.Context) (any, error) {
return processPayment(ctx, orderID)
})
return err
}Combine circuit breaker with retry logic:
package main
import (
"context"
"net/http"
"time"
"github.com/kolosys/ion/circuit"
)
type ResilientHTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func NewResilientHTTPClient() *ResilientHTTPClient {
return &ResilientHTTPClient{
client: &http.Client{
Timeout: 5 * time.Second,
},
circuit: circuit.New("http-client",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(15*time.Second),
),
}
}
func (c *ResilientHTTPClient) GetWithRetry(ctx context.Context, url string, maxRetries int) (*http.Response, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
})
if err == nil {
return result.(*http.Response), nil
}
// If circuit is open, don't retry
if circuit.IsCircuitOpen(err) {
return nil, err
}
lastErr = err
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, lastErr
}Ion provides preset configurations for common scenarios:
Fast to trip, fast to recover - suitable for non-critical operations:
cb := circuit.New("non-critical-service", circuit.QuickFailover()...)Slow to trip, slow to recover - suitable for critical operations:
cb := circuit.New("critical-service", circuit.Conservative()...)Quick to trip, slow to recover - suitable for protecting against cascading failures:
cb := circuit.New("protected-service", circuit.Aggressive()...)Circuit breakers integrate with Ion's observability system:
import (
"github.com/kolosys/ion/circuit"
"github.com/kolosys/ion/observe"
)
obs := observe.New().
WithLogger(myLogger).
WithMetrics(myMetrics).
WithTracer(myTracer)
cb := circuit.New("service",
circuit.WithObservability(obs),
)
// Metrics are automatically collected:
// - circuit.requests_total
// - circuit.requests_succeeded
// - circuit.requests_failed
// - circuit.requests_rejected
// - circuit.state_changes
// - circuit.request_durationAccess circuit breaker metrics:
metrics := cb.Metrics()
fmt.Printf("State: %s
", metrics.State)
fmt.Printf("Total Requests: %d
", metrics.TotalRequests)
fmt.Printf("Successes: %d
", metrics.TotalSuccesses)
fmt.Printf("Failures: %d
", metrics.TotalFailures)
fmt.Printf("Failure Rate: %.2f%%
", metrics.FailureRate()*100)
fmt.Printf("State Changes: %d
", metrics.StateChanges)Problem: Circuit trips on normal transient failures
// Too sensitive
cb := circuit.New("service", circuit.WithFailureThreshold(1))Solution: Use appropriate thresholds based on your service's failure characteristics
// Better
cb := circuit.New("service", circuit.WithFailureThreshold(5))Problem: Not handling circuit open errors appropriately
// Bad
result, err := cb.Execute(ctx, fn)
if err != nil {
return err // User sees circuit breaker error
}Solution: Provide user-friendly error messages
// Good
result, err := cb.Execute(ctx, fn)
if err != nil {
if circuit.IsCircuitOpen(err) {
return errors.New("service temporarily unavailable")
}
return err
}Problem: Client errors (4xx) trip the circuit
Solution: Use failure predicates to distinguish error types
cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count server errors (5xx) and timeouts
return isServerError(err) || isTimeout(err)
}),
)type HTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func (c *HTTPClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.client.Do(req.WithContext(ctx))
})
if err != nil {
return nil, err
}
return result.(*http.Response), nil
}func (c *gRPCClient) Call(ctx context.Context, method string, req, resp any) error {
_, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.conn.Invoke(ctx, method, req, resp)
})
return err
}func (db *DB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := db.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return db.conn.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}┌─────────┐ failures ≥ threshold ┌─────────┐
│ Closed │ ──────────────────────> │ Open │
│ │ <───────────────────── │ │
└─────────┘ recovery timeout └─────────┘
▲ │
│ │
│ successes ≥ threshold │
└────────────────────────────────────┘
┌───────────┐
│ Half-Open │
└───────────┘cb := circuit.New("payment-service",
circuit.WithFailureThreshold(5), // Trip after 5 consecutive failures
)cb := circuit.New("payment-service",
circuit.WithRecoveryTimeout(30*time.Second), // Wait 30s before testing
circuit.WithHalfOpenMaxRequests(3), // Allow 3 test requests
circuit.WithHalfOpenSuccessThreshold(2), // Need 2 successes to close
)cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count 5xx errors and timeouts as failures
// 4xx errors (client errors) should not trip the circuit
if err == nil {
return false
}
// In a real implementation, check HTTP status code
return true
}),
)package main
import (
"context"
"errors"
"time"
"github.com/kolosys/ion/circuit"
)
type PaymentService struct {
circuit circuit.CircuitBreaker
}
func NewPaymentService() *PaymentService {
return &PaymentService{
circuit: circuit.New("payment-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
circuit.WithHalfOpenMaxRequests(2),
circuit.WithHalfOpenSuccessThreshold(1),
circuit.WithStateChangeCallback(func(from, to circuit.State) {
logStateChange("payment-service", from, to)
}),
),
}
}
func (ps *PaymentService) ProcessPayment(ctx context.Context, amount float64) error {
_, err := ps.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
// Call actual payment service
return callPaymentAPI(ctx, amount)
})
if err != nil {
if circuit.IsCircuitOpen(err) {
// Circuit is open - return user-friendly error
return errors.New("payment service temporarily unavailable, please try again later")
}
return err
}
return nil
}
func callPaymentAPI(ctx context.Context, amount float64) (string, error) {
// Actual API call implementation
return "payment-id", nil
}
func logStateChange(name string, from, to circuit.State) {
// Log state changes for monitoring
fmt.Printf("Circuit %s: %s -> %s
", name, from, to)
}package main
import (
"context"
"database/sql"
"time"
"github.com/kolosys/ion/circuit"
)
type ProtectedDB struct {
db *sql.DB
circuit circuit.CircuitBreaker
}
func NewProtectedDB(dsn string) (*ProtectedDB, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
return &ProtectedDB{
db: db,
circuit: circuit.New("database",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(10*time.Second),
circuit.WithFailurePredicate(func(err error) bool {
// Only count connection errors, not query errors
return err == sql.ErrConnDone || err == context.DeadlineExceeded
}),
),
}, nil
}
func (pdb *ProtectedDB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := pdb.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return pdb.db.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}package main
import (
"context"
"time"
"github.com/kolosys/ion/circuit"
)
type ServiceMesh struct {
userService circuit.CircuitBreaker
orderService circuit.CircuitBreaker
paymentService circuit.CircuitBreaker
}
func NewServiceMesh() *ServiceMesh {
return &ServiceMesh{
userService: circuit.New("user-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
),
orderService: circuit.New("order-service",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(20*time.Second),
),
paymentService: circuit.New("payment-service",
circuit.WithFailureThreshold(2), // More sensitive
circuit.WithRecoveryTimeout(60*time.Second), // Longer recovery
),
}
}
func (sm *ServiceMesh) ProcessOrder(ctx context.Context, orderID string) error {
// Check user service
_, err := sm.userService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateUser(ctx, orderID)
})
if err != nil {
return err
}
// Check order service
_, err = sm.orderService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateOrder(ctx, orderID)
})
if err != nil {
return err
}
// Process payment
_, err = sm.paymentService.Execute(ctx, func(ctx context.Context) (any, error) {
return processPayment(ctx, orderID)
})
return err
}package main
import (
"context"
"net/http"
"time"
"github.com/kolosys/ion/circuit"
)
type ResilientHTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func NewResilientHTTPClient() *ResilientHTTPClient {
return &ResilientHTTPClient{
client: &http.Client{
Timeout: 5 * time.Second,
},
circuit: circuit.New("http-client",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(15*time.Second),
),
}
}
func (c *ResilientHTTPClient) GetWithRetry(ctx context.Context, url string, maxRetries int) (*http.Response, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
})
if err == nil {
return result.(*http.Response), nil
}
// If circuit is open, don't retry
if circuit.IsCircuitOpen(err) {
return nil, err
}
lastErr = err
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, lastErr
}cb := circuit.New("non-critical-service", circuit.QuickFailover()...)cb := circuit.New("critical-service", circuit.Conservative()...)cb := circuit.New("protected-service", circuit.Aggressive()...)import (
"github.com/kolosys/ion/circuit"
"github.com/kolosys/ion/observe"
)
obs := observe.New().
WithLogger(myLogger).
WithMetrics(myMetrics).
WithTracer(myTracer)
cb := circuit.New("service",
circuit.WithObservability(obs),
)
// Metrics are automatically collected:
// - circuit.requests_total
// - circuit.requests_succeeded
// - circuit.requests_failed
// - circuit.requests_rejected
// - circuit.state_changes
// - circuit.request_durationmetrics := cb.Metrics()
fmt.Printf("State: %s
", metrics.State)
fmt.Printf("Total Requests: %d
", metrics.TotalRequests)
fmt.Printf("Successes: %d
", metrics.TotalSuccesses)
fmt.Printf("Failures: %d
", metrics.TotalFailures)
fmt.Printf("Failure Rate: %.2f%%
", metrics.FailureRate()*100)
fmt.Printf("State Changes: %d
", metrics.StateChanges)// Too sensitive
cb := circuit.New("service", circuit.WithFailureThreshold(1))// Better
cb := circuit.New("service", circuit.WithFailureThreshold(5))// Bad
result, err := cb.Execute(ctx, fn)
if err != nil {
return err // User sees circuit breaker error
}// Good
result, err := cb.Execute(ctx, fn)
if err != nil {
if circuit.IsCircuitOpen(err) {
return errors.New("service temporarily unavailable")
}
return err
}cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count server errors (5xx) and timeouts
return isServerError(err) || isTimeout(err)
}),
)type HTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func (c *HTTPClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.client.Do(req.WithContext(ctx))
})
if err != nil {
return nil, err
}
return result.(*http.Response), nil
}func (c *gRPCClient) Call(ctx context.Context, method string, req, resp any) error {
_, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.conn.Invoke(ctx, method, req, resp)
})
return err
}func (db *DB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := db.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return db.conn.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}┌─────────┐ failures ≥ threshold ┌─────────┐
│ Closed │ ──────────────────────> │ Open │
│ │ <───────────────────── │ │
└─────────┘ recovery timeout └─────────┘
▲ │
│ │
│ successes ≥ threshold │
└────────────────────────────────────┘
┌───────────┐
│ Half-Open │
└───────────┘cb := circuit.New("payment-service",
circuit.WithFailureThreshold(5), // Trip after 5 consecutive failures
)cb := circuit.New("payment-service",
circuit.WithRecoveryTimeout(30*time.Second), // Wait 30s before testing
circuit.WithHalfOpenMaxRequests(3), // Allow 3 test requests
circuit.WithHalfOpenSuccessThreshold(2), // Need 2 successes to close
)cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count 5xx errors and timeouts as failures
// 4xx errors (client errors) should not trip the circuit
if err == nil {
return false
}
// In a real implementation, check HTTP status code
return true
}),
)package main
import (
"context"
"errors"
"time"
"github.com/kolosys/ion/circuit"
)
type PaymentService struct {
circuit circuit.CircuitBreaker
}
func NewPaymentService() *PaymentService {
return &PaymentService{
circuit: circuit.New("payment-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
circuit.WithHalfOpenMaxRequests(2),
circuit.WithHalfOpenSuccessThreshold(1),
circuit.WithStateChangeCallback(func(from, to circuit.State) {
logStateChange("payment-service", from, to)
}),
),
}
}
func (ps *PaymentService) ProcessPayment(ctx context.Context, amount float64) error {
_, err := ps.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
// Call actual payment service
return callPaymentAPI(ctx, amount)
})
if err != nil {
if circuit.IsCircuitOpen(err) {
// Circuit is open - return user-friendly error
return errors.New("payment service temporarily unavailable, please try again later")
}
return err
}
return nil
}
func callPaymentAPI(ctx context.Context, amount float64) (string, error) {
// Actual API call implementation
return "payment-id", nil
}
func logStateChange(name string, from, to circuit.State) {
// Log state changes for monitoring
fmt.Printf("Circuit %s: %s -> %s
", name, from, to)
}package main
import (
"context"
"database/sql"
"time"
"github.com/kolosys/ion/circuit"
)
type ProtectedDB struct {
db *sql.DB
circuit circuit.CircuitBreaker
}
func NewProtectedDB(dsn string) (*ProtectedDB, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
return &ProtectedDB{
db: db,
circuit: circuit.New("database",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(10*time.Second),
circuit.WithFailurePredicate(func(err error) bool {
// Only count connection errors, not query errors
return err == sql.ErrConnDone || err == context.DeadlineExceeded
}),
),
}, nil
}
func (pdb *ProtectedDB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := pdb.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return pdb.db.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}package main
import (
"context"
"time"
"github.com/kolosys/ion/circuit"
)
type ServiceMesh struct {
userService circuit.CircuitBreaker
orderService circuit.CircuitBreaker
paymentService circuit.CircuitBreaker
}
func NewServiceMesh() *ServiceMesh {
return &ServiceMesh{
userService: circuit.New("user-service",
circuit.WithFailureThreshold(5),
circuit.WithRecoveryTimeout(30*time.Second),
),
orderService: circuit.New("order-service",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(20*time.Second),
),
paymentService: circuit.New("payment-service",
circuit.WithFailureThreshold(2), // More sensitive
circuit.WithRecoveryTimeout(60*time.Second), // Longer recovery
),
}
}
func (sm *ServiceMesh) ProcessOrder(ctx context.Context, orderID string) error {
// Check user service
_, err := sm.userService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateUser(ctx, orderID)
})
if err != nil {
return err
}
// Check order service
_, err = sm.orderService.Execute(ctx, func(ctx context.Context) (any, error) {
return validateOrder(ctx, orderID)
})
if err != nil {
return err
}
// Process payment
_, err = sm.paymentService.Execute(ctx, func(ctx context.Context) (any, error) {
return processPayment(ctx, orderID)
})
return err
}package main
import (
"context"
"net/http"
"time"
"github.com/kolosys/ion/circuit"
)
type ResilientHTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func NewResilientHTTPClient() *ResilientHTTPClient {
return &ResilientHTTPClient{
client: &http.Client{
Timeout: 5 * time.Second,
},
circuit: circuit.New("http-client",
circuit.WithFailureThreshold(3),
circuit.WithRecoveryTimeout(15*time.Second),
),
}
}
func (c *ResilientHTTPClient) GetWithRetry(ctx context.Context, url string, maxRetries int) (*http.Response, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
})
if err == nil {
return result.(*http.Response), nil
}
// If circuit is open, don't retry
if circuit.IsCircuitOpen(err) {
return nil, err
}
lastErr = err
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, lastErr
}cb := circuit.New("non-critical-service", circuit.QuickFailover()...)cb := circuit.New("critical-service", circuit.Conservative()...)cb := circuit.New("protected-service", circuit.Aggressive()...)import (
"github.com/kolosys/ion/circuit"
"github.com/kolosys/ion/observe"
)
obs := observe.New().
WithLogger(myLogger).
WithMetrics(myMetrics).
WithTracer(myTracer)
cb := circuit.New("service",
circuit.WithObservability(obs),
)
// Metrics are automatically collected:
// - circuit.requests_total
// - circuit.requests_succeeded
// - circuit.requests_failed
// - circuit.requests_rejected
// - circuit.state_changes
// - circuit.request_durationmetrics := cb.Metrics()
fmt.Printf("State: %s
", metrics.State)
fmt.Printf("Total Requests: %d
", metrics.TotalRequests)
fmt.Printf("Successes: %d
", metrics.TotalSuccesses)
fmt.Printf("Failures: %d
", metrics.TotalFailures)
fmt.Printf("Failure Rate: %.2f%%
", metrics.FailureRate()*100)
fmt.Printf("State Changes: %d
", metrics.StateChanges)// Too sensitive
cb := circuit.New("service", circuit.WithFailureThreshold(1))// Better
cb := circuit.New("service", circuit.WithFailureThreshold(5))// Bad
result, err := cb.Execute(ctx, fn)
if err != nil {
return err // User sees circuit breaker error
}// Good
result, err := cb.Execute(ctx, fn)
if err != nil {
if circuit.IsCircuitOpen(err) {
return errors.New("service temporarily unavailable")
}
return err
}cb := circuit.New("http-client",
circuit.WithFailurePredicate(func(err error) bool {
// Only count server errors (5xx) and timeouts
return isServerError(err) || isTimeout(err)
}),
)type HTTPClient struct {
client *http.Client
circuit circuit.CircuitBreaker
}
func (c *HTTPClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
result, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.client.Do(req.WithContext(ctx))
})
if err != nil {
return nil, err
}
return result.(*http.Response), nil
}func (c *gRPCClient) Call(ctx context.Context, method string, req, resp any) error {
_, err := c.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return c.conn.Invoke(ctx, method, req, resp)
})
return err
}func (db *DB) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
result, err := db.circuit.Execute(ctx, func(ctx context.Context) (any, error) {
return db.conn.QueryContext(ctx, query, args...)
})
if err != nil {
return nil, err
}
return result.(*sql.Rows), nil
}