-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce ResilienceStrategyRegistry (#1085)
- Loading branch information
Showing
6 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
src/Polly.Core.Tests/Registry/ResilienceStrategyProviderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using Polly.Registry; | ||
|
||
namespace Polly.Core.Tests.Registry; | ||
|
||
public class ResilienceStrategyProviderTests | ||
{ | ||
[Fact] | ||
public void Get_DoesNotExist_Throws() | ||
{ | ||
new Provider() | ||
.Invoking(o => o.Get("not-exists")) | ||
.Should() | ||
.Throw<KeyNotFoundException>() | ||
.WithMessage("Unable to find a resilience strategy associated with the key 'not-exists'. Please ensure that either the resilience strategy or the builder is registered."); | ||
} | ||
|
||
[Fact] | ||
public void Get_Exist_Ok() | ||
{ | ||
var provider = new Provider { Strategy = new TestResilienceStrategy() }; | ||
|
||
provider.Get("exists").Should().Be(provider.Strategy); | ||
} | ||
|
||
private class Provider : ResilienceStrategyProvider<string> | ||
{ | ||
public ResilienceStrategy? Strategy { get; set; } | ||
|
||
public override bool TryGet(string key, [NotNullWhen(true)] out ResilienceStrategy? strategy) | ||
{ | ||
strategy = Strategy; | ||
return Strategy != null; | ||
} | ||
} | ||
} |
172 changes: 172 additions & 0 deletions
172
src/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Globalization; | ||
using Polly.Builder; | ||
using Polly.Registry; | ||
|
||
namespace Polly.Core.Tests.Registry; | ||
|
||
public class ResilienceStrategyRegistryTests | ||
{ | ||
private Action<ResilienceStrategyBuilder> _callback = _ => { }; | ||
|
||
[Fact] | ||
public void Ctor_Default_Ok() | ||
{ | ||
this.Invoking(_ => new ResilienceStrategyRegistry<string>()).Should().NotThrow(); | ||
} | ||
|
||
[Fact] | ||
public void Ctor_InvalidOptions_Throws() | ||
{ | ||
this.Invoking(_ => new ResilienceStrategyRegistry<string>(new ResilienceStrategyRegistryOptions<string> { BuilderFactory = null! })) | ||
.Should() | ||
.Throw<ValidationException>().WithMessage("The resilience strategy registry options are invalid.*"); | ||
} | ||
|
||
[Fact] | ||
public void Clear_Ok() | ||
{ | ||
var registry = new ResilienceStrategyRegistry<string>(); | ||
|
||
registry.TryAddBuilder("C", (_, b) => b.AddStrategy(new TestResilienceStrategy())); | ||
|
||
registry.TryAdd("A", new TestResilienceStrategy()); | ||
registry.TryAdd("B", new TestResilienceStrategy()); | ||
registry.TryAdd("C", new TestResilienceStrategy()); | ||
|
||
registry.Clear(); | ||
|
||
registry.TryGet("A", out _).Should().BeFalse(); | ||
registry.TryGet("B", out _).Should().BeFalse(); | ||
registry.TryGet("C", out _).Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public void Remove_Ok() | ||
{ | ||
var registry = new ResilienceStrategyRegistry<string>(); | ||
|
||
registry.TryAdd("A", new TestResilienceStrategy()); | ||
registry.TryAdd("B", new TestResilienceStrategy()); | ||
|
||
registry.Remove("A").Should().BeTrue(); | ||
registry.Remove("A").Should().BeFalse(); | ||
|
||
registry.TryGet("A", out _).Should().BeFalse(); | ||
registry.TryGet("B", out _).Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public void RemoveBuilder_Ok() | ||
{ | ||
var registry = new ResilienceStrategyRegistry<string>(); | ||
registry.TryAddBuilder("A", (_, b) => b.AddStrategy(new TestResilienceStrategy())); | ||
|
||
registry.RemoveBuilder("A").Should().BeTrue(); | ||
registry.RemoveBuilder("A").Should().BeFalse(); | ||
|
||
registry.TryGet("A", out _).Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void GetStrategy_BuilderMultiInstance_EnsureMultipleInstances() | ||
{ | ||
var builderName = "A"; | ||
var registry = CreateRegistry(); | ||
var strategies = new HashSet<ResilienceStrategy>(); | ||
registry.TryAddBuilder(StrategyId.Create(builderName), (_, builder) => builder.AddStrategy(new TestResilienceStrategy())); | ||
|
||
for (int i = 0; i < 100; i++) | ||
{ | ||
var key = StrategyId.Create(builderName, i.ToString(CultureInfo.InvariantCulture)); | ||
|
||
strategies.Add(registry.Get(key)); | ||
|
||
// call again, the strategy should be already cached | ||
strategies.Add(registry.Get(key)); | ||
} | ||
|
||
strategies.Should().HaveCount(100); | ||
} | ||
|
||
[Fact] | ||
public void AddBuilder_GetStrategy_EnsureCalled() | ||
{ | ||
var activatorCalls = 0; | ||
_callback = _ => activatorCalls++; | ||
var registry = CreateRegistry(); | ||
var called = 0; | ||
registry.TryAddBuilder(StrategyId.Create("A"), (key, builder) => | ||
{ | ||
builder.AddStrategy(new TestResilienceStrategy()); | ||
builder.Options.Properties.Set(StrategyId.ResilienceKey, key); | ||
called++; | ||
}); | ||
|
||
var key1 = StrategyId.Create("A"); | ||
var key2 = StrategyId.Create("A", "Instance1"); | ||
var key3 = StrategyId.Create("A", "Instance2"); | ||
var keys = new[] { key1, key2, key3 }; | ||
var strategies = keys.ToDictionary(k => k, registry.Get); | ||
foreach (var key in keys) | ||
{ | ||
registry.Get(key); | ||
} | ||
|
||
called.Should().Be(3); | ||
activatorCalls.Should().Be(3); | ||
strategies.Keys.Should().HaveCount(3); | ||
} | ||
|
||
[Fact] | ||
public void TryGet_NoBuilder_Null() | ||
{ | ||
var registry = CreateRegistry(); | ||
var key = StrategyId.Create("A"); | ||
|
||
registry.TryGet(key, out var strategy).Should().BeFalse(); | ||
strategy.Should().BeNull(); | ||
} | ||
|
||
[Fact] | ||
public void TryGet_ExplicitStrategyAdded_Ok() | ||
{ | ||
var expectedStrategy = new TestResilienceStrategy(); | ||
var registry = CreateRegistry(); | ||
var key = StrategyId.Create("A", "Instance"); | ||
registry.TryAdd(key, expectedStrategy).Should().BeTrue(); | ||
|
||
registry.TryGet(key, out var strategy).Should().BeTrue(); | ||
|
||
strategy.Should().BeSameAs(expectedStrategy); | ||
} | ||
|
||
[Fact] | ||
public void TryAdd_Twice_SecondNotAdded() | ||
{ | ||
var expectedStrategy = new TestResilienceStrategy(); | ||
var registry = CreateRegistry(); | ||
var key = StrategyId.Create("A", "Instance"); | ||
registry.TryAdd(key, expectedStrategy); | ||
|
||
registry.TryAdd(key, new TestResilienceStrategy()).Should().BeFalse(); | ||
|
||
registry.TryGet(key, out var strategy).Should().BeTrue(); | ||
strategy.Should().BeSameAs(expectedStrategy); | ||
} | ||
|
||
private ResilienceStrategyRegistry<StrategyId> CreateRegistry() | ||
{ | ||
return new ResilienceStrategyRegistry<StrategyId>(new ResilienceStrategyRegistryOptions<StrategyId> | ||
{ | ||
BuilderFactory = () => | ||
{ | ||
var builder = new ResilienceStrategyBuilder(); | ||
_callback(builder); | ||
return builder; | ||
}, | ||
StrategyComparer = StrategyId.Comparer, | ||
BuilderComparer = StrategyId.BuilderComparer | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Polly.Core.Tests.Registry; | ||
|
||
public record StrategyId(Type Type, string BuilderName, string InstanceName = "") | ||
{ | ||
public static readonly ResiliencePropertyKey<StrategyId> ResilienceKey = new("Polly.StrategyId"); | ||
|
||
public static StrategyId Create<T>(string builderName, string instanceName = "") | ||
=> new(typeof(T), builderName, instanceName); | ||
public static StrategyId Create(string builderName, string instanceName = "") | ||
=> new(typeof(StrategyId), builderName, instanceName); | ||
|
||
public static readonly IEqualityComparer<StrategyId> Comparer = EqualityComparer<StrategyId>.Default; | ||
|
||
public static readonly IEqualityComparer<StrategyId> BuilderComparer = new BuilderResilienceKeyComparer(); | ||
|
||
private sealed class BuilderResilienceKeyComparer : IEqualityComparer<StrategyId> | ||
{ | ||
public bool Equals(StrategyId? x, StrategyId? y) => x?.Type == y?.Type && x?.BuilderName == y?.BuilderName; | ||
|
||
public int GetHashCode(StrategyId obj) => (obj.Type, obj.BuilderName).GetHashCode(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Polly.Registry; | ||
|
||
#pragma warning disable CA1716 // Identifiers should not match keywords | ||
|
||
/// <summary> | ||
/// Represents a provider for resilience strategies that are accessible by <typeparamref name="TKey"/>. | ||
/// </summary> | ||
/// <typeparam name="TKey">The type of the key.</typeparam> | ||
public abstract class ResilienceStrategyProvider<TKey> | ||
where TKey : notnull | ||
{ | ||
/// <summary> | ||
/// Retrieves a resilience strategy from the provider using the specified key. | ||
/// </summary> | ||
/// <param name="key">The key used to identify the resilience strategy.</param> | ||
/// <returns>The resilience strategy associated with the specified key.</returns> | ||
/// <exception cref="KeyNotFoundException">Thrown when no resilience strategy is found for the specified key.</exception> | ||
public virtual ResilienceStrategy Get(TKey key) | ||
{ | ||
if (TryGet(key, out var strategy)) | ||
{ | ||
return strategy; | ||
} | ||
|
||
throw new KeyNotFoundException($"Unable to find a resilience strategy associated with the key '{key}'. " + | ||
$"Please ensure that either the resilience strategy or the builder is registered."); | ||
} | ||
|
||
/// <summary> | ||
/// Tries to get a resilience strategy from the provider using the specified key. | ||
/// </summary> | ||
/// <param name="key">The key used to identify the resilience strategy.</param> | ||
/// <param name="strategy">The output resilience strategy if found, null otherwise.</param> | ||
/// <returns>true if the strategy was found, false otherwise.</returns> | ||
public abstract bool TryGet(TKey key, [NotNullWhen(true)] out ResilienceStrategy? strategy); | ||
} |
Oops, something went wrong.