Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Resilience Patterns in .NET with Polly: Practical Scenarios

Tech Apr 22 31
using System;

namespace ResilienceDemo.Core
{
    public abstract class ResilienceScenario
    {
        public abstract string Name { get; }

        public void Log(string message, ConsoleColor color)
        {
            lock (typeof(Console))
            {
                var originalColor = Console.ForegroundColor;
                Console.ForegroundColor = color;
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}");
                Console.ForegroundColor = originalColor;
            }
        }

        public void Info(string message) => Log(message, ConsoleColor.Cyan);
        public void Success(string message) => Log(message, ConsoleColor.Green);
        public void Error(string message) => Log(message, ConsoleColor.Red);

        public abstract void Execute();
    }
}

Retry Pattern

using Polly;
using System;

namespace ResilienceDemo.Scenarios
{
    public class RetryScenario : ResilienceScenario
    {
        public override string Name => "Retry Policy";
        private int tokenRefreshCount = 0;

        public override void Execute()
        {
            var retryPolicy = Policy
                .Handle<AuthorizationException>()
                .Or<RateLimitException>()
                .Or<Exception>(ex => ex.Message == "Transient network glitch")
                .Retry(3, (exception, retryCount) =>
                {
                    Error($"Attempt {retryCount} failed: {exception.Message}. Retrying...");
                    RefreshCredentials();
                });

            try
            {
                retryPolicy.Execute(CallSecureApi);
                Success("API request successful.");
            }
            catch (Exception ex)
            {
                Error($"Request ultimately failed: {ex.Message}");
            }
        }

        private void RefreshCredentials()
        {
            tokenRefreshCount++;
            Info("Refreshed authentication token.");
        }

        private void CallSecureApi()
        {
            if (tokenRefreshCount < 1) throw new AuthorizationException("Invalid token");
            if (tokenRefreshCount < 2) throw new RateLimitException("Too many requests");
            if (tokenRefreshCount < 3 && DateTime.Now.Millisecond % 2 == 0) throw new Exception("Transient network glitch");
            Info("Fetching data from API...");
        }
    }

    public class AuthorizationException : Exception { public AuthorizationException(string msg) : base(msg) {} }
    public class RateLimitException : Exception { public RateLimitException(string msg) : base(msg) {} }
}

Timeout Pattern

using Polly;
using Polly.Timeout;
using System;
using System.Threading;

namespace ResilienceDemo.Scenarios
{
    public class TimeoutScenario : ResilienceScenario
    {
        public override string Name => "Timeout Policy";

        public override void Execute()
        {
            var timeoutPolicy = Policy.Timeout(2, TimeoutStrategy.Pessimistic);

            for (int i = 1; i <= 5; i++)
            {
                try
                {
                    Info($"Executing request {i}...");
                    timeoutPolicy.Execute(FetchRemoteConfig);
                    Success($"Request {i} completed.");
                }
                catch (TimeoutRejectedException ex)
                {
                    Error($"Request {i} timed out: {ex.Message}");
                }
            }
        }

        private void FetchRemoteConfig()
        {
            var latency = new Random().Next(0, 4000);
            Thread.Sleep(latency);
            if (latency > 2000) throw new OperationCanceledException();
        }
    }
}

Fallback Pattern

using Polly;
using System;
using System.Threading;

namespace ResilienceDemo.Scenarios
{
    public class FallbackScenario : ResilienceScenario
    {
        public override string Name => "Fallback Policy";
        private int networkLatency = 1;

        public override void Execute()
        {
            var fallbackPolicy = Policy.Handle<GatewayTimeoutException>()
                .Fallback(() =>
                {
                    networkLatency++;
                    Error($"Service overloaded. Adjusting latency tolerance to {networkLatency}s.");
                });

            try
            {
                for (int i = 0; i < 5; i++)
                {
                    fallbackPolicy.Execute(DownloadAsset);
                }
            }
            catch (Exception ex)
            {
                Error($"Critical failure: {ex.Message}");
            }
        }

        private void DownloadAsset()
        {
            if (networkLatency < 3)
            {
                Thread.Sleep(networkLatency * 1000);
                throw new GatewayTimeoutException("Download exceeded current tolerance");
            }
            
            Thread.Sleep(networkLatency * 1000);
            Success("Asset downloaded successfully.");
            networkLatency = 2; // Simulate fluctuating network
        }
    }

    public class GatewayTimeoutException : Exception { public GatewayTimeoutException(string msg) : base(msg) {} }
}

Circuit Breaker Patttern

using Polly;
using Polly.CircuitBreaker;
using System;

namespace ResilienceDemo.Scenarios
{
    public class CircuitBreakerScenario : ResilienceScenario
    {
        public override string Name => "Circuit Breaker Policy";

        public override void Execute()
        {
            var breakerPolicy = Policy.Handle<DatabaseUnavailableException>()
                .CircuitBreaker(3, TimeSpan.FromSeconds(5));

            for (int i = 1; i <= 6; i++)
            {
                try
                {
                    Info($"Processing transaction {i}...");
                    breakerPolicy.Execute(WriteToDatabase);
                }
                catch (BrokenCircuitException)
                {
                    Error("Circuit is open. Request blocked.");
                }
                catch (Exception ex)
                {
                    Error($"Transaction failed: {ex.Message}");
                }
            }
        }

        private void WriteToDatabase() => throw new DatabaseUnavailableException("Primary replica offline");
    }

    public class DatabaseUnavailableException : Exception { public DatabaseUnavailableException(string msg) : base(msg) {} }
}

Advanced Circuit Breaker Pattern

using Polly;
using Polly.CircuitBreaker;
using System;
using System.Threading;

namespace ResilienceDemo.Scenarios
{
    public class AdvancedCircuitBreakerScenario : ResilienceScenario
    {
        public override string Name => "Advanced Circuit Breaker Policy";

        public override void Execute()
        {
            var advancedBreaker = Policy.Handle<SmtpException>()
                .AdvancedCircuitBreaker(
                    failureThreshold: 0.5,
                    minimumThroughput: 4,
                    samplingDuration: TimeSpan.FromSeconds(5),
                    durationOfBreak: TimeSpan.FromSeconds(10),
                    onBreak: (ex, delay) => Error($"Circuit tripped for {delay.TotalSeconds}s. Reason: {ex.Message}"),
                    onHalfOpen: () => Info("Circuit is half-open. Testing connection..."),
                    onReset: () => Success("Circuit closed. Service restored.")
                );

            for (int i = 1; i <= 10; i++)
            {
                try
                {
                    advancedBreaker.Execute(SendNotification);
                }
                catch (BrokenCircuitException)
                {
                    Error("Blocked by open circuit.");
                }
                catch (Exception ex)
                {
                    Error($"Notification failed: {ex.Message}");
                }
                finally
                {
                    Thread.Sleep(300);
                }
            }
        }

        private void SendNotification()
        {
            if (new Random().Next(2) == 0) throw new SmtpException("Connection refused");
            Success("Notification sent.");
        }
    }

    public class SmtpException : Exception { public SmtpException(string msg) : base(msg) {} }
}

Bulkhead Isolasion Pattern

using Polly;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ResilienceDemo.Scenarios
{
    public class BulkheadScenario : ResilienceScenario
    {
        public override string Name => "Bulkhead Isolation Policy";

        public override void Execute()
        {
            var bulkhead = Policy.Bulkhead(3);

            for (int i = 1; i <= 10; i++)
            {
                var taskId = i;
                Task.Run(() =>
                {
                    try
                    {
                        bulkhead.Execute(() => ProcessVideo(taskId));
                    }
                    catch (BulkheadRejectedException)
                    {
                        Error($"Task {taskId} rejected: Queue capacity reached.");
                    }
                });
            }

            Thread.Sleep(5000); // Wait for tasks to complete
        }

        private void ProcessVideo(int id)
        {
            Info($"Processing video {id}...");
            Thread.Sleep(1000);
            Success($"Video {id} processed.");
        }
    }
}

Policy Wrap

using Polly;
using Polly.Timeout;
using System;
using System.Threading;

namespace ResilienceDemo.Scenarios
{
    public class PolicyWrapScenario : ResilienceScenario
    {
        public override string Name => "Policy Wrap";

        public override void Execute()
        {
            var fallbackPolicy = Policy.Handle<TimeoutRejectedException>()
                .Fallback(() =>
                {
                    Error("Fallback: Serving cached response due to timeout.");
                });

            var timeoutPolicy = Policy.Timeout(1, TimeoutStrategy.Pessimistic);

            var combinedPolicy = fallbackPolicy.Wrap(timeoutPolicy);

            for (int i = 1; i <= 3; i++)
            {
                try
                {
                    combinedPolicy.Execute(CallExternalApi);
                }
                catch (Exception ex)
                {
                    Error($"Unhandled exception: {ex.Message}");
                }
            }
        }

        private void CallExternalApi()
        {
            var delay = new Random().Next(0, 2000);
            Thread.Sleep(delay);
            if (delay > 1000) throw new OperationCanceledException();
            Success($"API responded in {delay}ms.");
        }
    }
}

Cache Pattern

using Polly;
using Polly.Caching;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Threading;

namespace ResilienceDemo.Scenarios
{
    public class CacheScenario : ResilienceScenario
    {
        public override string Name => "Cache Policy";

        public override void Execute()
        {
            var memoryCache = new MemoryCache(new MemoryCacheOptions());
            var cacheProvider = new MemoryCacheProvider(memoryCache);

            var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromSeconds(5));

            for (int i = 0; i < 4; i++)
            {
                var context = new Context($"Product-99");
                var result = cachePolicy.Execute(ctx => FetchProductDetails(), context);
                Info($"Result: {result}");
                Thread.Sleep(2000);
            }
        }

        private string FetchProductDetails()
        {
            Info("Querying database for product details...");
            return "Product: Laptop, Price: $1200";
        }
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Comprehensive Guide to Hive SQL Syntax and Operations

This article provides a detailed walkthrough of Hive SQL, categorizing its features and syntax for practical use. Hive SQL is segmented into the following categories: DDL Statements: Operations on...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.