-
-
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
[Proposal] Should we keep or remove the non-generic ResilienceStrategy
#1429
Comments
At a summary glance, the proposed changes look a lot clunkier to use. |
I see almost no difference in the usage though I see a lot of benefits |
I couldn't catch this - is there a specific use-case? why would anyone want to re-use a strategy across different pipelines? |
this can be hidden behind a syntax sugar Execute<Action<..>> method, right? |
That's also the way it works currently. Generic builder won't allow you to combine strategies that work with different result types while still allowing to add non-reactive strategies to it. Although that check is only when using public APIs. Internally the core indeed uses non-generic strategies for everything.
Maybe to expose these as void-based extensions for
For example, shared circuit breaker across multiple pipelines. Ech pipeline handles different result types, but uses non-generic circuit breaker. see #959 or @martincostello project that mirrors their internal project: |
Seems like there are more cons than pros, while it favors the maintenance I think it disfavors the usage experience, personally, it feels kinda convoluted, especially for the void strategies. |
ResilienceStrategy
ResilienceStrategy
I agree that unifying the builders would improve maintainability. However, the new usage looks to me like an API limitation workaround. I believe that To me generic/non-generic is the intuitive way to do it. If I care about a result, I specify the type. If I don't I don't specify anything. The unification would be fine, if C# had standard object type for null/void/no data. But instead it treats void special, so I think Polly should do it the same way, not try to be different. Ease of use is probably the most important thing. If it's too difficult, people would rather write it themselves, usually all they need is just retries and you can do that fairly easily. |
I agree with the comment above. The API surface as it is today with regards to TResult vs void looks more intuitive to use than having to use I think we should err on the side of optimising the API for the consumers' usage (within reason), rather than making it harder to the use for the majority for the benefit of a minority (i.e. the Polly maintainers and contributors). Polly is a very popular package and the spectrum of knowledge/experience its users have ranges from the novice to the deep expert. The majority of the developers using Polly will be on the less experienced side of that spectrum, so I think hampering the usability of the API to try and remove the long tail of remaining potentially optimisable CPU cycles out of the design where it affects the end users is something we should avoid. |
Thank everyone for the feedback! I would like to add more context on why the core building block, As we designed the V8 API, we knew we would implement two types of strategies:
The reactive strategies inspect and handle concrete result types. Hence, when defining them, consumers must provide generic callbacks to manage the values. On the other hand, the non-reactive strategies only perform actions before or after the execution callback provided by the user. They're indifferent to the result type. Moreover, it was common to combine reactive strategies with non-reactive ones. If we were to place a generic constraint on A challenge we faced was exposing the API for both generic and non-generic strategies without causing excessive code duplication. For non-reactive strategies, avoiding duplication was relatively straightforward, as they function equally well with both generic and non-generic APIs. In the case of reactive strategies, we underwent several iterations, each one progressively reducing the amount of code, until we finally settled on the current model. Each reactive strategy now defines generic options: public class RetryStrategyOptions<T>
{
// omitted for brevity
} For non-generic APIs, we simply expose non-generic options derived from the generic ones, thereby minimizing duplication: public class RetryStrategyOptions : RetryStrategyOptions<object>
{
} In terms of implementation, a single one exists for both generic and non-generic versions, eliminating any duplication. An example of this can be found in RetryResilienceStrategy. Reactive strategies use a straightforward bridge that adds a generic constraint. Lastly, the strategy needs to be exposed on both While it's possible that extension authors may unintentionally mix unsupported reactive strategies, this issue can easily be spotted through unit testing. For the typical consumer, the API is safe and doesn't permit the mixing of incompatible strategies, such as combining I hope this explanation clarifies and provides more details on the V8 design. |
I agree with the others here who believe that removing non-generic strategies has more cons than pros, not least of which is adding complexity where there is no clear benefit in doing so for most developers/use cases. Explicitly using |
I think we should keep both the non-generic & generic support. It is a bit of more code on our side but it is for the benefit of users and usage simplicity, which seems worth. The usage of one policy for various possible return types as well as void "returns" is a common use case. Forcing a meta type which allows both "object" and "void" looks like a hacky workaround to me as Joel mentioned. For the usage of policies for handling exceptions only (assuming any result a valid object to return back) there is no need to declare a typed policy as the return type is not relevant. However users would need to force some "dummy" type which will only clutter the method for no benefit. |
We are making the executive decision to keep both non-generic and generic support. Marking issue as closed. |
Recent enhancements to
ResilienceStrategy<T>
(#1428) have significantly improved the usability of generic resilience strategies, expanding use-case scenarios:This progress paves the way for potential removal of the non-generic
ResilienceStrategy
type and associated infrastructure. This proposal brings both advantages and disadvantages that should be thoroughly assessed before making a decision.Advantages
ResilienceStrategy
toResilienceStrategy<T>
. This change would render the bridge class obsolete, integrating support for reactive strategies directly.Disadvantages
ResilienceStrategy
, which lacks a generic constraint, facilitating strategy composition. If non-generic strategies are removed, the newResilienceStrategy<T>
signature would restrict composability to strategies ofT
orResilienceStrategy<object>
.Timeout
andRateLimiter
are indifferent to result types and can naturally be executed across all result types. Under the new model, these strategies would no longer have this "natural implementation."Void
Result Types: The removal ofResilienceStrategy
would eliminate a straightforward method for executing void-based callbacks. While aVoidResult
type could be introduced, usage would be more complex than the existing system.ResilienceStrategy
: Presently, it's simple to pass and flowResilienceStrategy
in the API for basic scenarios or when exceptions are exclusively handled by the consumer. In the proposed model, passingResilienceStrategy<object>
feels less intuitive.Current Usage:
New usage:
Contributes to #1365
Would love your feedback and insights on this matter before we proceed with any modifications, as these changes could significantly affect the usability of V8.
@martincostello
@joelhulen
@juraj-blazek
@geeknoid
@PeterCsalaHbo
@andrey-noskov
@vany0114
The text was updated successfully, but these errors were encountered: