# Best Practices Production-ready guidelines for using Murphy effectively and safely. ## General Principles ### 1. Start Simple, Add Complexity Gradually ```pascal // Start with just retry Policy := TRetryBuilder.Handle(Exception).Retry(3).Build; // Add circuit breaker when needed // Add fallback for critical paths // Combine multiple patterns only when necessary ``` ### 2. Use Specific Exception Types ```pascal // Good - Specific exceptions Policy := TRetryBuilder .Handle([EIdHTTPProtocolException, EIdSocketError]) .Retry(3) .Build; // Bad - Catches everything, may hide bugs Policy := TRetryBuilder .Handle(Exception) .Retry(3) .Build; ``` ### 3. Configure Timeouts Appropriately ```pascal // Good - Reasonable timeout based on SLA Policy := TRetryBuilder .Handle(Exception) .Retry(3) .Wait(TTimeSpan.FromSeconds(2)) // Based on service recovery time .Build; // Bad - Too short (likely to fail) Policy := TRetryBuilder.Handle(Exception).Retry(10).Wait(TTimeSpan.FromMilliseconds(10)).Build; // Bad - Too long (wastes user time) Policy := TRetryBuilder.Handle(Exception).Retry(5).Wait(TTimeSpan.FromMinutes(5)).Build; ``` ## Retry Pattern Best Practices ### Use Exponential Backoff for Network Operations ```pascal Policy := TRetryBuilder .Handle(EIdHTTPProtocolException) .Retry(5) .Wait(TTimeSpan.FromSeconds(1)) .When(function(Ctx: TRetryContext): Boolean begin Ctx.WaitDelay := TTimeSpan.FromMilliseconds( Ctx.WaitDelay.TotalMilliseconds * 2); Result := True; end) .Build; ``` ### Don't Retry Non-Idempotent Operations Without Careful Design ```pascal // Dangerous - May create duplicate records Policy.Execute(procedure begin Database.Execute('INSERT INTO orders...'); end); // Better - Use idempotency key or check before insert Policy.Execute( procedure begin if not OrderExists(OrderId) then Database.Execute('INSERT INTO orders...'); end); ``` ### Log Retry Attempts ```pascal Policy := TRetryBuilder .Handle(Exception) .Retry(3) .When(function(Ctx: TRetryContext): Boolean begin Log.Warning(Format('Retry attempt %d after %s', [Ctx.Attempts, Ctx.Exception.Message])); Result := True; end) .Build; ``` ## Circuit Breaker Best Practices ### Configure Based on Service Characteristics ```pascal // Fast-recovering service (network glitch) Policy := TCircuitBreakerBuilder .Handle(Exception) .Fail(5) .Within(TTimeSpan.FromSeconds(10)) .Build; // Slow-recovering service (deployment, restart) Policy := TCircuitBreakerBuilder .Handle(Exception) .Fail(3) .Within(TTimeSpan.FromMinutes(5)) .Build; ``` ### Monitor and Alert on Circuit State Changes ```pascal type TMonitoredCircuitBreaker = class private FPolicy: ICircuitBreakerPolicy; FLastState: TCircuitState; procedure CheckStateChange; public procedure Execute(AProc: TProc); end; procedure TMonitoredCircuitBreaker.Execute(AProc: TProc); begin FPolicy.Execute(AProc); CheckStateChange; end; procedure TMonitoredCircuitBreaker.CheckStateChange; begin var CurrentState := FPolicy.CircuitState; if CurrentState <> FLastState then begin Log.Warning(Format('Circuit state changed: %s → %s', [GetEnumName(TypeInfo(TCircuitState), Ord(FLastState)), GetEnumName(TypeInfo(TCircuitState), Ord(CurrentState))])); if CurrentState = TCircuitState.Open then AlertOps('Service circuit breaker opened'); FLastState := CurrentState; end; end; ``` ### Always Handle EBrokenCircuitException ```pascal try CircuitPolicy.Execute(procedure begin CallService; end); except on E: EBrokenCircuitException do begin Log.Error('Service unavailable - circuit is open'); // Provide fallback or user-friendly error end; end; ``` ## Fallback Best Practices ### Ensure Fallback is Reliable ```pascal // Good - Fallback doesn't throw exceptions Policy := TFallbackBuilder .Handle(Exception) .Fallback(function: string begin try Result := GetCachedData; except Result := 'Default Value'; // Ultimate fallback end; end) .Build; ``` ### Indicate Degraded Mode to Users ```pascal var Data := FallbackPolicy.Execute( function: string begin try Result := FetchLiveData; except ShowWarning('Using cached data - live data unavailable'); raise; end; end); ``` ### Cache Management ```pascal type TCachedService = class private FCache: string; FCacheTime: TDateTime; public function GetData: string; end; function TCachedService.GetData: string; var Policy: IFallbackPolicy; begin Policy := TFallbackBuilder .Handle(Exception) .Fallback(function: string begin var Age := SecondsBetween(Now, FCacheTime); if Age > 3600 then Log.Warning(Format('Cache is %d seconds old', [Age])); Result := FCache; end) .Build; Result := Policy.Execute( function: string begin Result := FetchFreshData; FCache := Result; FCacheTime := Now; end); end; ``` ## Rate Limit Best Practices ### Add Safety Margin ```pascal // If API allows 100 req/min, configure for 95 to add safety margin Policy := TRateLimitBuilder .Handle(Exception) .Allow(95) .Within(TTimeSpan.FromMinutes(1)) .Build; ``` ### Handle Rate Limit Rejections Gracefully ```pascal try RateLimitPolicy.Execute(procedure begin MakeAPICall; end); except on E: ERateLimitRejectedException do begin // Queue for later QueueForRetry(Request); // Or inform user ShowMessage('Request queued - rate limit reached'); end; end; ``` ### Use Separate Policies for Different Resources ```pascal type TAPIClient = class private FSearchRateLimit: IRateLimitPolicy; FUpdateRateLimit: IRateLimitPolicy; public constructor Create; end; constructor TAPIClient.Create; begin // Different limits for different endpoints FSearchRateLimit := TRateLimitBuilder.Handle(Exception) .Allow(100).Within(TTimeSpan.FromMinutes(1)).Build; FUpdateRateLimit := TRateLimitBuilder.Handle(Exception) .Allow(10).Within(TTimeSpan.FromMinutes(1)).Build; end; ``` ## Combining Patterns Best Practices ### Order Matters - Choose Wisely ```pascal // Recommended: Circuit outside Retry CircuitBreaker.Execute(procedure begin Retry.Execute(procedure begin CallService; end); end); // Circuit tracks retry failures // Opens faster on persistent issues ``` ### Test Pattern Combinations ```pascal procedure TestCombinedPolicies; var Retry: IRetryPolicy; Fallback: IFallbackPolicy; begin MurphyTestModeEnabled := True; Retry := TRetryBuilder.Handle(Exception).Retry(2).Build; Fallback := TFallbackBuilder.Handle(Exception) .Fallback(function: string begin Result := 'Fallback'; end).Build; // Test that retry happens before fallback var Attempts := 0; var Result := Fallback.Execute( function: string begin Retry.Execute( procedure begin Inc(Attempts); raise Exception.Create('Test'); end); end); Assert.AreEqual(3, Attempts); // Verify retry happened Assert.AreEqual('Fallback', Result); // Verify fallback activated MurphyTestModeEnabled := False; end; ``` ## Performance Considerations ### Reuse Policy Instances ```pascal // Good - Create once, use many times type TService = class private FRetryPolicy: IRetryPolicy; public constructor Create; procedure Operation1; procedure Operation2; end; constructor TService.Create; begin FRetryPolicy := TRetryBuilder.Handle(Exception).Retry(3).Build; end; procedure TService.Operation1; begin FRetryPolicy.Execute(procedure begin ... end); end; // Bad - Create new policy each time procedure BadExample; begin TRetryBuilder.Handle(Exception).Retry(3).Build .Execute(procedure begin ... end); end; ``` ### Don't Overuse Policies ```pascal // Bad - Unnecessary for local, reliable operations Policy.Execute(procedure begin LocalVariable := 1; end); // Good - Use only for operations that can actually fail Policy.Execute(procedure begin HTTP.Get(URL); end); ``` ## Monitoring and Observability ### Add Metrics ```pascal type TMetricsWrapper = class private FPolicy: IRetryPolicy; public RetryCount: Integer; SuccessAfterRetry: Integer; procedure Execute(AProc: TProc); end; procedure TMetricsWrapper.Execute(AProc: TProc); var Attempts: Integer = 0; begin FPolicy.When( function(Ctx: TRetryContext): Boolean begin Inc(FRetryCount); Result := True; end) .Execute(AProc); if Attempts > 1 then Inc(SuccessAfterRetry); end; ``` ### Log Important Events ```pascal // Circuit state changes // Retry exhaustion // Fallback activation // Rate limit rejections ``` ## Security Considerations ### Don't Log Sensitive Data ```pascal // Bad - May log passwords, tokens Policy.When(function(Ctx: TRetryContext): Boolean begin Log.Debug('Exception: ' + Ctx.Exception.ToString); // May contain secrets end); // Good - Log safely Policy.When(function(Ctx: TRetryContext): Boolean begin Log.Debug(Format('Retry attempt %d: %s', [Ctx.Attempts, Ctx.Exception.ClassName])); end); ``` ### Validate Before Retry ```pascal // Ensure retry won't cause security issues Policy.Execute( procedure begin if not ValidateRequest(Request) then raise ESecurityException.Create('Invalid request'); ProcessRequest(Request); end); ``` --- [Back to Index](../Home.md)