Skip to content

Commit

Permalink
[Proposal] Introduce ResilienceStrategyBuilderBase (#1236)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored May 30, 2023
1 parent 141bd0a commit 045cec0
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 318 deletions.
4 changes: 2 additions & 2 deletions src/Polly.Core.Tests/GenericResilienceStrategyBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ public void Ctor_EnsureDefaults()
_builder.Properties.Should().NotBeNull();
_builder.TimeProvider.Should().Be(TimeProvider.System);
_builder.OnCreatingStrategy.Should().BeNull();
_builder.Builder.IsGenericBuilder.Should().BeTrue();
_builder.IsGenericBuilder.Should().BeTrue();
}

[Fact]
public void CopyCtor_Ok()
{
new ResilienceStrategyBuilder<string>(new ResilienceStrategyBuilder()).Builder.IsGenericBuilder.Should().BeTrue();
new ResilienceStrategyBuilder<string>(new ResilienceStrategyBuilder()).IsGenericBuilder.Should().BeTrue();
}

[Fact]
Expand Down
3 changes: 1 addition & 2 deletions src/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,12 @@ public void BuildStrategy_EnsureCorrectContext()
{
BuilderName = "builder-name",
TimeProvider = new FakeTimeProvider().Object,
IsGenericBuilder = true
};

builder.AddStrategy(
context =>
{
context.IsGenericBuilder.Should().BeTrue();
context.IsGenericBuilder.Should().BeFalse();
context.BuilderName.Should().Be("builder-name");
context.StrategyName.Should().Be("strategy-name");
context.StrategyType.Should().Be("Test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public static class CircuitBreakerResilienceStrategyBuilderExtensions
/// <summary>
/// Add advanced circuit breaker strategy to the builder.
/// </summary>
/// <typeparam name="TResult">The type of result the circuit breaker strategy handles.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="options">The options instance.</param>
/// <returns>A builder with the circuit breaker strategy added.</returns>
Expand All @@ -25,58 +24,35 @@ public static class CircuitBreakerResilienceStrategyBuilderExtensions
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> are invalid.</exception>
public static ResilienceStrategyBuilder<TResult> AddAdvancedCircuitBreaker<TResult>(this ResilienceStrategyBuilder<TResult> builder, AdvancedCircuitBreakerStrategyOptions<TResult> options)
public static ResilienceStrategyBuilder AddAdvancedCircuitBreaker(this ResilienceStrategyBuilder builder, AdvancedCircuitBreakerStrategyOptions options)
{
Guard.NotNull(builder);
Guard.NotNull(options);

ValidationHelper.ValidateObject(options, "The advanced circuit breaker strategy options are invalid.");

return builder.AddStrategy(
context =>
{
var behavior = new AdvancedCircuitBehavior(
options.FailureThreshold,
options.MinimumThroughput,
HealthMetrics.Create(options.SamplingDuration, context.TimeProvider));

return CreateStrategy(context, options, behavior);
},
options);
return builder.AddAdvancedCircuitBreakerCore(options);
}

/// <summary>
/// Add advanced circuit breaker strategy to the builder.
/// </summary>
/// <typeparam name="TResult">The type of result the circuit breaker strategy handles.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="options">The options instance.</param>
/// <returns>A builder with the circuit breaker strategy added.</returns>
/// <remarks>
/// See <see cref="AdvancedCircuitBreakerStrategyOptions"/> for more details about the advanced circuit breaker strategy.
/// See <see cref="AdvancedCircuitBreakerStrategyOptions{TResult}"/> for more details about the advanced circuit breaker strategy.
/// <para>
/// If you are discarding the strategy created by this call make sure to use <see cref="CircuitBreakerManualControl"/> and dispose the manual control instance when the strategy is no longer used.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> are invalid.</exception>
public static ResilienceStrategyBuilder AddAdvancedCircuitBreaker(this ResilienceStrategyBuilder builder, AdvancedCircuitBreakerStrategyOptions options)
public static ResilienceStrategyBuilder<TResult> AddAdvancedCircuitBreaker<TResult>(this ResilienceStrategyBuilder<TResult> builder, AdvancedCircuitBreakerStrategyOptions<TResult> options)
{
Guard.NotNull(builder);
Guard.NotNull(options);

ValidationHelper.ValidateObject(options, "The advanced circuit breaker strategy options are invalid.");

return builder.AddStrategy(
context =>
{
var behavior = new AdvancedCircuitBehavior(
options.FailureThreshold,
options.MinimumThroughput,
HealthMetrics.Create(options.SamplingDuration, context.TimeProvider));

return CreateStrategy(context, options, behavior);
},
options);
return builder.AddAdvancedCircuitBreakerCore(options);
}

/// <summary>
Expand All @@ -99,9 +75,7 @@ public static ResilienceStrategyBuilder<TResult> AddSimpleCircuitBreaker<TResult
Guard.NotNull(builder);
Guard.NotNull(options);

ValidationHelper.ValidateObject(options, "The circuit breaker strategy options are invalid.");

return builder.AddStrategy(context => CreateStrategy<TResult>(context, options, new ConsecutiveFailuresCircuitBehavior(options.FailureThreshold)), options);
return builder.AddSimpleCircuitBreakerCore(options);
}

/// <summary>
Expand All @@ -111,7 +85,7 @@ public static ResilienceStrategyBuilder<TResult> AddSimpleCircuitBreaker<TResult
/// <param name="options">The options instance.</param>
/// <returns>A builder with the circuit breaker strategy added.</returns>
/// <remarks>
/// See <see cref="SimpleCircuitBreakerStrategyOptions"/> for more details about the advanced circuit breaker strategy.
/// See <see cref="SimpleCircuitBreakerStrategyOptions{TResult}"/> for more details about the advanced circuit breaker strategy.
/// <para>
/// If you are discarding the strategy created by this call make sure to use <see cref="CircuitBreakerManualControl"/> and dispose the manual control instance when the strategy is no longer used.
/// </para>
Expand All @@ -123,27 +97,36 @@ public static ResilienceStrategyBuilder AddSimpleCircuitBreaker(this ResilienceS
Guard.NotNull(builder);
Guard.NotNull(options);

ValidationHelper.ValidateObject(options, "The circuit breaker strategy options are invalid.");
return builder.AddSimpleCircuitBreakerCore(options);
}

private static TBuilder AddAdvancedCircuitBreakerCore<TBuilder, TResult>(this TBuilder builder, AdvancedCircuitBreakerStrategyOptions<TResult> options)
where TBuilder : ResilienceStrategyBuilderBase
{
ValidationHelper.ValidateObject(options, "The advanced circuit breaker strategy options are invalid.");

builder.AddStrategy(
context =>
{
var behavior = new AdvancedCircuitBehavior(
options.FailureThreshold,
options.MinimumThroughput,
HealthMetrics.Create(options.SamplingDuration, context.TimeProvider));

return CreateStrategy(context, options, behavior);
},
options);

return builder.AddStrategy(context => CreateStrategy(context, options, new ConsecutiveFailuresCircuitBehavior(options.FailureThreshold)), options);
return builder;
}

internal static CircuitBreakerResilienceStrategy CreateStrategy(ResilienceStrategyBuilderContext context, CircuitBreakerStrategyOptions<object> options, CircuitBehavior behavior)
private static TBuilder AddSimpleCircuitBreakerCore<TBuilder, TResult>(this TBuilder builder, SimpleCircuitBreakerStrategyOptions<TResult> options)
where TBuilder : ResilienceStrategyBuilderBase
{
var controller = new CircuitStateController(
options.BreakDuration,
context.CreateInvoker(options.OnOpened),
context.CreateInvoker(options.OnClosed),
options.OnHalfOpened,
behavior,
context.TimeProvider,
context.Telemetry);
ValidationHelper.ValidateObject(options, "The circuit breaker strategy options are invalid.");

return new CircuitBreakerResilienceStrategy(
context.CreateInvoker(options.ShouldHandle)!,
controller,
options.StateProvider,
options.ManualControl);
builder.AddStrategy(context => CreateStrategy(context, options, new ConsecutiveFailuresCircuitBehavior(options.FailureThreshold)), options);
return builder;
}

internal static CircuitBreakerResilienceStrategy CreateStrategy<TResult>(ResilienceStrategyBuilderContext context, CircuitBreakerStrategyOptions<TResult> options, CircuitBehavior behavior)
Expand Down
61 changes: 10 additions & 51 deletions src/Polly.Core/ResilienceStrategyBuilder.TResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,21 @@ namespace Polly;
/// The resulting instance of <see cref="ResilienceStrategy{TResult}"/> created by the <see cref="Build"/> call will execute the strategies in the same order they were added to the builder.
/// The order of the strategies is important.
/// </remarks>
public class ResilienceStrategyBuilder<TResult>
public sealed class ResilienceStrategyBuilder<TResult> : ResilienceStrategyBuilderBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ResilienceStrategyBuilder{TResult}"/> class.
/// </summary>
public ResilienceStrategyBuilder() => Builder = new()
public ResilienceStrategyBuilder()
{
IsGenericBuilder = true
};

internal ResilienceStrategyBuilder(ResilienceStrategyBuilder builder)
{
Builder = builder;
Builder.IsGenericBuilder = true;
}

/// <summary>
/// Gets or sets the name of the builder.
/// </summary>
/// <remarks>This property is also included in the telemetry that is produced by the individual resilience strategies.</remarks>
[Required(AllowEmptyStrings = true)]
public string BuilderName
{
get => Builder.BuilderName;
set => Builder.BuilderName = value;
}

/// <summary>
/// Gets the custom properties attached to builder options.
/// </summary>
public ResilienceProperties Properties => Builder.Properties;

/// <summary>
/// Gets or sets a <see cref="TimeProvider"/> that is used by strategies that work with time.
/// </summary>
/// <remarks>
/// This property is internal until we switch to official System.TimeProvider.
/// </remarks>
[Required]
internal TimeProvider TimeProvider
{
get => Builder.TimeProvider;
set => Builder.TimeProvider = value;
}

/// <summary>
/// Gets or sets the callback that is invoked just before the final resilience strategy is being created.
/// </summary>
internal Action<IList<ResilienceStrategy>>? OnCreatingStrategy
internal ResilienceStrategyBuilder(ResilienceStrategyBuilderBase other)
: base(other)
{
get => Builder.OnCreatingStrategy;
set => Builder.OnCreatingStrategy = value;
}

internal ResilienceStrategyBuilder Builder { get; }
internal override bool IsGenericBuilder => true;

/// <summary>
/// Adds an already created strategy instance to the builder.
Expand All @@ -75,11 +35,11 @@ internal Action<IList<ResilienceStrategy>>? OnCreatingStrategy
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="strategy"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used.</exception>
public ResilienceStrategyBuilder<TResult> AddStrategy(ResilienceStrategy strategy)
public new ResilienceStrategyBuilder<TResult> AddStrategy(ResilienceStrategy strategy)
{
Guard.NotNull(strategy);

Builder.AddStrategy(strategy);
base.AddStrategy(strategy);
return this;
}

Expand All @@ -92,15 +52,14 @@ public ResilienceStrategyBuilder<TResult> AddStrategy(ResilienceStrategy strateg
/// <exception cref="ArgumentNullException">Thrown when <paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used.</exception>
/// <exception cref="ValidationException">Thrown when the <paramref name="options"/> are invalid.</exception>
public ResilienceStrategyBuilder<TResult> AddStrategy(
public new ResilienceStrategyBuilder<TResult> AddStrategy(
Func<ResilienceStrategyBuilderContext, ResilienceStrategy> factory,
ResilienceStrategyOptions options)
{
Guard.NotNull(factory);
Guard.NotNull(options);

Builder.AddStrategy(factory, options);

base.AddStrategy(factory, options);
return this;
}

Expand All @@ -109,5 +68,5 @@ public ResilienceStrategyBuilder<TResult> AddStrategy(
/// </summary>
/// <returns>An instance of <see cref="ResilienceStrategy{TResult}"/>.</returns>
/// <exception cref="ValidationException">Thrown when this builder has invalid configuration.</exception>
public ResilienceStrategy<TResult> Build() => new(Builder.Build());
public ResilienceStrategy<TResult> Build() => new(BuildStrategy());
}
Loading

0 comments on commit 045cec0

Please sign in to comment.