Voyager.Common.Resilience
1.7.1
dotnet add package Voyager.Common.Resilience --version 1.7.1
NuGet\Install-Package Voyager.Common.Resilience -Version 1.7.1
<PackageReference Include="Voyager.Common.Resilience" Version="1.7.1" />
<PackageVersion Include="Voyager.Common.Resilience" Version="1.7.1" />
<PackageReference Include="Voyager.Common.Resilience" />
paket add Voyager.Common.Resilience --version 1.7.1
#r "nuget: Voyager.Common.Resilience, 1.7.1"
#:package Voyager.Common.Resilience@1.7.1
#addin nuget:?package=Voyager.Common.Resilience&version=1.7.1
#tool nuget:?package=Voyager.Common.Resilience&version=1.7.1
Voyager.Common.Resilience
Resilience patterns for Railway Oriented Programming - Advanced failure handling extensions for Voyager.Common.Results.
Prevent cascading failures and improve system reliability with the Circuit Breaker pattern integrated into the Result monad.
Supports .NET Framework 4.8, .NET 6.0, and .NET 8.0 ๐
โจ Features
- ๐ก๏ธ Circuit Breaker Pattern - Prevent cascading failures in distributed systems
- ๐งต Thread-safe - Built with SemaphoreSlim for safe async operations
- ๐ฏ 3-State Model - Closed, Open, HalfOpen with automatic transitions
- ๐ Error Context Preservation - CircuitBreakerOpenError includes last failure details
- โก Result<T> Integration - Seamless Railway Oriented Programming
- ๐ง Configurable Policies - Failure thresholds, timeouts, recovery attempts
- ๐ฆ Minimal Dependencies - Only depends on Voyager.Common.Results
- ๐งช Fully Tested - Comprehensive test coverage across all target frameworks
๐ฆ Installation
# Install both packages
dotnet add package Voyager.Common.Results
dotnet add package Voyager.Common.Resilience
๐ Quick Start
using Voyager.Common.Results;
using Voyager.Common.Resilience;
// Create a circuit breaker policy
var circuitBreaker = new CircuitBreakerPolicy(
failureThreshold: 5, // Open after 5 consecutive failures
openTimeout: TimeSpan.FromSeconds(30), // Stay open for 30 seconds
halfOpenMaxAttempts: 3 // Allow 3 recovery test attempts
);
// Execute operations through the circuit breaker
var result = await GetUser(userId)
.BindWithCircuitBreakerAsync(
user => CallExternalApiAsync(user),
circuitBreaker
);
// Handle results including circuit breaker state
var message = result.Match(
onSuccess: data => $"Success: {data}",
onFailure: error => error.Type == ErrorType.CircuitBreakerOpen
? "Service temporarily unavailable - circuit breaker is open"
: $"Error: {error.Message}"
);
๐ฏ Circuit Breaker States
The circuit breaker implements a 3-state model and only counts infrastructure errors (Unavailable, Timeout, Database, Unexpected) towards failure thresholds. Business errors (Validation, NotFound, Permission, etc.) are ignored.
๐ข Closed (Normal Operation)
- All requests flow through to the protected operation
- Failures are counted
- When failure threshold is reached โ transitions to Open
๐ด Open (Failing Fast)
- Requests immediately fail with
ErrorType.CircuitBreakerOpen - No calls are made to the protected operation (prevents cascading failures)
- After
openTimeoutโ transitions to HalfOpen
๐ก HalfOpen (Testing Recovery)
- Limited number of test requests are allowed (
halfOpenMaxAttempts) - If all test requests succeed โ transitions to Closed
- If any test request fails โ transitions back to Open
๐ง Configuration Options
var policy = new CircuitBreakerPolicy(
failureThreshold: 10, // Number of failures before opening
openTimeout: TimeSpan.FromMinutes(1), // How long to stay open
halfOpenMaxAttempts: 5 // Test attempts in half-open state
);
Best Practices:
- failureThreshold: 3-10 for most scenarios (lower = more sensitive)
- openTimeout: 30-60 seconds for typical services (longer for slow recovery)
- halfOpenMaxAttempts: 3-5 attempts (enough to verify recovery without overload)
Infrastructure Errors (counted towards threshold):
ErrorType.Unavailable- Service down, network issuesErrorType.Timeout- Request exceeded time limitErrorType.Database- Database connection/query failedErrorType.Unexpected- Unhandled exceptions
Business Errors (ignored by circuit breaker):
ErrorType.Validation- Invalid inputErrorType.NotFound- Resource doesn't existErrorType.Business- Business rule violationErrorType.Permission/ErrorType.Unauthorized- Access deniedErrorType.Conflict- Duplicate/collision
๐ Usage Examples
Basic Circuit Breaker
var policy = new CircuitBreakerPolicy(
failureThreshold: 5,
openTimeout: TimeSpan.FromSeconds(30),
halfOpenMaxAttempts: 3
);
// Sync function
var result = await GetUserId()
.BindWithCircuitBreakerAsync(
id => _externalService.GetUserData(id),
policy
);
// Async function
var result = await GetUserIdAsync()
.BindWithCircuitBreakerAsync(
id => _externalService.GetUserDataAsync(id),
policy
);
With Async Result Chains
var result = await ValidateRequestAsync(request)
.BindAsync(req => AuthenticateAsync(req))
.BindWithCircuitBreakerAsync(
user => _externalApi.FetchDataAsync(user),
circuitBreaker
)
.MapAsync(data => ProcessData(data));
Monitoring Circuit State
// Check current state
switch (policy.State)
{
case CircuitBreakerState.Closed:
_logger.LogInfo("Circuit healthy");
break;
case CircuitBreakerState.Open:
_logger.LogWarning("Circuit open - service degraded");
break;
case CircuitBreakerState.HalfOpen:
_logger.LogInfo("Circuit testing recovery");
break;
}
// Manual reset if needed (e.g., after manual intervention)
policy.Reset();
Error Handling
var result = await operation.BindWithCircuitBreakerAsync(CallServiceAsync, policy);
result.Switch(
onSuccess: data => Console.WriteLine($"Success: {data}"),
onFailure: error =>
{
if (error.Type == ErrorType.CircuitBreakerOpen)
{
// Circuit breaker is open
var cbError = error; // Contains last failure in message
_logger.LogWarning($"Circuit open: {error.Message}");
// Implement fallback behavior
return GetCachedData();
}
else
{
// Other error types
_logger.LogError($"Operation failed: {error.Message}");
}
}
);
Combining with Retry
using Voyager.Common.Results.Extensions;
// Retry for transient failures + Circuit Breaker for cascading failures
var result = await GetConnectionAsync()
.BindWithRetryAsync(
conn => ExecuteQueryAsync(conn),
RetryPolicies.TransientErrors(maxAttempts: 3)
)
.BindWithCircuitBreakerAsync(
data => CallDownstreamServiceAsync(data),
circuitBreaker
);
๐๏ธ Architecture
The Resilience library is designed as a separate package to:
- โ Keep core Results library dependency-free
- โ Allow independent versioning of resilience patterns
- โ Enable optional adoption (use only what you need)
- โ Future-proof for additional patterns (Bulkhead, RateLimiter, etc.)
See ADR-0004 for architectural rationale.
๐ Related Packages
- Voyager.Common.Results - Core Result pattern library (required dependency)
- Polly - More advanced resilience library (if you need Bulkhead, Rate Limiter, etc.)
๐ Documentation
- Main Documentation - Voyager.Common.Results overview
- ADR-0004 - Circuit Breaker design decisions
- CHANGELOG - Version history
๐ค Contributing
Contributions are welcome! Please see CONTRIBUTING.md.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Version History
See CHANGELOG.md for version history and release notes.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- Voyager.Common.Results (>= 1.7.1)
-
net6.0
- Voyager.Common.Results (>= 1.0.0)
-
net8.0
- Voyager.Common.Results (>= 1.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Voyager.Common.Resilience:
| Package | Downloads |
|---|---|
|
Voyager.Common.Proxy.Client
HTTP client proxy generation for C# interfaces with Result<T> support |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.7.1 | 101 | 2/3/2026 |
| 1.7.0 | 76 | 2/3/2026 |
| 1.7.0-preview.2 | 49 | 1/31/2026 |
| 1.7.0-preview.1 | 40 | 1/31/2026 |
| 1.6.1-preview.1 | 28 | 2/3/2026 |
| 1.6.0 | 87 | 1/30/2026 |
| 1.6.0-preview.5.4 | 40 | 1/30/2026 |
| 1.6.0-preview.5 | 47 | 1/28/2026 |
| 1.6.0-preview.4 | 36 | 1/28/2026 |
| 1.6.0-preview.3 | 40 | 1/28/2026 |
| 1.6.0-preview.2 | 42 | 1/28/2026 |