-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Added PolicyRegistry Feature #231
Added PolicyRegistry Feature #231
Conversation
Implemented IPolicyRegistry interface as DefaultPolicyRegistry using Dictionary<string,> as backend store
Added XML Comments.
Pcl.Specs now targets PCL Profile 111.
Added exception element in xml comment to ContainsKey Method
…xer should overwrite. - Add and Retrieve through indexer are now separate tests
Hi @ankitbko, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution! The agreement was validated by .NET Foundation and real humans are currently evaluating your PR. TTYL, DNFBOT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ankitbko Huge thanks for this contribution around the PolicyRegistry
(and all your initial ideas, which really helped shape it!). This is looking great. I've made some comments, mainly around the specs - what do you think?
using Polly.Registry; | ||
using Polly.NoOp; | ||
|
||
namespace Polly.SharedSpecs.Registry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Namespace should be Polly.Specs.Registry
, to match others. (Visual Studio will have inserted that .SharedSpecs.
on file creation, due to the name of the shared specs library...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely missed this one out. Fixed.
} | ||
|
||
[Fact] | ||
public void Should_Overwrite_Existing_Policy_When_Using_Indexer() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the test assigns the same instance policy
to r[key]
twice, it doesn't prove that the first value at r[key]
has been overwritten by the second assignment. But it looks like this test has been superseded anyway by Should_Overwrite_Existing_Policy_If_Key_Exists_When_Inserting_Using_Idexer()
, which does check the overwrite 👍 . So, perhaps this test can simply be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah.. Missed to delete it. Done now.
|
||
_registry.Count.Should().Be(2); | ||
|
||
//Using Indexer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the indexer section of this test be removed, now that we have the test Should_Allow_Adding_Policy_Using_Indexer()
below?
(Or: If intent is to show/test that we can mix adding policies using .Add(...)
and indexer, maybe state that explicitly in the // comment
, or pull out into separate test with title stating that, eg: Should_be_able_to_mix_adding_policies_with_Add_and_indexer
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
} | ||
|
||
[Fact] | ||
public void Should_Allow_Adding_Policy_Using_Add() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've tended to use lower case for words in the spec name, except if that word is a class / property / method etc normally capitalised in C#. We've also tended to use Should_be_able_to
instead of Should_allow
(apart from where we mean allow only one instead of allow two)
So: Should_be_able_to_add_Policy_using_Add()
(No biggie, but it would be nice to make consistent.) (Similarly, in other specs.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Didn't knew about this. I also tend to keep things consistent and follow the practice of the project. Will keep this in mind in my future contributions. 😁
_registry.Count.Should().Be(1); | ||
|
||
policy = Policy.NoOp(); | ||
key = Guid.NewGuid().ToString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where spec is inserting two different policies against two different keys, it may be slightly clearer (quicker to understand intent at a glance), if we use different variables for the second policy and key (eg key2
, policy2
), rather than re-using existing variables policy
and key
. What do you think?
(Similarly, in other specs.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't make any difference in behavior as I was re-initializing the variables but I see your point. I actually had done the same before but removed it later. 😞 Will make the changes.
/// <param name="key">The key of the policy to get or set.</param> | ||
/// <returns>The policy with specified Key.</returns> | ||
/// <exception cref="System.ArgumentNullException">Key is null.</exception> | ||
/// <exception cref="KeyNotFoundException">The property is retrieved and key is not found.</exception> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? The property is retrieved ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// </summary> | ||
/// <typeparam name="Key">The type of keys in the dictionary</typeparam> | ||
/// <typeparam name="Policy">The type of Policy to store. Must have <see cref="Polly.Policy"/> as base class. </typeparam> | ||
public interface IPolicyRegistry<Key, Policy> where Policy: Polly.Policy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To follow the standard Microsoft style for generic type parameters, I suggest we call the type parameters TKey
and TPolicy
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// <param name="key">The key whose value to get</param> | ||
/// <param name="value"> | ||
/// When this method returns, the Policy associated with the specified key, if the | ||
/// key is found; otherwise, the default value for the type of the value parameter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherwise, null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Default value for reference types would have returned null. Making it explicit makes it more clear.
|
||
bool result = false; | ||
_registry.Invoking(r => result = r.ContainsKey(key)) | ||
.ShouldNotThrow(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe .ShouldNotThrow()
is redundant (can be removed) throughout this test. .ShouldNotThrow()
is perfect where it adds semantic value to the test, to state "this call shouldn't throw" - to make a contrast with elsewhere in the test (or other circumstances perhaps in different tests) where the same call might throw. In this case, we never expect ContainsKey(...)
to throw with a non-null key, so maybe _registry.ContainsKey(key).Should().BeTrue();
is just more readable?
What do you think? Simplicity better? See if you think the same applies elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed .ShouldNotThrow()
which were redundant unless, as you mentioned, to make a contrast with elsewhere in the test (Should_not_be_able_to_add_Policy_with_duplicate_key_using_Add
) and where the test explicitly says Should_not_throw (Should_not_throw_while_retrieving_when_key_does_not_exist_using_TryGetValue
)
PS: I hadn't worked with FluentAssertion much before. Thanks for helping me out with best practices. Really appreciate it.
} | ||
|
||
[Fact] | ||
public void Should_Not_Throw_While_Retrieving_When_Key_Does_Not_Exist_Using_TryGetValue() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice also to have the complementary test: Should_throw_while_retrieving_using_indexer_when_key_does_not_exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added. I also added more test cases to check ArgumentNullException
is thrown when key is null while adding, removing and retrieving policy. Plus since the number of tests has now increased, I grouped them intro different region
.
- Exception thrown when retrieving when key does not exist - Exception thrown when key is null while adding, removing and retriving policy.
…ng ContainsKey Formatted the specs Grouped them in region.
@ankitbko Apologies for my delay replying (due to volume of paid work!). Many thanks for the revisions! This is perfect for When travelling two weeks ago, I realised we hadn't provided a (Background: In Polly, What we need to do To complete the PolicyRegistry offering, I think we need to add a
I think the need for Views? |
@reisenberger No need to apologies. Its been great working with you. Thanks for such detailed discussion on all topic. And excellent spot that we missed I did not fully understand what you mentioned here of Are you suggesting to create another class Just thinking out loud, what if we have another kind of Ok so the requirement is that to register Then the next challenge is that concrete implementation also depends upon public class DefaultPolicyRegistry : IPolicyRegistry<string, Policy> How about we create an empty interface For the user, registry just stores policy(can be anywhere) and gives ability to retrieve them based on Apologies if I misunderstood you and you meant something different (I had a long, tiring day). PS: I did not create a new PR instead I just switched the target branch in this pull request to v5.0.7 (so the discussions are not split in multiple places). I hope it works for you. |
@ankitbko Perfect to just re-target the PR to the new branch! The evolution of Polly has led to I was also uncomfortable about multiple registries: one registry would probably be better, as you say. Creating an empty marker interface, as you suggest, could be a way to do this. I can think of one other approach. I will aim to spike out - or describe in more detail - shortly. |
Huge thanks @ankitbko for all the great thinking towards Empty marker interface definitely a possible way to bridge between (a) if the registry returns instances fulfilling empty
(b) given an empty interface, users might soon ask why it is empty, and ask for populated interfaces that in fact provide the relevant Which led me to consider whether: (i) we can/could/should now introduce interfaces, say, (ii) whether either the Re (ii), I think not - intend to post later on this - but now is definitely the time to consider, before we introduce interfaces. If we go for interfaces now, the helper methods at (a) above also would return those rather than the concrete Thoughts on any aspect of this? |
@reisenberger Very inline with my thoughts. Next steps could very well be populating the relevant interfaces. |
Hi @ankitbko ! Here reisenberger/Polly/policy-registry-TResult-nointerfaces is a prototype branch (light amend from yours) providing a single policy registry class offering overloads for both
Comments welcome! We could go this way with |
Hi @ankitbko ! Here is a Registry based on yours, but allowing us to pull policies out of the Registry correctly typed, based on generic methods. The generic methods become particularly useful because of adding interfaces. This version again omits the EDIT: Do you think it is better to exclude the Please let me know what you think! (particularly between the two options of this comment, and the previous one) |
@reisenberger Sorry for delay. This is excellent. Interfaces looks amazing. Couple of thoughts on the Interface implementation of registry -
I am leaning more towards interfaces side. It leaves registry class clean (rather than having different get set for Policy and Policy). What do you think? As for But if we change |
@ankitbko Huge thanks for your thoughtful feedback on the interface-based approach I overlaid on your PR! Pushed changes in response to your comments, again to this branch: https://github.com/reisenberger/Polly/tree/feature/interfacesplusregistry Responses to your comments:
Agree. Done!
Great catch! Done!
The intention was that The original reason for not using
Agree could reduce it to
👍
Completely get the suggestion. I'm (starting out) genuinely ambivalent, per following discussion: (a) The history is that the sync/async split in Polly was, long pre-AppvNext, implemented (unfortunately imo) as a runtime exception on the Policy class (instances of My introducing As (b-i) Keep Having started out ambivalent, I'm leaning to (b-i). Doing (b-ii) just places us back to the "false promise" that a single Policy instance can be used for both sync or async, when in fact it can only be used for one or other. (b-i) at least begins to push the solution architecture in the right direction. For further background, this recent blog post sets out why I think we're stuck (without a major change of syntax) with separate Policy instances for sync and async executions, in Polly. Sorry for that long explanation. The sync/async split in Polly is an unhappy legacy part of the Polly architecture I've been tussling with, hence the long explanation - and interested in people's views. 😅 |
Hi @ankitbko Per discussion on slack, I have:
Anything you think we should add/change? I'll aim to sort out the PRs between code branches in next few days |
@ankitbko Merged this to the App-vNext:v5.0.7registry branch. Decided it would be easier to merge there, then add my interfaces on top. Thank you for your great contribution to this feature! 💯 |
Implementation of PolicyRegistry Proposal #226