From cd8224196524392c9618b06eb8a97f7bbd31d79a Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 25 Feb 2024 12:47:40 -0500 Subject: [PATCH 1/9] * document outcome strategy anti-patterns * deprecates AddException API in the OutcomeGenerator --- docs/chaos/outcome.md | 110 ++++++++++++++++-- .../Simmy/Outcomes/OutcomeGenerator.cs | 9 ++ src/Snippets/Docs/Chaos.Outcome.cs | 93 ++++++++++++++- 3 files changed, 199 insertions(+), 13 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index 2c4d6fac8d..68a83c4126 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -9,19 +9,18 @@ --- -The outcome chaos strategy is designed to inject or substitute fake results into system operations. This allows testing how an application behaves when it receives different types of responses, like successful results, errors, or exceptions. +The outcome chaos strategy is designed to inject or substitute fake results into system operations. This allows testing how an application behaves when it receives different types of responses, like successful or error results. ## Usage ```cs -// To use OutcomeGenerator to register the results and exceptions to be injected (equal probability) +// To use OutcomeGenerator to register the results to be injected (equal probability) var optionsWithResultGenerator = new ChaosOutcomeStrategyOptions { OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests)) .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) - .AddException(() => new HttpRequestException("Chaos request exception.")), InjectionRate = 0.1 }; @@ -132,24 +131,23 @@ To generate a faulted outcome (result or exception), you need to specify a `Outc ### Use `OutcomeGenerator` class to generate outcomes -The `OutcomeGenerator` is a convenience API that allows you to specify what outcomes (results or exceptions) are to be injected. Additionally, it also allows assigning weight to each registered outcome. +The `OutcomeGenerator` is a convenience API that allows you to specify what outcomes are to be injected. Additionally, it also allows assigning weight to each registered outcome. ```cs new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - // Use OutcomeGenerator to register the results and exceptions to be injected + // Use OutcomeGenerator to register the results to be injected OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result - .AddException(), // You can also register exceptions }); ``` -### Use delegates to generate faults +### Use delegates to generate outcomes Delegates give you the most flexibility at the expense of slightly more complicated syntax. Delegates also support asynchronous outcome generation, if you ever need that possibility. @@ -159,6 +157,35 @@ new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { // The same behavior can be achieved with delegates + OutcomeGenerator = args => + { + Outcome? outcome = Random.Shared.Next(350) switch + { + < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), + < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), + < 350 => Outcome.FromResult(CreateResultFromContext(args.Context)), + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + }; + + return ValueTask.FromResult(outcome); + } + }); +``` + + +## Anti-patterns + +### Injecting faults (exceptions) + +❌ DON'T + +Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so, one of them is the telemetry events, which might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way, and this could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". + + +```cs +var pipeline = new ResiliencePipelineBuilder() + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { OutcomeGenerator = args => { Outcome? outcome = Random.Shared.Next(350) switch @@ -166,12 +193,77 @@ new ResiliencePipelineBuilder() < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - < 350 => Outcome.FromException(new TimeoutException()), + < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + }; + + return ValueTask.FromResult(outcome); + }, + OnOutcomeInjected = static args => + { + // You might have to put some logic here to determine what kind of output was injected. 😕 + if (args.Outcome.Exception != null) + { + Console.WriteLine($"OnBehaviorInjected, Exception: {args.Outcome.Exception.Message}, Operation: {args.Context.OperationKey}."); + } + else + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + } + + return default; + } + }) + .Build(); +``` + + +✅ DO + +The previous approach is tempting since it looks more succinct, but use the fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome. + + +```cs +var randomThreshold = Random.Shared.Next(350); +var pipeline = new ResiliencePipelineBuilder() + .AddChaosFault(new ChaosFaultStrategyOptions + { + FaultGenerator = args => + { + Exception? exception = randomThreshold switch + { + >= 250 and < 350 => new HttpRequestException("Chaos request exception."), _ => null }; + return new ValueTask(exception); + }, + OnFaultInjected = static args => + { + Console.WriteLine($"OnFaultInjected, Exception: {args.Fault.Message}, Operation: {args.Context.OperationKey}."); + return default; + } + }) + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { + OutcomeGenerator = args => + { + Outcome? outcome = randomThreshold switch + { + < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), + < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), + < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + }; + return ValueTask.FromResult(outcome); + }, + OnOutcomeInjected = static args => + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + return default; } - }); + }) + .Build(); ``` diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs index 95d961f3d4..57f6371003 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs @@ -35,6 +35,9 @@ internal OutcomeGenerator(Func weightGenerator) /// The delegate that generates the exception. /// The weight assigned to this generator. Defaults to 100. /// The current instance of . +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] +#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(Func generator, int weight = DefaultWeight) { Guard.NotNull(generator); @@ -50,6 +53,9 @@ public OutcomeGenerator AddException(Func generator, int wei /// The delegate that generates the exception, accepting a . /// The weight assigned to this generator. Defaults to 100. /// The current instance of . +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] +#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(Func generator, int weight = DefaultWeight) { Guard.NotNull(generator); @@ -65,6 +71,9 @@ public OutcomeGenerator AddException(Func /// The type of the exception to generate. /// The weight assigned to this generator. Defaults to 100. /// The current instance of . +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] +#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(int weight = DefaultWeight) where TException : Exception, new() { diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index a8c004a0a8..faad248149 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -1,7 +1,8 @@ -using System.Net; +using System.Net; using System.Net.Http; using Polly.Retry; using Polly.Simmy; +using Polly.Simmy.Fault; using Polly.Simmy.Outcomes; namespace Snippets.Docs; @@ -19,7 +20,6 @@ public static void OutcomeUsage() OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests)) .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) - .AddException(() => new HttpRequestException("Chaos request exception.")), InjectionRate = 0.1 }; @@ -83,7 +83,6 @@ public static void OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result - .AddException(), // You can also register exceptions }); #endregion @@ -105,7 +104,7 @@ public static void OutcomeGeneratorDelegates() < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), < 350 => Outcome.FromException(new TimeoutException()), - _ => null + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; return ValueTask.FromResult(outcome); @@ -115,5 +114,91 @@ public static void OutcomeGeneratorDelegates() #endregion } + public static void AntiPattern_InjectFault() + { + #region chaos-outcome-anti-pattern-inject-fault + var pipeline = new ResiliencePipelineBuilder() + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { + OutcomeGenerator = args => + { + Outcome? outcome = Random.Shared.Next(350) switch + { + < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), + < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), + < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), + < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + }; + + return ValueTask.FromResult(outcome); + }, + OnOutcomeInjected = static args => + { + // You might have to put some logic here to determine what kind of output was injected. 😕 + if (args.Outcome.Exception != null) + { + Console.WriteLine($"OnBehaviorInjected, Exception: {args.Outcome.Exception.Message}, Operation: {args.Context.OperationKey}."); + } + else + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + } + + return default; + } + }) + .Build(); + + #endregion + } + + public static void Pattern_InjectFault() + { + #region chaos-outcome-pattern-inject-fault + var randomThreshold = Random.Shared.Next(350); + var pipeline = new ResiliencePipelineBuilder() + .AddChaosFault(new ChaosFaultStrategyOptions + { + FaultGenerator = args => + { + Exception? exception = randomThreshold switch + { + >= 250 and < 350 => new HttpRequestException("Chaos request exception."), + _ => null + }; + + return new ValueTask(exception); + }, + OnFaultInjected = static args => + { + Console.WriteLine($"OnFaultInjected, Exception: {args.Fault.Message}, Operation: {args.Context.OperationKey}."); + return default; + } + }) + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { + OutcomeGenerator = args => + { + Outcome? outcome = randomThreshold switch + { + < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), + < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), + < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), + _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + }; + + return ValueTask.FromResult(outcome); + }, + OnOutcomeInjected = static args => + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + return default; + } + }) + .Build(); + #endregion + } + private static HttpResponseMessage CreateResultFromContext(ResilienceContext context) => new(HttpStatusCode.TooManyRequests); } From c297b96210642a548e41650a861df7f5f3c644d9 Mon Sep 17 00:00:00 2001 From: Geovanny Alzate Sandoval Date: Sun, 25 Feb 2024 13:26:19 -0500 Subject: [PATCH 2/9] controlled chaos --- docs/chaos/outcome.md | 7 ++++++- src/Snippets/Docs/Chaos.Outcome.cs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index 68a83c4126..9d09b2e050 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -181,11 +181,14 @@ new ResiliencePipelineBuilder() Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so, one of them is the telemetry events, which might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way, and this could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". +Also, you end up losing control of how/when to inject outcomes vs faults since this way does not allow you to control separately when to inject a fault vs an outcome. + ```cs var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { + InjectionRate = 0.5, // same injection rate for both fault and outcome OutcomeGenerator = args => { Outcome? outcome = Random.Shared.Next(350) switch @@ -220,7 +223,7 @@ var pipeline = new ResiliencePipelineBuilder() ✅ DO -The previous approach is tempting since it looks more succinct, but use the fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome. +The previous approach is tempting since it looks more succinct, but use the fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome, also by separating them you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. ```cs @@ -228,6 +231,7 @@ var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { + InjectionRate = 0.1, // different injection rate for faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -246,6 +250,7 @@ var pipeline = new ResiliencePipelineBuilder() }) .AddChaosOutcome(new ChaosOutcomeStrategyOptions { + InjectionRate = 0.5, // different injection rate for outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index faad248149..fc03d6163c 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -120,6 +120,7 @@ public static void AntiPattern_InjectFault() var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { + InjectionRate = 0.5, // same injection rate for both fault and outcome OutcomeGenerator = args => { Outcome? outcome = Random.Shared.Next(350) switch @@ -160,6 +161,7 @@ public static void Pattern_InjectFault() var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { + InjectionRate = 0.1, // different injection rate for faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -178,6 +180,7 @@ public static void Pattern_InjectFault() }) .AddChaosOutcome(new ChaosOutcomeStrategyOptions { + InjectionRate = 0.5, // different injection rate for outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch From 262b1367d3cc8fdcbcce90907771b699cb2f427f Mon Sep 17 00:00:00 2001 From: Geo Date: Sat, 2 Mar 2024 16:20:51 -0500 Subject: [PATCH 3/9] wrapping up --- docs/chaos/outcome.md | 49 ++++++++++++++++--- .../Simmy/Outcomes/OutcomeGenerator.cs | 9 ---- src/Snippets/Docs/Chaos.Outcome.cs | 44 +++++++++++++++-- 3 files changed, 83 insertions(+), 19 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index 9d09b2e050..cd58b70a48 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -9,18 +9,19 @@ --- -The outcome chaos strategy is designed to inject or substitute fake results into system operations. This allows testing how an application behaves when it receives different types of responses, like successful or error results. +The outcome chaos strategy is designed to inject or substitute fake results into system operations. This allows testing how an application behaves when it receives different types of responses, like successful results, errors, or exceptions. ## Usage ```cs -// To use OutcomeGenerator to register the results to be injected (equal probability) +// To use OutcomeGenerator to register the results and exceptions to be injected (equal probability) var optionsWithResultGenerator = new ChaosOutcomeStrategyOptions { OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests)) .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) + .AddException(() => new HttpRequestException("Chaos request exception.")), InjectionRate = 0.1 }; @@ -138,11 +139,12 @@ The `OutcomeGenerator` is a convenience API that allows you to specify what o new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - // Use OutcomeGenerator to register the results to be injected + // Use OutcomeGenerator to register the results and exceptions to be injected OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result + .AddException(), // You can also register exceptions }); ``` @@ -183,20 +185,20 @@ Use outcome strategies to inject faults in advanced scenarios which you need to Also, you end up losing control of how/when to inject outcomes vs faults since this way does not allow you to control separately when to inject a fault vs an outcome. - + ```cs var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // same injection rate for both fault and outcome OutcomeGenerator = args => - { + { Outcome? outcome = Random.Shared.Next(350) switch { < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), + < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), // ⚠️ Avoid this ⚠️ _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; @@ -232,6 +234,7 @@ var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { InjectionRate = 0.1, // different injection rate for faults + EnabledGenerator = args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // different settings might apply to inject faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -251,6 +254,7 @@ var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // different injection rate for outcomes + EnabledGenerator = args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // different settings might apply to inject outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch @@ -272,3 +276,36 @@ var pipeline = new ResiliencePipelineBuilder() .Build(); ``` + +❌ DON'T + +Use outcome strategies to inject only faults, use the [`ChaosFaultStrategy`](fault.md) instead. + + +```cs +new ResiliencePipelineBuilder() + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { + OutcomeGenerator = new OutcomeGenerator() + .AddException(), // ⚠️ Avoid this ⚠️ + }); +``` + + +✅ DO + +Use fault strategies to properly inject the exception. + + +```cs +new ResiliencePipelineBuilder() + .AddChaosFault(new ChaosFaultStrategyOptions + { + FaultGenerator = new FaultGenerator() + .AddException(), + }); +``` + + +> [!NOTE] +> Even though the outcome strategy is flexible enough to allow you to inject outcomes as well as exceptions without the need to chain a fault strategy in the pipeline, use your judgment when doing so because of the caveats and side effects explained before around the telemetry and injection control. diff --git a/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs index 57f6371003..95d961f3d4 100644 --- a/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs +++ b/src/Polly.Core/Simmy/Outcomes/OutcomeGenerator.cs @@ -35,9 +35,6 @@ internal OutcomeGenerator(Func weightGenerator) /// The delegate that generates the exception. /// The weight assigned to this generator. Defaults to 100. /// The current instance of . -#pragma warning disable S1133 // Deprecated code should be removed - [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] -#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(Func generator, int weight = DefaultWeight) { Guard.NotNull(generator); @@ -53,9 +50,6 @@ public OutcomeGenerator AddException(Func generator, int wei /// The delegate that generates the exception, accepting a . /// The weight assigned to this generator. Defaults to 100. /// The current instance of . -#pragma warning disable S1133 // Deprecated code should be removed - [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] -#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(Func generator, int weight = DefaultWeight) { Guard.NotNull(generator); @@ -71,9 +65,6 @@ public OutcomeGenerator AddException(Func /// The type of the exception to generate. /// The weight assigned to this generator. Defaults to 100. /// The current instance of . -#pragma warning disable S1133 // Deprecated code should be removed - [Obsolete("This method is deprecated and will be removed in the next version. Use Chaos fault strategy instead.")] -#pragma warning restore S1133 // Deprecated code should be removed public OutcomeGenerator AddException(int weight = DefaultWeight) where TException : Exception, new() { diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index fc03d6163c..1e6a40da70 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -20,6 +20,7 @@ public static void OutcomeUsage() OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests)) .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) + .AddException(() => new HttpRequestException("Chaos request exception.")), InjectionRate = 0.1 }; @@ -83,6 +84,7 @@ public static void OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result + .AddException(), // You can also register exceptions }); #endregion @@ -114,9 +116,9 @@ public static void OutcomeGeneratorDelegates() #endregion } - public static void AntiPattern_InjectFault() + public static void AntiPattern_GeneratorDelegateInjectFault() { - #region chaos-outcome-anti-pattern-inject-fault + #region chaos-outcome-anti-pattern-generator-inject-fault var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { @@ -154,14 +156,15 @@ public static void AntiPattern_InjectFault() #endregion } - public static void Pattern_InjectFault() + public static void Pattern_GeneratorDelegateInjectFaultAndOutcome() { - #region chaos-outcome-pattern-inject-fault + #region chaos-outcome-pattern-generator-inject-fault var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { InjectionRate = 0.1, // different injection rate for faults + EnabledGenerator = args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // different settings might apply to inject faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -181,6 +184,7 @@ public static void Pattern_InjectFault() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // different injection rate for outcomes + EnabledGenerator = args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // different settings might apply to inject outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch @@ -203,5 +207,37 @@ public static void Pattern_InjectFault() #endregion } + public static void AntiPattern_OnlyInjectFault() + { + #region chaos-outcome-anti-pattern-only-inject-fault + + new ResiliencePipelineBuilder() + .AddChaosOutcome(new ChaosOutcomeStrategyOptions + { + OutcomeGenerator = new OutcomeGenerator() + .AddException(), + }); + + #endregion + } + + public static void Pattern_OnlyInjectFault() + { + #region chaos-outcome-pattern-only-inject-fault + + new ResiliencePipelineBuilder() + .AddChaosFault(new ChaosFaultStrategyOptions + { + FaultGenerator = new FaultGenerator() + .AddException(), + }); + + #endregion + } + + private static bool ShouldEnableFaults(ResilienceContext context) => true; + + private static bool ShouldEnableOutcome(ResilienceContext context) => true; + private static HttpResponseMessage CreateResultFromContext(ResilienceContext context) => new(HttpStatusCode.TooManyRequests); } From 85d88c4956fa9e7726d88a6683e42925d5bdeb39 Mon Sep 17 00:00:00 2001 From: Geo Date: Sat, 2 Mar 2024 16:24:28 -0500 Subject: [PATCH 4/9] fix snippet link --- docs/chaos/outcome.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index cd58b70a48..c0e8f9b231 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -227,7 +227,7 @@ var pipeline = new ResiliencePipelineBuilder() The previous approach is tempting since it looks more succinct, but use the fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome, also by separating them you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. - + ```cs var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() From c8b618e26354020dd71e1d73700d82acdd82fd4b Mon Sep 17 00:00:00 2001 From: Geo Date: Sat, 2 Mar 2024 16:43:40 -0500 Subject: [PATCH 5/9] put back description --- docs/chaos/outcome.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index c0e8f9b231..a47111a62d 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -132,7 +132,7 @@ To generate a faulted outcome (result or exception), you need to specify a `Outc ### Use `OutcomeGenerator` class to generate outcomes -The `OutcomeGenerator` is a convenience API that allows you to specify what outcomes are to be injected. Additionally, it also allows assigning weight to each registered outcome. +The `OutcomeGenerator` is a convenience API that allows you to specify what outcomes (results or exceptions) are to be injected. Additionally, it also allows assigning weight to each registered outcome. ```cs From e47812ca22abaf0c7d8cea2751ebb97d842a896c Mon Sep 17 00:00:00 2001 From: Geo Date: Sun, 3 Mar 2024 11:29:52 -0500 Subject: [PATCH 6/9] PR feedback. --- docs/chaos/outcome.md | 22 +++++++++++----------- src/Snippets/Docs/Chaos.Outcome.cs | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index a47111a62d..bd89dbbacb 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -181,17 +181,17 @@ new ResiliencePipelineBuilder() ❌ DON'T -Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so, one of them is the telemetry events, which might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way, and this could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". +Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so, one of them is the telemetry events, which might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way. This could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". -Also, you end up losing control of how/when to inject outcomes vs faults since this way does not allow you to control separately when to inject a fault vs an outcome. +Also, you end up losing control of how/when to inject outcomes vs. faults since this way does not allow you to separately control when to inject a fault vs. an outcome. ```cs var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // same injection rate for both fault and outcome - OutcomeGenerator = args => + InjectionRate = 0.5, // Same injection rate for both fault and outcome + OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch { @@ -225,7 +225,7 @@ var pipeline = new ResiliencePipelineBuilder() ✅ DO -The previous approach is tempting since it looks more succinct, but use the fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome, also by separating them you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. +The previous approach is tempting since it looks more succinct, but use fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome. Also by separating them, you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. ```cs @@ -233,8 +233,8 @@ var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { - InjectionRate = 0.1, // different injection rate for faults - EnabledGenerator = args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // different settings might apply to inject faults + InjectionRate = 0.1, // Different injection rate for faults + EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // Different settings might apply to inject faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -253,8 +253,8 @@ var pipeline = new ResiliencePipelineBuilder() }) .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // different injection rate for outcomes - EnabledGenerator = args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // different settings might apply to inject outcomes + InjectionRate = 0.5, // Different injection rate for outcomes + EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // Different settings might apply to inject outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch @@ -294,7 +294,7 @@ new ResiliencePipelineBuilder() ✅ DO -Use fault strategies to properly inject the exception. +Use fault strategies to inject the exception. ```cs @@ -308,4 +308,4 @@ new ResiliencePipelineBuilder() > [!NOTE] -> Even though the outcome strategy is flexible enough to allow you to inject outcomes as well as exceptions without the need to chain a fault strategy in the pipeline, use your judgment when doing so because of the caveats and side effects explained before around the telemetry and injection control. +> Even though the outcome strategy is flexible enough to allow you to inject outcomes as well as exceptions without the need to chain a fault strategy in the pipeline, use your judgment when doing so because of the caveats and side effects explained regarding telemetry and injection control. diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index 1e6a40da70..f69634f2f6 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -123,7 +123,7 @@ public static void AntiPattern_GeneratorDelegateInjectFault() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // same injection rate for both fault and outcome - OutcomeGenerator = args => + OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch { @@ -163,8 +163,8 @@ public static void Pattern_GeneratorDelegateInjectFaultAndOutcome() var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { - InjectionRate = 0.1, // different injection rate for faults - EnabledGenerator = args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // different settings might apply to inject faults + InjectionRate = 0.1, // Different injection rate for faults + EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // Different settings might apply to inject faults FaultGenerator = args => { Exception? exception = randomThreshold switch @@ -183,8 +183,8 @@ public static void Pattern_GeneratorDelegateInjectFaultAndOutcome() }) .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // different injection rate for outcomes - EnabledGenerator = args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // different settings might apply to inject outcomes + InjectionRate = 0.5, // Different injection rate for outcomes + EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // Different settings might apply to inject outcomes OutcomeGenerator = args => { Outcome? outcome = randomThreshold switch From 1ecd966c84df10eabc1e9ee2424a55771bbc856e Mon Sep 17 00:00:00 2001 From: Geo Date: Sat, 9 Mar 2024 10:53:57 -0500 Subject: [PATCH 7/9] PR feedback --- docs/chaos/outcome.md | 42 ++++++++++++++-------------- src/Snippets/Docs/Chaos.Outcome.cs | 45 +++++++++++++++--------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index bd89dbbacb..cc42bf0b61 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -69,7 +69,7 @@ var pipeline = new ResiliencePipelineBuilder() OutcomeGenerator = static args => { var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); - return new ValueTask?>(Outcome.FromResult(response)); + return ValueTask.FromResult?>(Outcome.FromResult(response)); }, InjectionRate = 0.1 }) @@ -143,7 +143,7 @@ new ResiliencePipelineBuilder() OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight - .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result + .AddResult(context => new HttpResponseMessage(CreateResultFromContext(context))) // Access the ResilienceContext to create result .AddException(), // You can also register exceptions }); ``` @@ -165,7 +165,8 @@ new ResiliencePipelineBuilder() { < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 350 => Outcome.FromResult(CreateResultFromContext(args.Context)), + < 250 => Outcome.FromResult(new HttpResponseMessage(CreateResultFromContext(args.Context))), + < 350 => Outcome.FromException(new TimeoutException()), _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; @@ -190,15 +191,15 @@ Also, you end up losing control of how/when to inject outcomes vs. faults since var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // Same injection rate for both fault and outcome + InjectionRate = 0.5, // same injection rate for both fault and outcome OutcomeGenerator = static args => - { + { Outcome? outcome = Random.Shared.Next(350) switch { < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), // ⚠️ Avoid this ⚠️ + < 250 => Outcome.FromResult(new HttpResponseMessage(CreateResultFromContext(args.Context))), + < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), // ⚠️ Avoid this ⚠️ _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; @@ -229,21 +230,20 @@ The previous approach is tempting since it looks more succinct, but use fault ch ```cs -var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { InjectionRate = 0.1, // Different injection rate for faults - EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // Different settings might apply to inject faults - FaultGenerator = args => + EnabledGenerator = static args => ShouldEnableFaults(args.Context), // Different settings might apply to inject faults + FaultGenerator = static args => { - Exception? exception = randomThreshold switch + Exception? exception = RandomThreshold switch { >= 250 and < 350 => new HttpRequestException("Chaos request exception."), _ => null }; - return new ValueTask(exception); + return ValueTask.FromResult(exception); }, OnFaultInjected = static args => { @@ -254,18 +254,18 @@ var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // Different injection rate for outcomes - EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // Different settings might apply to inject outcomes - OutcomeGenerator = args => + EnabledGenerator = static args => ShouldEnableOutcome(args.Context), // Different settings might apply to inject outcomes + OutcomeGenerator = static args => { - Outcome? outcome = randomThreshold switch + HttpStatusCode statusCode = RandomThreshold switch { - < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), - < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + < 100 => HttpStatusCode.InternalServerError, + < 150 => HttpStatusCode.TooManyRequests, + < 250 => CreateResultFromContext(args.Context), + _ => HttpStatusCode.OK }; - return ValueTask.FromResult(outcome); + return ValueTask.FromResult?>(Outcome.FromResult(new HttpResponseMessage(statusCode))); }, OnOutcomeInjected = static args => { @@ -287,7 +287,7 @@ new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { OutcomeGenerator = new OutcomeGenerator() - .AddException(), // ⚠️ Avoid this ⚠️ + .AddException(), // ⚠️ Avoid this ⚠️ }); ``` diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index f69634f2f6..5e5a92fd9b 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -11,6 +11,8 @@ namespace Snippets.Docs; internal static partial class Chaos { + private static readonly int RandomThreshold = Random.Shared.Next(350); + public static void OutcomeUsage() { #region chaos-outcome-usage @@ -64,7 +66,7 @@ public static void OutcomeUsage() OutcomeGenerator = static args => { var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); - return new ValueTask?>(Outcome.FromResult(response)); + return ValueTask.FromResult?>(Outcome.FromResult(response)); }, InjectionRate = 0.1 }) @@ -83,7 +85,7 @@ public static void OutcomeGenerator() OutcomeGenerator = new OutcomeGenerator() .AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator .AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight - .AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result + .AddResult(context => new HttpResponseMessage(CreateResultFromContext(context))) // Access the ResilienceContext to create result .AddException(), // You can also register exceptions }); @@ -104,7 +106,7 @@ public static void OutcomeGeneratorDelegates() { < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), + < 250 => Outcome.FromResult(new HttpResponseMessage(CreateResultFromContext(args.Context))), < 350 => Outcome.FromException(new TimeoutException()), _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; @@ -129,8 +131,8 @@ public static void AntiPattern_GeneratorDelegateInjectFault() { < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), + < 250 => Outcome.FromResult(new HttpResponseMessage(CreateResultFromContext(args.Context))), + < 350 => Outcome.FromException(new HttpRequestException("Chaos request exception.")), // ⚠️ Avoid this ⚠️ _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) }; @@ -159,21 +161,20 @@ public static void AntiPattern_GeneratorDelegateInjectFault() public static void Pattern_GeneratorDelegateInjectFaultAndOutcome() { #region chaos-outcome-pattern-generator-inject-fault - var randomThreshold = Random.Shared.Next(350); var pipeline = new ResiliencePipelineBuilder() .AddChaosFault(new ChaosFaultStrategyOptions { InjectionRate = 0.1, // Different injection rate for faults - EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableFaults(args.Context)), // Different settings might apply to inject faults - FaultGenerator = args => + EnabledGenerator = static args => ShouldEnableFaults(args.Context), // Different settings might apply to inject faults + FaultGenerator = static args => { - Exception? exception = randomThreshold switch + Exception? exception = RandomThreshold switch { >= 250 and < 350 => new HttpRequestException("Chaos request exception."), _ => null }; - return new ValueTask(exception); + return ValueTask.FromResult(exception); }, OnFaultInjected = static args => { @@ -184,18 +185,18 @@ public static void Pattern_GeneratorDelegateInjectFaultAndOutcome() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { InjectionRate = 0.5, // Different injection rate for outcomes - EnabledGenerator = static args => ValueTask.FromResult(ShouldEnableOutcome(args.Context)), // Different settings might apply to inject outcomes - OutcomeGenerator = args => + EnabledGenerator = static args => ShouldEnableOutcome(args.Context), // Different settings might apply to inject outcomes + OutcomeGenerator = static args => { - Outcome? outcome = randomThreshold switch + HttpStatusCode statusCode = RandomThreshold switch { - < 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)), - < 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)), - < 250 => Outcome.FromResult(CreateResultFromContext(args.Context)), - _ => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.OK)) + < 100 => HttpStatusCode.InternalServerError, + < 150 => HttpStatusCode.TooManyRequests, + < 250 => CreateResultFromContext(args.Context), + _ => HttpStatusCode.OK }; - return ValueTask.FromResult(outcome); + return ValueTask.FromResult?>(Outcome.FromResult(new HttpResponseMessage(statusCode))); }, OnOutcomeInjected = static args => { @@ -215,7 +216,7 @@ public static void AntiPattern_OnlyInjectFault() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { OutcomeGenerator = new OutcomeGenerator() - .AddException(), + .AddException(), // ⚠️ Avoid this ⚠️ }); #endregion @@ -235,9 +236,9 @@ public static void Pattern_OnlyInjectFault() #endregion } - private static bool ShouldEnableFaults(ResilienceContext context) => true; + private static ValueTask ShouldEnableFaults(ResilienceContext context) => ValueTask.FromResult(true); - private static bool ShouldEnableOutcome(ResilienceContext context) => true; + private static ValueTask ShouldEnableOutcome(ResilienceContext context) => ValueTask.FromResult(true); - private static HttpResponseMessage CreateResultFromContext(ResilienceContext context) => new(HttpStatusCode.TooManyRequests); + private static HttpStatusCode CreateResultFromContext(ResilienceContext context) => HttpStatusCode.TooManyRequests; } From b8fde8d4f43ab41aff72a06cd29e19fd755999c8 Mon Sep 17 00:00:00 2001 From: Geo Date: Mon, 11 Mar 2024 18:51:23 -0500 Subject: [PATCH 8/9] PR feedback --- docs/chaos/outcome.md | 2 +- src/Snippets/Docs/Chaos.Outcome.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index cc42bf0b61..13c82c65be 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -159,7 +159,7 @@ new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { // The same behavior can be achieved with delegates - OutcomeGenerator = args => + OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch { diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index 5e5a92fd9b..383c29bb3f 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -100,7 +100,7 @@ public static void OutcomeGeneratorDelegates() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { // The same behavior can be achieved with delegates - OutcomeGenerator = args => + OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch { From 5a7c5e466b4881f8942f61fe9d470190759b8a03 Mon Sep 17 00:00:00 2001 From: Geo Date: Thu, 21 Mar 2024 17:03:15 -0500 Subject: [PATCH 9/9] pr feedback --- docs/chaos/outcome.md | 8 ++++---- src/Snippets/Docs/Chaos.Outcome.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/chaos/outcome.md b/docs/chaos/outcome.md index 13c82c65be..ed21508877 100644 --- a/docs/chaos/outcome.md +++ b/docs/chaos/outcome.md @@ -182,16 +182,16 @@ new ResiliencePipelineBuilder() ❌ DON'T -Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so, one of them is the telemetry events, which might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way. This could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". +Use outcome strategies to inject faults in advanced scenarios which you need to inject outcomes using delegates. This is an opinionated anti-pattern since you can consider an exception as a result/outcome, however, there might be undesired implications when doing so. One of these implications is these is to the telemetry events. Events might end up affecting your metrics as the `ChaosOutcomeStrategy` reports both result and exceptions in the same way. This could pose a problem for instrumentation purposes since it's clearer looking for fault injected events to be 100% sure where/when exceptions were injected, rather than have them mixed in the same "bag". -Also, you end up losing control of how/when to inject outcomes vs. faults since this way does not allow you to separately control when to inject a fault vs. an outcome. +Another problem is that you end up losing control of how/when to inject outcomes vs. faults. This is because the approach does not allow you to separately control when to inject a fault vs. an outcome. ```cs var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // same injection rate for both fault and outcome + InjectionRate = 0.5, // Same injection rate for both fault and outcome OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch @@ -226,7 +226,7 @@ var pipeline = new ResiliencePipelineBuilder() ✅ DO -The previous approach is tempting since it looks more succinct, but use fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events effectively as faults not just as any other outcome. Also by separating them, you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. +The previous approach is tempting since it looks like less code, but use fault chaos instead as the [`ChaosFaultStrategy`](fault.md) correctly tracks telemetry events as faults, not just as any other outcome. By separating them, you can control the injection rate and enable/disable them separately which gives you more control when it comes to injecting chaos dynamically and in a controlled manner. ```cs diff --git a/src/Snippets/Docs/Chaos.Outcome.cs b/src/Snippets/Docs/Chaos.Outcome.cs index 383c29bb3f..fe1e184773 100644 --- a/src/Snippets/Docs/Chaos.Outcome.cs +++ b/src/Snippets/Docs/Chaos.Outcome.cs @@ -124,7 +124,7 @@ public static void AntiPattern_GeneratorDelegateInjectFault() var pipeline = new ResiliencePipelineBuilder() .AddChaosOutcome(new ChaosOutcomeStrategyOptions { - InjectionRate = 0.5, // same injection rate for both fault and outcome + InjectionRate = 0.5, // Same injection rate for both fault and outcome OutcomeGenerator = static args => { Outcome? outcome = Random.Shared.Next(350) switch