From 72da696b4826c6ad67d79af1aa33a8af82d373d8 Mon Sep 17 00:00:00 2001 From: peter-csala Date: Fri, 3 Nov 2023 10:01:30 +0100 Subject: [PATCH 1/4] Revise migration guide part 3 --- docs/migration-v8.md | 162 ++++++++++++------ .../pipelines/resilience-pipeline-registry.md | 2 +- src/Snippets/Docs/Migration.Context.cs | 35 +++- src/Snippets/Docs/Migration.Execute.cs | 9 +- src/Snippets/Docs/Migration.Registry.cs | 26 +-- 5 files changed, 158 insertions(+), 76 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 33de7e0bce3..f8edb723c0f 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -409,7 +409,7 @@ new ResiliencePipelineBuilder().AddRetry(new RetryStrategyO > Things to remember: > > - Use `AddRetry` to add a retry strategy to your resiliency pipeline -> - Use the `RetryStrategyOptions` to customize your retry behavior to meet your requirements +> - Use the `RetryStrategyOptions{}` to customize your retry behavior to meet your requirements > > For further information please check out the [Retry resilience strategy documentation](strategies/retry.md). @@ -689,7 +689,7 @@ cbPolicy.Reset(); // Transitions into the Closed state ### Circuit breaker in v8 -> [!TIP] +> [!NOTE] > > Polly V8 does not support the standard (*"classic"*) circuit breaker with consecutive failure counting. > @@ -769,23 +769,24 @@ ____ > > For further information please check out the [Circuit Breaker resilience strategy documentation](strategies/circuit-breaker.md). -## Migrating other policies +## Migrating `Polly.Context` -Migrating is a process similar to the ones described in the previous sections. Keep in mind that: +The successor of the `Polly.Context` is the `ResilienceContext`. The major differences: -- Strategy configurations (or policies in v7) are now in options. Property names should match the v7 APIs and scenarios. -- Use `ResiliencePipelineBuilder` or `ResiliencePipelineBuilder` and their respective extensions to add specific strategies. -- For more details on each strategy, refer to the [resilience strategies](strategies/index.md) documentation. +- `ResilienceContext` is pooled for enhanced performance and cannot be directly created. Instead, use the `ResilienceContextPool` class to get an instance. +- `Context` allowed directly custom data attachment, whereas `ResilienceContext` employs the `ResilienceContext.Properties` for the same purpose. +- In order to set or get a custom data you need to utilize the generic `ResiliencePropertyKey` structure. -## Migrating `Polly.Context` +### Predefined keys -`Polly.Context` has been succeeded by `ResilienceContext`. Here are the main changes: +| In V7 | In V8 | +| :-- | :-- | +| `OperationKey` | It can be used in the same way | +| `PolicyKey` | It's been relocated to `ResiliencePipelineBuilder` and used for [telemetry](advanced/telemetry.md#metrics) | +| `PolicyWrapKey` | It's been relocated to `ResiliencePipelineBuilder` and used for [telemetry](advanced/telemetry.md#metrics) | +| `CorrelationId` | It's been removed. For similar functionality, you can either use `System.Diagnostics.Activity.Current.Id` or attach your custom Id using `ResilienceContext.Properties`. | -- `ResilienceContext` is pooled for enhanced performance and cannot be directly created. Instead, use the `ResilienceContextPool` class to get an instance. -- Directly attaching custom data is supported by `Context`, whereas `ResilienceContext` employs the `ResilienceContext.Properties` property. -- Both `PolicyKey` and `PolicyWrapKey` are no longer a part of `ResilienceContext`. They've been relocated to `ResiliencePipelineBuilder` and are now used for [telemetry](advanced/telemetry.md#metrics). -- The `CorrelationId` property has been removed. For similar functionality, you can either use `System.Diagnostics.Activity.Current.Id` or attach your custom Id using `ResilienceContext.Properties`. -- Additionally, `ResilienceContext` introduces the `CancellationToken` property. +- Additionally, `ResilienceContext` introduces a new property for `CancellationToken`. ### `Context` in v7 @@ -798,12 +799,19 @@ Context context = new Context(); context = new Context("my-operation-key"); // Attach custom properties -context["prop-1"] = "value-1"; -context["prop-2"] = 100; +context[Key1] = "value-1"; +context[Key2] = 100; // Retrieve custom properties -string value1 = (string)context["prop-1"]; -int value2 = (int)context["prop-2"]; +string value1 = (string)context[Key1]; +int value2 = (int)context[Key2]; + +// Bulk attach +context = new Context("my-operation-key", new Dictionary +{ + { Key1 , "value-1" }, + { Key2 , 100 } +}); ``` @@ -818,19 +826,36 @@ ResilienceContext context = ResilienceContextPool.Shared.Get(); context = ResilienceContextPool.Shared.Get("my-operation-key"); // Attach custom properties -context.Properties.Set(new ResiliencePropertyKey("prop-1"), "value-1"); -context.Properties.Set(new ResiliencePropertyKey("prop-2"), 100); +ResiliencePropertyKey propertyKey1 = new(Key1); +context.Properties.Set(propertyKey1, "value-1"); + +ResiliencePropertyKey propertyKey2 = new(Key2); +context.Properties.Set(propertyKey2, 100); + +// Bulk attach +context.Properties.SetProperties(new Dictionary +{ + { Key1 , "value-1" }, + { Key2 , 100 } +}, out var oldProperties); // Retrieve custom properties -string value1 = context.Properties.GetValue(new ResiliencePropertyKey("prop-1"), "default"); -int value2 = context.Properties.GetValue(new ResiliencePropertyKey("prop-2"), 0); +string value1 = context.Properties.GetValue(propertyKey1, "default"); +int value2 = context.Properties.GetValue(propertyKey2, 0); // Return the context to the pool ResilienceContextPool.Shared.Return(context); ``` -For more details, refer to the [Resilience Context](advanced/resilience-context.md) documentation. +> [!TIP] +> +> Things to remember: +> +> - Use `ResilienceContextPool.Shared` to get a context and return it back to the pool +> - Use the `ResiliencePropertyKey` to define type-safe keys for your custom data +> +> For further information please check out the [Resilience Context documentation](advanced/resilience-context.md). ## Migrating safe execution @@ -862,27 +887,34 @@ if (policyResult.Outcome == OutcomeType.Successful) else { Exception exception = policyResult.FinalException; - FaultType failtType = policyResult.FaultType!.Value; + FaultType faultType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; // Process failure } // Access context +const string Key = "context_key"; IAsyncPolicy asyncPolicyWithContext = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), onTimeoutAsync: (ctx, ts, task) => { - ctx["context_key"] = "context_value"; + ctx[Key] = "context_value"; return Task.CompletedTask; }); asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx, token) => MethodAsync(token), new Context(), CancellationToken.None); -string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; +string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault(Key) as string; ``` ### `ExecuteOutcomeAsync` in V8 +> [!NOTE] +> +> Polly V8 does not provide an API to synchronously execute and capture the outcome of a pipeline. +> +> On the other hand it introduced a type-safe `state`: TBD. + ```cs ResiliencePipeline pipeline = new ResiliencePipelineBuilder() @@ -890,7 +922,7 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder() .Build(); // Synchronous execution -// Polly v8 does not provide an API to synchronously execute and capture the outcome of a pipeline +// Polly v8 does not support // Asynchronous execution var context = ResilienceContextPool.Shared.Get(); @@ -938,34 +970,43 @@ ResilienceContextPool.Shared.Return(context); ``` +> [!TIP] +> +> Things to remember: +> +> - Use `ExecuteOutcomeAsync` to execute your callback in a safe way +> - TBD: when to use context and when to use state + ## Migrating no-op policies -- For `Policy.NoOp` or `Policy.NoOpAsync`, switch to `ResiliencePipeline.Empty`. -- For `Policy.NoOp` or `Policy.NoOpAsync`, switch to `ResiliencePipeline.Empty`. +| In V7 | In V8 | +| :-- | :-- | +| `Policy.NoOp` | `ResiliencePipeline.Empty` | +| `Policy.NoOpAsync` | `ResiliencePipeline.Empty` | +| `Policy.NoOp` | `ResiliencePipeline.Empty` | +| `Policy.NoOpAsync` | `ResiliencePipeline.Empty` | ## Migrating policy registries In v7, the following registry APIs are exposed: -- `IPolicyRegistry` -- `IReadOnlyPolicyRegistry` -- `IConcurrentPolicyRegistry` -- `PolicyRegistry` +- `IConcurrentPolicyRegistry` +- `IPolicyRegistry` +- `IReadOnlyPolicyRegistry` +- `PolicyRegistry` In v8, these have been replaced by: - `ResiliencePipelineProvider`: Allows adding and accessing resilience pipelines. - `ResiliencePipelineRegistry`: Read-only access to resilience pipelines. -The main updates in the new registry include: +The main updates: -- It's append-only, which means removal of items is not supported to avoid race conditions. +- It's **append-only**, which means removal of items is not supported to avoid race conditions. - It's thread-safe and supports features like dynamic reloading and resource disposal. -- It allows dynamic creation and caching of resilience pipelines (previously known as policies in v7) using pre-registered delegates. +- It allows dynamic creation and caching of resilience pipelines using pre-registered delegates. - Type safety is enhanced, eliminating the need for casting between policy types. -For more details, refer to the [pipeline registry](pipelines/resilience-pipeline-registry.md) documentation. - ### Registry in v7 @@ -973,18 +1014,18 @@ For more details, refer to the [pipeline registry](pipelines/resilience-pipeline // Create a registry var registry = new PolicyRegistry(); +// Add a policy +registry.Add(PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10))); + // Try get a policy -registry.TryGet("my-key", out IAsyncPolicy? policy); +registry.TryGet(PolicyKey, out IAsyncPolicy? policy); // Try get a generic policy -registry.TryGet>("my-key", out IAsyncPolicy? genericPolicy); - -// Add a policy -registry.Add("my-key", Policy.Timeout(TimeSpan.FromSeconds(10))); +registry.TryGet>(PolicyKey, out IAsyncPolicy? genericPolicy); // Update a policy registry.AddOrUpdate( - "my-key", + PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10)), (key, previous) => Policy.Timeout(TimeSpan.FromSeconds(10))); ``` @@ -992,28 +1033,47 @@ registry.AddOrUpdate( ### Registry in v8 +> [!NOTE] +> +> Polly V8 does not provide an explicit API to directly update a strategy in the registry. +> +> On the other hand it does provide a mechanism to reload pipelines. + ```cs // Create a registry var registry = new ResiliencePipelineRegistry(); +// Add a pipeline using a builder, when the pipeline is retrieved it will be dynamically built and cached +registry.TryAddBuilder(PipelineKey, (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); + // Try get a pipeline -registry.TryGetPipeline("my-key", out ResiliencePipeline? pipeline); +registry.TryGetPipeline(PipelineKey, out ResiliencePipeline? pipeline); // Try get a generic pipeline -registry.TryGetPipeline("my-key", out ResiliencePipeline? genericPipeline); - -// Add a pipeline using a builder, when "my-key" pipeline is retrieved it will be dynamically built and cached -registry.TryAddBuilder("my-key", (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); +registry.TryGetPipeline(PipelineKey, out ResiliencePipeline? genericPipeline); // Get or add pipeline -registry.GetOrAddPipeline("my-key", builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); +registry.GetOrAddPipeline(PipelineKey, builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); ``` +> [!TIP] +> +> Things to remember: +> +> - Use `ResiliencePipelineRegistry` to add or get a pipelines to the registry +> - Prefer the safe methods (for example: `TryGetPipeline{}`) over their counterpart (for example: `GetPipeline{}`) +> +> For further information please check out the [Resilience pipeline registry documentation](pipelines/resilience-pipeline-registry.md). + ## Interoperability between policies and resilience pipelines -In certain scenarios, you might not want to migrate your code to the v8 API. Instead, you may prefer to use strategies from v8 and apply them to v7 APIs. Polly provides a set of extension methods to support easy conversion from v8 to v7 APIs, as shown in the example below: +In certain scenarios, you might not able to migrate all your code to the v8 API. + +In the name of interoperability you can define V8 strategies use them with your v7 policies. + +V8 provides a set of extension methods to support easy conversion from v8 to v7 APIs, as shown in the example below: > [!NOTE] > In v8, you have to add the [`Polly.RateLimiting`](https://www.nuget.org/packages/Polly.RateLimiting) package to your application otherwise you won't see the `AddRateLimiter` extension. diff --git a/docs/pipelines/resilience-pipeline-registry.md b/docs/pipelines/resilience-pipeline-registry.md index b17380676dd..bde422d3cc4 100644 --- a/docs/pipelines/resilience-pipeline-registry.md +++ b/docs/pipelines/resilience-pipeline-registry.md @@ -95,7 +95,7 @@ The constructor for `ResiliencePipelineRegistry` accepts a parameter of ty | `InstanceNameFormatter` | `null` | Delegate formatting `TKey` to instance name. | | `BuilderNameFormatter` | Function returning the `key.ToString()` value. | Delegate formatting `TKey` to builder name. | -> [>NOTE] +> [!NOTE] > The `BuilderName` and `InstanceName` are used in [telemetry](../advanced/telemetry.md#metrics). Usage example: diff --git a/src/Snippets/Docs/Migration.Context.cs b/src/Snippets/Docs/Migration.Context.cs index f07beb7974d..793d64e5ddb 100644 --- a/src/Snippets/Docs/Migration.Context.cs +++ b/src/Snippets/Docs/Migration.Context.cs @@ -2,6 +2,8 @@ internal static partial class Migration { + private const string Key1 = nameof(Key1); + private const string Key2 = nameof(Key2); public static void Context_V7() { #region migration-context-v7 @@ -13,12 +15,19 @@ public static void Context_V7() context = new Context("my-operation-key"); // Attach custom properties - context["prop-1"] = "value-1"; - context["prop-2"] = 100; + context[Key1] = "value-1"; + context[Key2] = 100; // Retrieve custom properties - string value1 = (string)context["prop-1"]; - int value2 = (int)context["prop-2"]; + string value1 = (string)context[Key1]; + int value2 = (int)context[Key2]; + + // Bulk attach + context = new Context("my-operation-key", new Dictionary + { + { Key1 , "value-1" }, + { Key2 , 100 } + }); #endregion } @@ -34,12 +43,22 @@ public static void Context_V8() context = ResilienceContextPool.Shared.Get("my-operation-key"); // Attach custom properties - context.Properties.Set(new ResiliencePropertyKey("prop-1"), "value-1"); - context.Properties.Set(new ResiliencePropertyKey("prop-2"), 100); + ResiliencePropertyKey propertyKey1 = new(Key1); + context.Properties.Set(propertyKey1, "value-1"); + + ResiliencePropertyKey propertyKey2 = new(Key2); + context.Properties.Set(propertyKey2, 100); + + // Bulk attach + context.Properties.SetProperties(new Dictionary + { + { Key1 , "value-1" }, + { Key2 , 100 } + }, out var oldProperties); // Retrieve custom properties - string value1 = context.Properties.GetValue(new ResiliencePropertyKey("prop-1"), "default"); - int value2 = context.Properties.GetValue(new ResiliencePropertyKey("prop-2"), 0); + string value1 = context.Properties.GetValue(propertyKey1, "default"); + int value2 = context.Properties.GetValue(propertyKey2, 0); // Return the context to the pool ResilienceContextPool.Shared.Return(context); diff --git a/src/Snippets/Docs/Migration.Execute.cs b/src/Snippets/Docs/Migration.Execute.cs index 4ed92c9d122..11760d4aff4 100644 --- a/src/Snippets/Docs/Migration.Execute.cs +++ b/src/Snippets/Docs/Migration.Execute.cs @@ -27,22 +27,23 @@ public static async Task SafeExecute_V7() else { Exception exception = policyResult.FinalException; - FaultType failtType = policyResult.FaultType!.Value; + FaultType faultType = policyResult.FaultType!.Value; ExceptionType exceptionType = policyResult.ExceptionType!.Value; // Process failure } // Access context + const string Key = "context_key"; IAsyncPolicy asyncPolicyWithContext = Policy.TimeoutAsync(TimeSpan.FromSeconds(10), onTimeoutAsync: (ctx, ts, task) => { - ctx["context_key"] = "context_value"; + ctx[Key] = "context_value"; return Task.CompletedTask; }); asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx, token) => MethodAsync(token), new Context(), CancellationToken.None); - string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault("context_key") as string; + string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault(Key) as string; #endregion } @@ -54,7 +55,7 @@ public static async Task SafeExecute_V8() .Build(); // Synchronous execution - // Polly v8 does not provide an API to synchronously execute and capture the outcome of a pipeline + // Polly v8 does not support // Asynchronous execution var context = ResilienceContextPool.Shared.Get(); diff --git a/src/Snippets/Docs/Migration.Registry.cs b/src/Snippets/Docs/Migration.Registry.cs index efbb9723072..8adc0896aa5 100644 --- a/src/Snippets/Docs/Migration.Registry.cs +++ b/src/Snippets/Docs/Migration.Registry.cs @@ -4,6 +4,7 @@ namespace Snippets.Docs; internal static partial class Migration { + private const string PolicyKey = nameof(PolicyKey); public static void Registry_V7() { #region migration-registry-v7 @@ -11,24 +12,25 @@ public static void Registry_V7() // Create a registry var registry = new PolicyRegistry(); + // Add a policy + registry.Add(PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10))); + // Try get a policy - registry.TryGet("my-key", out IAsyncPolicy? policy); + registry.TryGet(PolicyKey, out IAsyncPolicy? policy); // Try get a generic policy - registry.TryGet>("my-key", out IAsyncPolicy? genericPolicy); - - // Add a policy - registry.Add("my-key", Policy.Timeout(TimeSpan.FromSeconds(10))); + registry.TryGet>(PolicyKey, out IAsyncPolicy? genericPolicy); // Update a policy registry.AddOrUpdate( - "my-key", + PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10)), (key, previous) => Policy.Timeout(TimeSpan.FromSeconds(10))); #endregion } + private const string PipelineKey = nameof(PipelineKey); public static void Registry_V8() { #region migration-registry-v8 @@ -36,17 +38,17 @@ public static void Registry_V8() // Create a registry var registry = new ResiliencePipelineRegistry(); + // Add a pipeline using a builder, when the pipeline is retrieved it will be dynamically built and cached + registry.TryAddBuilder(PipelineKey, (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); + // Try get a pipeline - registry.TryGetPipeline("my-key", out ResiliencePipeline? pipeline); + registry.TryGetPipeline(PipelineKey, out ResiliencePipeline? pipeline); // Try get a generic pipeline - registry.TryGetPipeline("my-key", out ResiliencePipeline? genericPipeline); - - // Add a pipeline using a builder, when "my-key" pipeline is retrieved it will be dynamically built and cached - registry.TryAddBuilder("my-key", (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10))); + registry.TryGetPipeline(PipelineKey, out ResiliencePipeline? genericPipeline); // Get or add pipeline - registry.GetOrAddPipeline("my-key", builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); + registry.GetOrAddPipeline(PipelineKey, builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); #endregion } From 9d2ad15451da8917a6c9739c5bbea22186247402 Mon Sep 17 00:00:00 2001 From: peter-csala Date: Fri, 3 Nov 2023 10:26:46 +0100 Subject: [PATCH 2/4] replace tbd --- docs/migration-v8.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index f8edb723c0f..086d74bab58 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -913,7 +913,7 @@ string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault(Key) as string; > > Polly V8 does not provide an API to synchronously execute and capture the outcome of a pipeline. > -> On the other hand it introduced a type-safe `state`: TBD. +> On the other hand it introduced a type-safe `state`: to-be-defined. ```cs @@ -975,7 +975,7 @@ ResilienceContextPool.Shared.Return(context); > Things to remember: > > - Use `ExecuteOutcomeAsync` to execute your callback in a safe way -> - TBD: when to use context and when to use state +> - To-Be-Defined: when to use context and when to use state ## Migrating no-op policies From 5daabccd0c8051b63d49a2de8a376e901b62002a Mon Sep 17 00:00:00 2001 From: peter-csala Date: Fri, 3 Nov 2023 10:58:12 +0100 Subject: [PATCH 3/4] Apply suggestions --- docs/migration-v8.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 086d74bab58..34e91cf79a2 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -912,8 +912,6 @@ string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault(Key) as string; > [!NOTE] > > Polly V8 does not provide an API to synchronously execute and capture the outcome of a pipeline. -> -> On the other hand it introduced a type-safe `state`: to-be-defined. ```cs @@ -975,7 +973,6 @@ ResilienceContextPool.Shared.Return(context); > Things to remember: > > - Use `ExecuteOutcomeAsync` to execute your callback in a safe way -> - To-Be-Defined: when to use context and when to use state ## Migrating no-op policies @@ -1037,7 +1034,7 @@ registry.AddOrUpdate( > > Polly V8 does not provide an explicit API to directly update a strategy in the registry. > -> On the other hand it does provide a mechanism to reload pipelines. +> On the other hand it does provide a mechanism to [reload pipelines](pipelines/resilience-pipeline-registry.md#dynamic-reloads). ```cs From 16716cf8d68dd97043b4f0b513326af959105afe Mon Sep 17 00:00:00 2001 From: peter-csala <57183693+peter-csala@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:49:19 +0100 Subject: [PATCH 4/4] Update docs/migration-v8.md Co-authored-by: Martin Costello --- docs/migration-v8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration-v8.md b/docs/migration-v8.md index 34e91cf79a2..21522a1ca49 100644 --- a/docs/migration-v8.md +++ b/docs/migration-v8.md @@ -1060,7 +1060,7 @@ registry.GetOrAddPipeline(PipelineKey, builder => builder.AddTimeout(TimeSpan.Fr > Things to remember: > > - Use `ResiliencePipelineRegistry` to add or get a pipelines to the registry -> - Prefer the safe methods (for example: `TryGetPipeline{}`) over their counterpart (for example: `GetPipeline{}`) +> - Prefer the safer methods (for example: `TryGetPipeline{}`) over their counterpart (for example: `GetPipeline{}`) > > For further information please check out the [Resilience pipeline registry documentation](pipelines/resilience-pipeline-registry.md).