Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Introduce ResilienceStrategyBuilderBase #1236

Merged
merged 4 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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