Fallback Service
```typescript
// fallback-service.ts
interface FallbackOptions {
primary: () => Promise;
fallback: () => Promise | T;
shouldFallback?: (error: Error) => boolean;
onFallback?: (error: Error) => void;
timeout?: number;
}
async function withFallback(options: FallbackOptions): Promise {
const { primary, fallback, shouldFallback, onFallback, timeout = 5000 } = options;
try {
// Add timeout to primary
const result = await Promise.race([
primary(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
),
]);
return result;
} catch (error) {
// Check if we should use fallback
if (shouldFallback && !shouldFallback(error as Error)) {
throw error;
}
// Log fallback usage
onFallback?.(error as Error);
console.warn('Using fallback due to:', (error as Error).message);
// Return fallback value
const fallbackResult = fallback();
return fallbackResult instanceof Promise ? await fallbackResult : fallbackResult;
}
}
export { withFallback, FallbackOptions };
```
Practical Examples
```typescript
// product-service.ts
class ProductService {
private cache: Cache;
private searchClient: SearchClient;
// Example 1: Cache fallback
async getProduct(id: string): Promise {
return withFallback({
primary: () => this.fetchFromDatabase(id),
fallback: () => this.cache.get(product:${id}),
onFallback: (err) => metrics.increment('product.cache_fallback'),
});
}
// Example 2: Search with degraded results
async searchProducts(query: string): Promise {
return withFallback({
primary: async () => {
// Full-featured Elasticsearch search
return this.searchClient.search({
query,
facets: true,
suggestions: true,
personalization: true,
});
},
fallback: async () => {
// Degraded: Simple database LIKE query
const products = await db.products.findMany({
where: { name: { contains: query } },
take: 20,
});
return {
results: products,
facets: null, // Not available
suggestions: null, // Not available
degraded: true, // Signal to frontend
};
},
timeout: 2000,
});
}
// Example 3: Recommendations with default fallback
async getRecommendations(userId: string): Promise {
return withFallback({
primary: () => this.mlService.getPersonalizedRecommendations(userId),
fallback: () => this.getPopularProducts(), // Generic fallback
shouldFallback: (err) => err.message !== 'User not found',
});
}
}
```
Partial Response Pattern
```typescript
// dashboard-service.ts
interface DashboardData {
user: User;
stats: Stats | null;
notifications: Notification[] | null;
recommendations: Product[] | null;
errors: string[];
}
async function getDashboard(userId: string): Promise {
const errors: string[] = [];
// User is required - fail if unavailable
const user = await userService.getUser(userId);
// Stats are nice to have
const stats = await withFallback({
primary: () => statsService.getUserStats(userId),
fallback: () => null,
onFallback: () => errors.push('Stats temporarily unavailable'),
});
// Notifications are nice to have
const notifications = await withFallback({
primary: () => notificationService.getUnread(userId),
fallback: () => null,
onFallback: () => errors.push('Notifications temporarily unavailable'),
});
// Recommendations are nice to have
const recommendations = await withFallback({
primary: () => recommendationService.getForUser(userId),
fallback: () => null,
onFallback: () => errors.push('Recommendations temporarily unavailable'),
});
return { user, stats, notifications, recommendations, errors };
}
```
Feature Degradation with Flags
```typescript
// feature-degradation.ts
class FeatureDegradation {
private degradedFeatures = new Set();
async execute(
feature: string,
primary: () => Promise,
fallback: () => T | Promise
): Promise {
// Check if feature is already degraded
if (this.degradedFeatures.has(feature)) {
return fallback instanceof Function ? fallback() : fallback;
}
try {
return await primary();
} catch (error) {
// Auto-degrade feature after failures
this.degradedFeatures.add(feature);
// Schedule recovery check
setTimeout(() => this.checkRecovery(feature, primary), 30000);
return fallback instanceof Function ? fallback() : fallback;
}
}
private async checkRecovery(feature: string, healthCheck: () => Promise) {
try {
await healthCheck();
this.degradedFeatures.delete(feature);
console.log(Feature ${feature} recovered);
} catch {
// Still failing, check again later
setTimeout(() => this.checkRecovery(feature, healthCheck), 60000);
}
}
}
const degradation = new FeatureDegradation();
// Usage
const searchResults = await degradation.execute(
'elasticsearch',
() => elasticSearch.query(term),
() => sqlSearch.query(term)
);
```