
7 Fallback Strategies for Resilient Systems: Instant Answers in Crisis
In software system design, fallback strategies are mechanisms that ensure a system remains responsive when queries fail or take too long due to network issues, service outages, or high load. These strategies provide alternative responses—such as cached data, defaults, or approximate results—to prevent user-facing delays or errors. The following seven strategies are designed for scenarios where the system must synchronously return an instant answer, maintaining a seamless user experience even under adverse conditions. Each strategy includes a real-world scenario and a C# implementation, making this guide ideal for developers building fault-tolerant applications.
1- Caching as a Fallback
Description:
The system uses cached data when the primary data source (e.g., database, API) is slow or unavailable, serving stale data to provide an instant response.
Real-World Scenario:
A news website caches article content in Redis. If a database query for the latest articles is slow due to high load, the site instantly serves cached articles to avoid delays.
public class ArticleService
{
private readonly NewsDbContext _dbContext;
private readonly IDatabase _redis;
private readonly TimeSpan _cacheTTL = TimeSpan.FromMinutes(10);
private const string CACHE_KEY = "latest_articles";
public ArticleService(NewsDbContext dbContext, IConnectionMultiplexer redis)
{
_dbContext = dbContext;
_redis = redis.GetDatabase();
}
public async Task<List<Article>> GetLatestArticlesAsync(int count, CancellationToken cancellationToken = default)
{
// Try database query with 3-second timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
try
{
var articles = await _dbContext.Articles
.OrderByDescending(a => a.PublishedAt)
.Take(count)
.ToListAsync(linkedCts.Token);
// Cache the results in Redis
string serializedArticles = JsonConvert.SerializeObject(articles);
await _redis.StringSetAsync(CACHE_KEY, serializedArticles, _cacheTTL);
return articles;
}
catch (OperationCanceledException)
{
// Database query timed out, fall back to cache
string cachedArticles = await _redis.StringGetAsync(CACHE_KEY);
if (!cachedArticles.IsNullOrEmpty)
{
return JsonConvert.DeserializeObject<List<Article>>(cachedArticles);
}
// If cache is empty, rethrow the cancellation exception
throw;
}
}
}
2- Default Response as a Fallback
Description:
Returns a predefined safe default response when a query fails or takes too long, ensuring an instant answer to prevent application crashes and maintain minimal functionality.
Real-World Scenario:
An enterprise HR web app fails to fetch employee settings from a configuration service due to a server outage. It instantly returns default settings (e.g., default dashboard layout) to keep the app functional for HR staff.
public class ConfigService
{
private readonly HttpClient _httpClient;
public ConfigService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Config> GetConfigAsync()
{
try
{
var response = await _httpClient.GetFromJsonAsync<Config>("https://config.api/settings");
return response ?? GetDefaultConfig();
}
catch (Exception ex)
{
Console.WriteLine($"Config fetch failed: {ex.Message}, using default config");
return GetDefaultConfig();
}
}
private Config GetDefaultConfig() => new Config { DashboardLayout = "Standard", MaxItems = 10 };
}
3- Approximation as a Fallback
Description:
Provides an approximate result when the exact query computation is too slow or unavailable. For example, use simpler algorithms for an instant response.
Real-World Scenario:
An enterprise logistics web app’s route optimization query takes too long due to high load. It instantly returns an estimated delivery time based on historical averages to keep dispatchers informed.
public class RouteService
{
private readonly LogisticsDbContext _dbContext;
public RouteService(LogisticsDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<TimeSpan> GetEstimatedDeliveryTimeAsync(string origin, string destination)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
try
{
var route = await _dbContext.Routes
.Where(r => r.Origin == origin && r.Destination == destination)
.Select(r => new { r.DistanceKm })
.FirstOrDefaultAsync(linkedCts.Token);
if (route == null)
throw new InvalidOperationException("Route not found.");
// Simulate complex computation (e.g., traffic, weather)
await Task.Delay(1000, linkedCts.Token);
double estimatedHours = route.DistanceKm / 50.0; // Assume 50 km/h
return TimeSpan.FromHours(estimatedHours);
}
catch (OperationCanceledException)
{
// Fallback to historical average if query times out
var route = await _dbContext.Routes
.Where(r => r.Origin == origin && r.Destination == destination)
.Select(r => r.AverageDeliveryTime)
.FirstOrDefaultAsync();
return route != default ? route : TimeSpan.FromHours(2); // Default fallback
}
}
}
4- Partial Answers as a Fallback
Description:
Returns a subset of the requested data when the full query result is delayed or unavailable, providing an instant partial response to ensure usability.
Real-World Scenario:
In an enterprise inventory web app, a user requests a product report (stock, supplier details, sales trends), and the system instantly returns stock and supplier details from the local database. If the external API call for sales trends fails, the UI displays only the available partial data, ensuring usability.
public class ProductReportService
{
private readonly AppDbContext _dbContext;
private readonly IExternalApi _externalApi;
public ProductReportService(AppDbContext dbContext, IExternalApi externalApi)
{
_dbContext = dbContext;
_externalApi = externalApi;
}
public ProductReport GetProductReport(int id)
{
var report = new ProductReport();
try
{
report.Stock = _dbContext.Products.Find(id).Stock;
report.Supplier = _dbContext.Suppliers.Find(id).Details;
report.SalesTrends = _externalApi.GetSalesTrends(id); // External API call
}
catch (ApiException)
{
return report; // Return partial data if API fails
}
return report;
}
}
5- Feature Toggle as a Fallback
Description:
Uses feature toggles to switch to another stable implementation when the primary implementation fails, providing an instant answer during rollouts or issues.
Real-World Scenario:
An enterprise search web app toggles to a legacy keyword-based search if the new AI-powered search query is too slow during a high-traffic period, ensuring instant results for users.
public class SearchService
{
private readonly IAiSearchService _aiSearchService;
private readonly ILegacySearchService _legacySearchService;
private readonly IFeatureToggleService _featureToggle;
public SearchService(IAiSearchService aiSearchService, ILegacySearchService legacySearchService, IFeatureToggleService featureToggle)
{
_aiSearchService = aiSearchService;
_legacySearchService = legacySearchService;
_featureToggle = featureToggle;
}
public async Task<List<string>> SearchAsync(string query)
{
if (await _featureToggle.IsFeatureEnabledAsync("AiSearch"))
{
try
{
return await _aiSearchService.SearchAsync(query);
}
catch (Exception ex)
{
Console.WriteLine($"AI search failed: {ex.Message}, falling back to legacy search");
}
}
return await _legacySearchService.SearchAsync(query);
}
}
6- Precomputed Results as a Fallback
Description:
Uses precomputed results to instantly respond to queries, if the process exceeds the acceptable processing time.
Real-World Scenario:
An enterprise financial web app precomputes daily revenue summaries overnight. If a real-time revenue query is slow, it instantly serves the precomputed summary.
public class FinancialService
{
private readonly IRevenueService _revenueService;
private readonly IPrecomputedRevenueService _precomputedRevenueService;
public FinancialService(IRevenueService revenueService, IPrecomputedRevenueService precomputedRevenueService)
{
_revenueService = revenueService;
_precomputedRevenueService = precomputedRevenueService;
}
public async Task<RevenueResult> GetRevenueAsync(string period)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
try
{
var data = await _revenueService.GetRealTimeRevenueAsync(period, linkedCts.Token);
await _precomputedRevenueService.SavePrecomputedRevenueAsync(period, data);
return new RevenueResult { Period = period, Data = data, Status = "Real-Time" };
}
catch (OperationCanceledException)
{
// Query timed out, fall back to precomputed data
var precomputedData = await _precomputedRevenueService.GetPrecomputedRevenueAsync(period);
if (precomputedData != null)
{
return new RevenueResult { Period = period, Data = precomputedData, Status = "Precomputed" };
}
// If no precomputed data, return default
throw;
}
}
}
7- Alternative Service as a Fallback
Description:
Switches to a secondary service or endpoint when the primary service fails or is too slow, ensuring an instant response with an alternative provider.
Real-World Scenario:
An enterprise e-commerce web app fails to process a payment with the primary payment gateway (e.g., Stripe) due to an outage. It instantly switches to a secondary gateway (e.g., PayPal) to complete the transaction.
public class PaymentService
{
private readonly IPrimaryPaymentGateway _primaryGateway;
private readonly ISecondaryPaymentGateway _secondaryGateway;
public PaymentService(IPrimaryPaymentGateway primaryGateway, ISecondaryPaymentGateway secondaryGateway)
{
_primaryGateway = primaryGateway;
_secondaryGateway = secondaryGateway;
}
public async Task<PaymentResult> ProcessPaymentAsync(decimal amount, string transactionId)
{
try
{
return await _primaryGateway.ProcessPaymentAsync(amount, transactionId);
}
catch (Exception ex)
{
Console.WriteLine($"Primary gateway failed: {ex.Message}, using secondary gateway");
try
{
return await _secondaryGateway.ProcessPaymentAsync(amount, transactionId);
}
catch (Exception secondaryEx)
{
Console.WriteLine($"Secondary gateway failed: {secondaryEx.Message}, returning error");
return new PaymentResult { Status = "Failed", Message = "All payment gateways unavailable" };
}
}
}
}
Leave a Reply
Your e-mail address will not be published. Required fields are marked *