Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
42af17e
Initial bits
IEvangelist Sep 8, 2023
db2524b
Trying to explore the resilience libs
IEvangelist Sep 8, 2023
a8cd975
Updates to resiliency
IEvangelist Sep 21, 2023
2d43e0a
More updates
IEvangelist Sep 26, 2023
af43e96
Updates to docs
IEvangelist Sep 27, 2023
b45429e
More fixes and updates
IEvangelist Sep 27, 2023
35bcf66
Fix heading
IEvangelist Sep 27, 2023
c4ae1cb
More HTTP bits
IEvangelist Sep 28, 2023
2971d2f
Apply suggestions from code review
IEvangelist Sep 28, 2023
deb606a
More tweaks
IEvangelist Sep 28, 2023
0436637
Added image
IEvangelist Sep 28, 2023
84fad50
Additions
IEvangelist Sep 28, 2023
c5385c6
More updates
IEvangelist Sep 28, 2023
7da24db
Apply suggestions from code review
IEvangelist Sep 29, 2023
0cda35c
Clean up and updates
IEvangelist Sep 29, 2023
a3b83b4
Add context
IEvangelist Sep 29, 2023
6f870bd
Retitle a bit
IEvangelist Sep 29, 2023
b48519a
A quick fix
IEvangelist Sep 29, 2023
6f5d134
Resilience in TOC
IEvangelist Sep 29, 2023
1fb59e2
Updates to headings
IEvangelist Sep 29, 2023
16f50c3
Fix TOC entry
IEvangelist Sep 29, 2023
45f1814
Added a bit more detail
IEvangelist Sep 29, 2023
d254367
Update diagram
IEvangelist Sep 29, 2023
ac69fce
Updates to HTTP resiliency content
IEvangelist Oct 2, 2023
90e16c0
Add more configuration bits
IEvangelist Oct 2, 2023
c5cef6a
From peer feedback
IEvangelist Oct 2, 2023
0201cdc
Clean up
IEvangelist Oct 2, 2023
62081e1
Apply suggestions from code review
IEvangelist Oct 2, 2023
7d3f28a
Update image
IEvangelist Oct 2, 2023
01a0a61
Apply suggestions from code review
IEvangelist Oct 3, 2023
af85439
Updates from peer review
IEvangelist Oct 3, 2023
a92ec18
Major updates to the resilience bits
IEvangelist Oct 19, 2023
690a0bb
A bit closer
IEvangelist Oct 19, 2023
3b3c1da
The id's should work
IEvangelist Oct 19, 2023
f4095d4
There we go
IEvangelist Oct 19, 2023
47088c2
code bits for resilience
IEvangelist Oct 19, 2023
6befcd0
Update source with markers
IEvangelist Oct 19, 2023
6076f8c
Apply suggestions from code review
IEvangelist Oct 19, 2023
c079b2f
Apply suggestions from code review
IEvangelist Oct 20, 2023
8027a21
Address all feedback
IEvangelist Oct 20, 2023
5aff14f
More clean up, final touches
IEvangelist Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/core/resilience/assets/http-get-comments-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
252 changes: 77 additions & 175 deletions docs/core/resilience/http-resilience.md

Large diffs are not rendered by default.

54 changes: 7 additions & 47 deletions docs/core/resilience/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Introduction to resilient app development
description: Learn about resiliency as it related to .NET and how to build a resilience pipeline.
author: IEvangelist
ms.author: dapine
ms.date: 09/29/2023
ms.date: 10/19/2023
---

# Introduction to resilient app development
Expand Down Expand Up @@ -42,32 +42,11 @@ For more information, see [dotnet add package](../tools/dotnet-add-package.md) o

## Build a resilience pipeline

To use resilience, you must first build a pipeline of resilience-based strategies. Each configured strategy executes in order of configuration. In other words, order is very important. The entry point is an extension method on the <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> type, named `AddResiliencePipeline`. This method takes an identifier of the pipeline and delegate that configures the pipeline. The delegate is passed an instance of `ResiliencePipelineBuilder`, which is used to add resilience strategies to the pipeline.
To use resilience, you must first build a pipeline of resilience-based strategies. Each configured strategy executes in order of configuration. In other words, order is important. The entry point is an extension method on the <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> type, named `AddResiliencePipeline`. This method takes an identifier of the pipeline and delegate that configures the pipeline. The delegate is passed an instance of `ResiliencePipelineBuilder`, which is used to add resilience strategies to the pipeline.

Consider the following string-based `key` example:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.CircuitBreaker;
using Polly.Registry;
using Polly.Retry;

var services = new ServiceCollection();

const string key = "Retry-CircuitBreaker-Timeout";

services.AddResiliencePipeline(key, builder =>
{
builder.AddRetry(new RetryStrategyOptions());

builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions());

builder.AddTimeout(TimeSpan.FromSeconds(5));

// Add other strategies here...
});
```
:::code language="csharp" source="snippets/resilience/Program.cs" id="setup":::

The preceding code:

Expand Down Expand Up @@ -99,11 +78,9 @@ Imagine 1,000 globally distributed service instances generating logs and metrics

### Add resilience enrichment

In addition to registering a resilience pipeline, you can also register resilience enrichment. To add enrichment, call the `AddResilienceEnrichment` extensions method on the `IServiceCollection` instance.
In addition to registering a resilience pipeline, you can also register resilience enrichment. To add enrichment, call the `AddResilienceEnricher` extensions method on the `IServiceCollection` instance.

```csharp
services.AddResilienceEnrichment();
```
:::code language="csharp" source="snippets/resilience/Program.cs" id="enricher":::

By calling the `AddResilienceEnrichment` extension method, you're adding dimensions on top of the default ones that are built in to the underlying Polly library. The following enrichment dimensions are added:

Expand All @@ -117,17 +94,7 @@ For more information, see [Polly: Telemetry metrics](https://www.pollydocs.org/a

To use a configured resilience pipeline, you must get the pipeline from a `ResiliencePipelineProvider<TKey>`. When you added the pipeline earlier, the `key` was of type `string`, so you must get the pipeline from the `ResiliencePipelineProvider<string>`.

```csharp
// Build service provider
using ServiceProvider provider = services.BuildServiceProvider();

// Get pipeline provider
ResiliencePipelineProvider<string> pipelineProvider =
provider.GetRequiredService<ResiliencePipelineProvider<string>>();

// Get the pipeline
ResiliencePipeline pipeline = pipelineProvider.GetPipeline(key);
```
:::code language="csharp" source="snippets/resilience/Program.cs" id="pipeline":::

The preceding code:

Expand All @@ -139,14 +106,7 @@ The preceding code:

To use the resilience pipeline, call any of the available `Execute*` methods on the `ResiliencePipeline` instance. For example, consider an example call to `ExecuteAsync` method:

```csharp
await pipeline.ExecuteAsync(static async cancellationToken =>
{
// Code that could potentially fail.

await ValueTask.CompletedTask;
});
```
:::code language="csharp" source="snippets/resilience/Program.cs" id="execute":::

The preceding code executes the delegate within the `ExecuteAsync` method. When there are failures, the configured strategies are executed. For example, if the `RetryStrategy` is configured to retry three times, the delegate is executed three times before the failure is propagated.

Expand Down
4 changes: 3 additions & 1 deletion docs/core/resilience/snippets/http-resilience/Comment.cs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
public record class Comment(
namespace Http.Resilience.Example;

public record class Comment(
int PostId, int Id, string Name, string Email, string Body);
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
using System.Net.Http.Json;

namespace Http.Resilience.Example;

/// <summary>
/// An example client service, that relies on the <see cref="HttpClient"/> instance.
/// </summary>
/// <param name="client">The given <see cref="HttpClient"/> instance.</param>
internal sealed class ExampleClient(HttpClient client)
{
/// <summary>
/// Returns an <see cref="IAsyncEnumerable{T}"/> of <see cref="Comment"/>s.
/// </summary>
public IAsyncEnumerable<Comment?> GetCommentsAsync()
{
return client.GetFromJsonAsAsyncEnumerable<Comment>("/comments");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience;
using Polly;

internal partial class Program
{
private static void WithCustomHandler(IHttpClientBuilder httpClientBuilder)
{
// <custom>
httpClientBuilder.AddResilienceHandler(
"CustomPipeline",
static builder =>
{
// See: https://www.pollydocs.org/strategies/retry.html
builder.AddRetry(new HttpRetryStrategyOptions
{
// Customize and configure the retry logic.
BackoffType = DelayBackoffType.Exponential,
MaxRetryAttempts = 5,
UseJitter = true
});

// See: https://www.pollydocs.org/strategies/circuit-breaker.html
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
// Customize and configure the circuit breaker logic.
SamplingDuration = TimeSpan.FromSeconds(10),
FailureRatio = 0.2,
MinimumThroughput = 3,
ShouldHandle = static args =>
{
return ValueTask.FromResult(args is
{
Outcome.Result.StatusCode:
HttpStatusCode.RequestTimeout or
HttpStatusCode.TooManyRequests
});
}
});

// See: https://www.pollydocs.org/strategies/timeout.html
builder.AddTimeout(TimeSpan.FromSeconds(5));
});
// </custom>
}

private static void WithAdvancedCustomHandler(IHttpClientBuilder httpClientBuilder)
{
// <advanced>
httpClientBuilder.AddResilienceHandler(
"AdvancedPipeline",
static (ResiliencePipelineBuilder<HttpResponseMessage> builder,
ResilienceHandlerContext context) =>
{
// Enable the reloads whenever the named options change
context.EnableReloads<HttpRetryStrategyOptions>("RetryOptions");

// Retrieve the named options
var retryOptions =
context.GetOptions<HttpRetryStrategyOptions>("RetryOptions");

// Add retries using the resolved options
builder.AddRetry(retryOptions);
});
// </advanced>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience;

internal partial class Program
{
private static void WithStandardHedgingHandler(IHttpClientBuilder httpClientBuilder)
{
// <standard>
httpClientBuilder.AddStandardHedgingHandler();
// </standard>
}

private static void WithConfiguredStandardHedgingHandler(IHttpClientBuilder httpClientBuilder)
{
// <ordered>
httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
// Hedging allows sending multiple concurrent requests
builder.ConfigureOrderedGroups(static options =>
{
options.Groups.Add(new UriEndpointGroup()
{
Endpoints =
{
// Imagine a scenario where 3% of the requests are
// sent to the experimental endpoint.
new() { Uri = new("https://example.net/api/experimental"), Weight = 3 },
new() { Uri = new("https://example.net/api/stable"), Weight = 97 }
}
});
});
});
// </ordered>

// <weighted>
httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
// Hedging allows sending multiple concurrent requests
builder.ConfigureWeightedGroups(static options =>
{
options.SelectionMode = WeightedGroupSelectionMode.EveryAttempt;

options.Groups.Add(new WeightedUriEndpointGroup()
{
Endpoints =
{
// Imagine a/b testing
new() { Uri = new("https://example.net/api/a"), Weight = 33 },
new() { Uri = new("https://example.net/api/b"), Weight = 33 },
new() { Uri = new("https://example.net/api/c"), Weight = 33 }
}
});
});
});
// </weighted>
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
using Polly;

internal partial class Program
{
private static void WithStandardHandler(IHttpClientBuilder httpClientBuilder)
{
// <standard>
httpClientBuilder.AddStandardResilienceHandler();
// </standard>
}

private static void WithConfiguredStandardHandler(IHttpClientBuilder httpClientBuilder)
{
// <configure>
httpClientBuilder.AddStandardResilienceHandler(static options =>
{
options.Retry.BackoffType = DelayBackoffType.Linear;
});
// </configure>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Http.Resilience;

internal partial class Program
{
private static void ConfigureRetryOptions(HostApplicationBuilder builder)
{
// <options>
var section =
builder.Configuration.GetSection("RetryOptions");

builder.Services.Configure<HttpRetryStrategyOptions>(section);
// </options>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;

namespace Http.Resilience.Example;

internal partial class Program
{
private static void CreateServiceCollection()
{
// <services>
var services = new ServiceCollection();

var httpClientBuilder = services.AddHttpClient<ExampleClient>(
configureClient: static client =>
{
client.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
// </services>
}
}
Loading