# Building Resilient Cloud Applications With .NET

> By designing your applications with resilience in mind, you can create robust and reliable systems, even when the going gets tough. In this newsletter, we'll explore the tools and techniques we have in .NET to build resilient systems.

Published: 2024-05-11. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet

.NET 8 introduced the `Microsoft.Extensions.Resilience` and `Microsoft.Extensions.Http.Resilience` packages, both built on top of Polly.
You compose retry, timeout, circuit breaker, fallback, hedging, and rate limiter strategies into a resilience pipeline, and the order you configure them matters.
For outgoing HTTP calls, `AddStandardResilienceHandler` applies a ready-made pipeline.

From my experience working with microservices systems, things don't always go as planned.
Network requests randomly fail, application servers become overloaded, and unexpected errors appear.
That's where resilience comes in.

Resilient applications can recover from transient failures and continue to function.
Resilience is achieved by designing applications that can handle failures gracefully and recover quickly.

By designing your applications with resilience in mind, you can create robust and reliable systems, even when the going gets tough.

In this newsletter, we'll explore the tools and techniques we have in .NET to build resilient systems.

## Resilience: Why You Should Care

Sending HTTP requests is a common approach for remote communication between services.
However, HTTP requests are susceptible to failures from network or server issues.
These failures can disrupt service availability, especially as dependencies increase and the risk of cascading failures grows.

So, how can you improve the resilience of your applications and services?

Here are a few strategies you can consider to increase resilience:

- **Retries**: Retry requests that fail due to transient errors.
- **Timeouts**: Cancel requests that exceed a specified time limit.
- **Fallbacks**: Define alternative actions or results for failed operations.
- **Circuit Breakers**: Temporarily suspend communication with unavailable services.

You can use these strategies individually or in combination for optimal HTTP request resilience.

Let's see how we can introduce resilience in a .NET application.

## Resilience Pipelines

With .NET 8, integrating resilience into your applications has become much simpler.
We can use `Microsoft.Extensions.Resilience` and `Microsoft.Extensions.Http.Resilience`, which are built on top of [Polly](https://github.com/App-vNext/Polly).
Polly is a .NET resilience and transient fault-handling library.
Polly allows us to define resilience strategies such as retry, circuit breaker, timeout, rate-limiting, fallback, and hedging.

Polly received a new API surface in its latest version (V8), which was implemented in collaboration with Microsoft.
You can learn more about the [**Polly V8 API in this video**](https://youtu.be/PqVQFUCTzUM).

If you were previously using `Microsoft.Extensions.Http.Polly`, it is recommended that you switch to one of the previously mentioned packages.

Let's start by installing the required NuGet packages:

```powershell
Install-Package Microsoft.Extensions.Resilience
Install-Package Microsoft.Extensions.Http.Resilience
```

To use resilience, you must first build a pipeline consisting of resilience [strategies](https://www.pollydocs.org/strategies/).
Each strategy that we configure as part of the pipeline will execute in order of configuration.
Order is important with resilience pipelines.
Keep that in mind.

We start by creating an instance of `ResiliencePipelineBuilder`, which allows us to configure resilience strategies.

```csharp
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<ConflictException>(),
        Delay = TimeSpan.FromSeconds(1),
        MaxRetryAttempts = 2,
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true
    })
    .AddTimeout(new TimeoutStrategyOptions
    {
        Timeout = TimeSpan.FromSeconds(10)
    })
    .Build();

await pipeline.ExecuteAsync(
    async ct => await httpClient.GetAsync("https://modularmonolith.com", ct),
    cancellationToken);
```

Here's what we're adding to the resilience pipeline:

- `AddRetry` - Configures a retry resilience strategy, which we can further configure by passing in a `RetryStrategyOptions` instance.
  We can provide a predicate for the `ShouldHandle` property to define which exceptions the resilience strategy should handle.
  The retry strategy also comes with some sensible [default values](https://www.pollydocs.org/strategies/retry.html#defaults).
- `AddTimeout` - Configures a timeout strategy that will throw a `TimeoutRejectedException` if the delegate does not complete before the timeout.
  We can provide a custom timeout by passing in a `TimeoutStrategyOptions` instance.
  The default timeout is 30 seconds.

Finally, we can `Build` the resilience pipeline and get back a configured `ResiliencePipeline` instance that will apply the respective resilience strategies.
To use the `ResiliencePipeline`, we can call the `ExecuteAsync` method and pass in a delegate.

## Resilience Pipelines and Dependency Injection

Configuring a resilience pipeline every time we want to use it is cumbersome.
.NET 8 introduces a new extension method for the `IServiceCollection` interface that allows us to register resilience pipelines with dependency injection.

Instead of manually configuring resilience every time, you ask for a pre-made pipeline by name.

We start by calling the `AddResiliencePipeline` method, which allows us to configure the resilience pipeline.
Each resilience pipeline needs to have a unique key.
We can use this key to resolve the respective resilience pipeline instance.

In this example, we're passing in a `string` key which allows us to configure the non-generic `ResiliencePipelineBuilder`.

```csharp
services.AddResiliencePipeline("retry", builder =>
{
    builder.AddRetry(new RetryStrategyOptions
    {
        Delay = TimeSpan.FromSeconds(1),
        MaxRetryAttempts = 2,
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true
    });
});
```

However, we can also specify generic arguments when calling `AddResiliencePipeline`.
This allows us to configure a typed resilience pipeline using `ResiliencePipelineBuilder<TResult>`.
Using this approach, we can access the [hedging](https://www.pollydocs.org/strategies/hedging.html) and [fallback](https://www.pollydocs.org/strategies/fallback.html) strategies.

In the following example, we're configuring a fallback strategy by calling `AddFallback`.
This allows us to provide a fallback value that we can return in case of a failure.
The fallback could be a static value or come from another HTTP request or the database.

```csharp
services.AddResiliencePipeline<string, GitHubUser?>("gh-fallback", builder =>
{
    builder.AddFallback(new FallbackStrategyOptions<GitHubUser?>
    {
        FallbackAction = _ =>
            Outcome.FromResultAsValueTask<GitHubUser?>(GitHubUser.Empty)
    });
});
```

To use resilience pipelines configured with dependency injection, we can use the `ResiliencePipelineProvider`.
It exposes a `GetPipeline` method for obtaining the pipeline instance.
We have to provide the key used to register the resilience pipeline.

```csharp {3,6}
app.MapGet("users", async (
    HttpClient httpClient,
    ResiliencePipelineProvider<string> pipelineProvider) =>
{
    ResiliencePipeline<GitHubUser?> pipeline =
        pipelineProvider.GetPipeline<GitHubUser?>("gh-fallback");

    var user = await pipeline.ExecuteAsync(async token =>
        await httpClient.GetAsync("api/users", token),
        cancellationToken);
});
```

## Resilience Strategies and Polly

[Resilience strategies](https://www.pollydocs.org/strategies/) are the core component of Polly.
They're designed to run custom callbacks while introducing an additional layer of resilience.
We can't run these strategies directly.
Instead, we execute them through a resilience pipeline.

Polly categorizes resilience strategies into **reactive** and **proactive**.
Reactive strategies handle specific exceptions or results.
Proactive strategies decide to cancel or reject the execution of callbacks using a rate limiter or a timeout resilience strategy.

Polly has the following built-in resilience strategies:

- **Retry**: The classic "try again" approach.
  Works great for temporary network glitches.
  You can configure how many retries you have and even add some randomness (jitter) to avoid overloading the system if everyone retries at once.
- **Circuit-breaker**: Like an electrical circuit breaker, this prevents hammering a failing system.
  If errors pile up, the circuit breaker "trips" temporarily to give the system time to recover.
- **Fallback**: Provides a safe, default response if your primary call fails.
  It might be a cached result or a simple "service unavailable" message.
- **Hedging**: Makes multiple requests simultaneously, taking the first successful response.
  It is helpful if your system has numerous ways of handling something.
- **Timeout**: Prevents requests from hanging forever by terminating them if the timeout is exceeded.
- **Rate-limiter**: Throttles outgoing requests to prevent overwhelming external services.

## HTTP Request Resilience

Sending HTTP calls to external services is how your application interacts with the outside world.
These could be third-party services like payment gateways and identity providers or other services your team owns and operates.

The `Microsoft.Extensions.Http.Resilience` library comes with ready-to-use resilience pipelines for sending HTTP requests.

We can add resilience to outgoing [**HttpClient requests**](https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet) using the `AddStandardResilienceHandler` method.

```csharp {5}
services.AddHttpClient<GitHubService>(static (httpClient) =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");
})
.AddStandardResilienceHandler();
```

This also means you can eliminate any [**delegating handlers**](https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore) you previously used for resilience.

The standard resilience handler combines five Polly strategies to create a resilience pipeline suitable for most scenarios.
The standard pipeline contains the following strategies:

- **Rate limiter**: Limits the maximum number of concurrent requests sent to the dependency.
- **Total request timeout**: Introduces a total timeout, including any retry attempts.
- **Retry**: Retries a request if it fails because of a timeout or a transient error.
- **Circuit breaker**: Prevents sending further requests if too many failures are detected.
- **Attempt timeout**: Introduces a timeout for an individual request.

You can customize any aspect of the standard resilience pipeline by configuring the `HttpStandardResilienceOptions`.

## Takeaway

Resilience isn't just a buzzword; it's a core principle for building reliable software systems.
We're fortunate to have powerful tools like `Microsoft.Extensions.Resilience` and [**Polly**](https://milanjovanovic.tech/blog/polly-v8-resilience-pipelines) at our disposal.
We can use them to design systems that gracefully handle any transient failures.

Good [**monitoring and observability**](https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet)
are essential to understand how your resilience mechanisms work in production.
Remember, the goal isn't to eliminate failures but to gracefully handle them and keep your application functioning.

Ready to dive deeper into resilient architecture?
My advanced course on [**building modular monoliths**](https://milanjovanovic.tech/modular-monolith-architecture) will equip you with the skills to design and implement robust, scalable systems.
Check out [**Modular Monolith Architecture**](https://milanjovanovic.tech/modular-monolith-architecture).

**Challenge**: Take a look at your existing .NET projects.
Are there any critical areas where a little resilience could go a long way?
Pick one and try applying some of the techniques we've discussed here.

That's all for today.

See you next week.

---

## Frequently asked questions

### What is Polly in .NET?

Polly is a .NET resilience and transient fault-handling library. It lets you define strategies such as retry, circuit breaker, timeout, rate limiting, fallback, and hedging. Polly V8 introduced a new API surface, implemented in collaboration with Microsoft, centered on resilience pipelines.

### What is a resilience pipeline?

A resilience pipeline is a composition of resilience strategies, built with ResiliencePipelineBuilder, that wraps a delegate you run through ExecuteAsync. The strategies execute in the order you configure them, so ordering matters. .NET 8 added packages built on Polly that make pipelines first-class.

### How do you add resilience to HttpClient in .NET?

The Microsoft.Extensions.Http.Resilience package, introduced alongside .NET 8, ships ready-made pipelines for HTTP. Calling AddStandardResilienceHandler on an HttpClient registration applies a standard pipeline, which can replace delegating handlers you previously wrote for resilience.

### What does the standard resilience handler include?

It combines five Polly strategies: a rate limiter capping concurrent requests, a total request timeout that includes retries, a retry strategy for transient failures, a circuit breaker that stops sending after repeated failures, and a per-attempt timeout. You can customize it through HttpStandardResilienceOptions.

### What is the difference between reactive and proactive resilience strategies?

Polly categorizes strategies as reactive or proactive. Reactive strategies, like retry and fallback, respond to specific exceptions or results after a callback runs. Proactive strategies, like timeouts and rate limiters, decide to cancel or reject the execution before or while it runs.

### How do you register a resilience pipeline with dependency injection?

The .NET 8 extensions added AddResiliencePipeline on IServiceCollection, where each pipeline gets a unique key. You later resolve it through ResiliencePipelineProvider and its GetPipeline method. Typed pipelines also unlock the fallback and hedging strategies, which return a result value.
