Combining Policies
Policies compose naturally in the resilience pipeline. Understanding the execution order is key to building effective combinations.
Fixed Execution Order
Regardless of the order you add them with the builder, policies always execute in this order:
Fallback → Chaos → RateLimiter → Timeout → CircuitBreaker → Bulkhead → Retry → Hedge → Handler
This means:
- Fallback sees all failures from all other policies
- Retry fires after CircuitBreaker passes the call through (open circuit = no retry)
- Timeout applies per attempt (inside Retry)
- Hedge runs the actual operation, potentially in parallel
Common Combinations
External API (Read)
var policy = ResiliencePolicy.Create()
.Fallback<ApiResponse>(opts =>
{
opts.FallbackValue = ApiResponse.Empty;
})
.Retry(opts =>
{
opts.MaxRetries = 3;
opts.BackoffType = BackoffType.Exponential;
opts.DelayMs = 200;
})
.CircuitBreaker(opts =>
{
opts.CircuitKey = "external-api";
opts.FailureThreshold = 10;
opts.BreakDuration = TimeSpan.FromSeconds(60);
})
.Timeout(TimeSpan.FromSeconds(10))
.Build();
Database Write
var policy = ResiliencePolicy.Create()
.Retry(opts =>
{
opts.MaxRetries = 2;
opts.BackoffType = BackoffType.Linear;
opts.DelayMs = 100;
// Only retry on transient DB errors
opts.RetryOnExceptions = new[] { typeof(SqlException) };
})
.Timeout(TimeSpan.FromSeconds(30))
.Build();
Low-Latency Read with Hedge
var policy = ResiliencePolicy.Create()
.Fallback<string>(opts => { opts.FallbackValue = string.Empty; })
.Timeout(TimeSpan.FromSeconds(2))
.Hedge(opts =>
{
opts.HedgeDelay = TimeSpan.FromMilliseconds(100); // P95 latency
opts.MaxHedgedAttempts = 1;
})
.Build();
Full Protection
var policy = ResiliencePolicy.Create()
.Fallback<Result<OrderDto>>(opts =>
{
opts.FallbackValue = Result<OrderDto>.Fail(
"Service temporarily unavailable.", ErrorType.Failure);
})
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.TokenBucket;
opts.BucketCapacity = 100;
opts.TokensPerInterval = 10;
opts.ReplenishmentInterval = TimeSpan.FromSeconds(1);
})
.Retry(opts =>
{
opts.MaxRetries = 3;
opts.BackoffType = BackoffType.Exponential;
})
.CircuitBreaker(opts =>
{
opts.CircuitKey = "order-service";
opts.FailureThreshold = 5;
opts.BreakDuration = TimeSpan.FromSeconds(30);
})
.Timeout(TimeSpan.FromSeconds(5))
.Build();
ResiliencePresets
Use built-in presets for common scenarios:
// For external API calls
var policy = ResiliencePresets.ForExternalApi();
// For database access
var policy = ResiliencePresets.ForDatabase();
// For critical paths with fallback
var policy = ResiliencePresets.ForCritical(fallbackValue: Result<string>.Fail("Unavailable", ErrorType.Failure));
Tips
Design Principles
- Always add Fallback when the caller cannot handle exceptions
- Put CircuitBreaker before Retry (already enforced by execution order) — the CB prevents retries against an open circuit
- Timeout applies per retry attempt — a 5s timeout with 3 retries = up to 15s total + backoff
- Hedge reduces P99 latency — not error recovery. Use Retry for errors, Hedge for latency.
- Use CircuitKey to share circuit state across multiple operations targeting the same service