# Combining Patterns Murphy's true power comes from combining multiple resilience patterns together. ## Common Combinations ### Retry + Fallback Retry failed operations, then fall back to cached data if retries are exhausted. ```pascal function GetUserData(UserId: Integer): string; var RetryPolicy: IRetryPolicy; FallbackPolicy: IFallbackPolicy; begin RetryPolicy := TRetryBuilder .Handle(EIdHTTPProtocolException) .Retry(3) .Wait(TTimeSpan.FromSeconds(1)) .Build; FallbackPolicy := TFallbackBuilder .Handle(Exception) .Fallback(function: string begin Result := GetCachedUserData(UserId); end) .Build; Result := FallbackPolicy.Execute( function: string begin RetryPolicy.Execute( procedure begin Result := HTTP.Get(Format('/users/%d', [UserId])); end); end); end; ``` ### Circuit Breaker + Fallback Fast-fail when service is down, provide fallback data immediately. ```pascal function FetchData: string; var CircuitPolicy: ICircuitBreakerPolicy; FallbackPolicy: IFallbackPolicy; begin CircuitPolicy := TCircuitBreakerBuilder .Handle(Exception) .Fail(5) .Within(TTimeSpan.FromSeconds(30)) .Build; FallbackPolicy := TFallbackBuilder .Handle([Exception, EBrokenCircuitException]) .Fallback(function: string begin WriteLn('Using fallback data'); Result := CachedData; end) .Build; Result := FallbackPolicy.Execute( function: string begin CircuitPolicy.Execute(procedure begin Result := FetchFromAPI; end); end); end; ``` ### Retry + Circuit Breaker Retry transient failures, but open circuit for persistent failures. ```pascal var Retry: IRetryPolicy; Circuit: ICircuitBreakerPolicy; begin Retry := TRetryBuilder.Handle(Exception).Retry(2).Build; Circuit := TCircuitBreakerBuilder.Handle(Exception).Fail(5).Build; // Circuit wraps retry - tracks retry exhaustion as failures Circuit.Execute( procedure begin Retry.Execute(procedure begin CallService; end); end); end; ``` ### Rate Limit + Retry Retry when rate limit is exceeded after a delay. ```pascal var RateLimit: IRateLimitPolicy; Retry: IRetryPolicy; begin RateLimit := TRateLimitBuilder.Handle(Exception) .Allow(100).Within(TTimeSpan.FromMinutes(1)).Build; Retry := TRetryBuilder .Handle(ERateLimitRejectedException) .Retry(3) .Wait(TTimeSpan.FromSeconds(20)) .Build; Retry.Execute( procedure begin RateLimit.Execute(procedure begin MakeAPICall; end); end); end; ``` ### Complete Stack: Retry + Circuit Breaker + Fallback Maximum resilience for critical operations. ```pascal function GetCriticalData: string; var Retry: IRetryPolicy; Circuit: ICircuitBreakerPolicy; Fallback: IFallbackPolicy; begin Retry := TRetryBuilder .Handle(EIdHTTPProtocolException) .Retry(2) .Wait(TTimeSpan.FromSeconds(1)) .Build; Circuit := TCircuitBreakerBuilder .Handle(Exception) .Fail(5) .Within(TTimeSpan.FromMinutes(1)) .Build; Fallback := TFallbackBuilder .Handle([Exception, EBrokenCircuitException]) .Fallback(function: string begin Result := GetFromCache; end) .Build; // Fallback → Circuit → Retry → Actual Operation Result := Fallback.Execute( function: string begin Circuit.Execute( procedure begin Retry.Execute( procedure begin Result := FetchFromAPI; end); end); end); end; ``` ## Ordering Matters The order in which you combine policies affects behavior: ### Circuit Outside Retry (Recommended) ```pascal Circuit.Execute(procedure begin Retry.Execute(procedure begin CallService; end); end); ``` - Circuit tracks retry exhaustion as failures - Opens after multiple retry failures - More responsive to persistent issues ### Retry Outside Circuit ```pascal Retry.Execute(procedure begin Circuit.Execute(procedure begin CallService; end); end); ``` - Retries even after circuit opens - May waste retries on open circuit - Less efficient ## Best Practices 1. **Start simple** - Add patterns incrementally 2. **Test combinations** - Ensure they interact correctly 3. **Consider order** - Think about execution flow 4. **Log interactions** - Track which policy triggered 5. **Monitor metrics** - Understand actual behavior ## Real-World Example ```pascal type TResilientAPIClient = class private FRetry: IRetryPolicy; FCircuit: ICircuitBreakerPolicy; FRateLimit: IRateLimitPolicy; FFallback: IFallbackPolicy; public constructor Create; function GetUser(UserId: Integer): TJSONObject; end; constructor TResilientAPIClient.Create; begin FRateLimit := TRateLimitBuilder.Handle(Exception) .Allow(100).Within(TTimeSpan.FromMinutes(1)).Build; FRetry := TRetryBuilder.Handle(EIdHTTPProtocolException) .Retry(3).Wait(TTimeSpan.FromSeconds(1)).Build; FCircuit := TCircuitBreakerBuilder.Handle(Exception) .Fail(10).Within(TTimeSpan.FromMinutes(5)).Build; FFallback := TFallbackBuilder .Handle([Exception, EBrokenCircuitException, ERateLimitRejectedException]) .Fallback(function: TJSONObject begin Result := GetCachedUser; end) .Build; end; function TResilientAPIClient.GetUser(UserId: Integer): TJSONObject; begin // Fallback → Circuit → Retry → RateLimit → API Result := FFallback.Execute( function: TJSONObject begin FCircuit.Execute( procedure begin FRetry.Execute( procedure begin FRateLimit.Execute( procedure begin Result := FetchUserFromAPI(UserId); end); end); end); end); end; ``` --- [Back to Index](../README.md)