Skip to content

Graceful Degradation

Definition

Graceful Degradation Definition


Degradation Strategies

Degradation Strategies


Implementation Examples

// Pattern 1: Fallback with Circuit Breaker
@Service
public class ProductService {

    @CircuitBreaker(name = "recommendations", fallbackMethod = "fallbackRecommendations")
    public List<Product> getRecommendations(String userId) {
        return recommendationClient.getRecommendations(userId);
    }

    // Fallback when recommendation service is down
    public List<Product> fallbackRecommendations(String userId, Exception e) {
        log.warn("Recommendation service unavailable, using fallback", e);

        // Try cached recommendations
        List<Product> cached = cache.get("recommendations:" + userId);
        if (cached != null) {
            return cached;
        }

        // Return popular products as default
        return productRepository.findTopByOrderBySalesDesc(10);
    }
}

// Pattern 2: Feature Toggle on Failure
@Service
public class SearchService {

    private final AtomicBoolean advancedSearchEnabled = new AtomicBoolean(true);

    public SearchResults search(SearchQuery query) {
        if (advancedSearchEnabled.get()) {
            try {
                return elasticSearch.search(query);
            } catch (ElasticSearchException e) {
                advancedSearchEnabled.set(false);  // Disable feature
                scheduleRetry();  // Try to re-enable later
                log.error("Advanced search failed, degrading to basic", e);
            }
        }

        // Degraded: basic database search
        return basicDatabaseSearch(query);
    }

    private void scheduleRetry() {
        scheduler.schedule(() -> {
            try {
                elasticSearch.healthCheck();
                advancedSearchEnabled.set(true);
                log.info("Advanced search re-enabled");
            } catch (Exception e) {
                scheduleRetry();  // Keep trying
            }
        }, 30, TimeUnit.SECONDS);
    }
}

Tiered Degradation

Tiered Degradation Levels


Resilience Patterns

// Circuit Breaker Pattern
@Configuration
public class ResilienceConfig {

    @Bean
    public CircuitBreakerConfig circuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
            .failureRateThreshold(50)  // Open at 50% failure rate
            .waitDurationInOpenState(Duration.ofSeconds(30))
            .slidingWindowSize(10)
            .build();
    }
}

// Bulkhead Pattern - Isolate failures
@Service
public class OrderService {

    @Bulkhead(name = "inventory", type = Bulkhead.Type.THREADPOOL)
    public StockResult checkInventory(String productId) {
        return inventoryClient.check(productId);
    }

    @Bulkhead(name = "payment", type = Bulkhead.Type.THREADPOOL)
    public PaymentResult processPayment(PaymentRequest request) {
        return paymentClient.process(request);
    }

    // Inventory issues don't affect payment processing
    // Each has its own thread pool
}

// Timeout with Fallback
@Service
public class PricingService {

    @TimeLimiter(name = "pricing", fallbackMethod = "fallbackPrice")
    public CompletableFuture<Price> getDynamicPrice(String productId) {
        return CompletableFuture.supplyAsync(() ->
            pricingEngine.calculate(productId)
        );
    }

    public CompletableFuture<Price> fallbackPrice(String productId, Exception e) {
        // Return list price when dynamic pricing times out
        return CompletableFuture.completedFuture(
            productRepository.getListPrice(productId)
        );
    }
}

Caching for Degradation

// Cache-aside with stale data fallback
@Service
public class ContentService {

    private final Cache<String, Content> cache;
    private final Cache<String, Content> staleCache;  // Longer TTL

    public Content getContent(String contentId) {
        // Try fresh cache
        Content cached = cache.getIfPresent(contentId);
        if (cached != null) {
            return cached;
        }

        try {
            // Fetch from source
            Content fresh = contentApi.fetch(contentId);
            cache.put(contentId, fresh);
            staleCache.put(contentId, fresh);  // Also update stale cache
            return fresh;

        } catch (Exception e) {
            log.warn("Content fetch failed, trying stale cache", e);

            // Fallback to stale data
            Content stale = staleCache.getIfPresent(contentId);
            if (stale != null) {
                return stale.withStaleWarning();
            }

            throw new ContentUnavailableException(contentId, e);
        }
    }
}

// Response indicating degraded state
public class ApiResponse<T> {
    private T data;
    private boolean degraded;
    private String degradedReason;
    private Instant dataAsOf;  // When data was fetched

    public static <T> ApiResponse<T> degraded(T fallbackData, String reason) {
        return new ApiResponse<>(fallbackData, true, reason, Instant.now());
    }
}

Frontend Degradation

Frontend Graceful Degradation


Tips & Tricks

Graceful Degradation Tips