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
Annotation exception config (#662) #679
Annotation exception config (#662) #679
Conversation
CircuitBreaker annotations now support both ignoreExceptions and recordExceptions. The spring boot config now pre-initializes the circuit breaker registry after the spring context has been scanned for annotations, which is merged with the properties configuration before initialization.
@olovandersson there is test failure in resilience4j-spring-boot:test FAILED , do u have the same locally ? |
Yes, I'm more used to building with maven, so I didn't notice that no tests were run when I ran the publishToMavenLocal task. Anyway, I just pushed a fix. |
You can use |
I review it when I'm back from vacation |
} | ||
|
||
private void findAnnotationConfig(Class<?> beanClass) { | ||
for (Method m : beanClass.getMethods()) { |
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.
here you assume the annotation will be present only only over methods , but it can be class level so you will miss the class level annotation in that case
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 hadn't time to look at the PR yet. I just wonder how do we solve the case when there are multiple annotations for the same CircuitBreaker instance, but with different exceptions configured?
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.
@Romeh Modified it to consider class-level annotations as well.
@RobWin The only reasonable behavior for conflicting annotation configurations would be to regard them as a programming error and throw an exception, so I implemented that. When it comes to external properties it is however consistent with spring in general to consider them as overrides.
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.
For the multiple annotations case in the same class , i guess we need to merge the configured exceptions from multiple annotations for the same instance , as the user maybe would like to define common ignored exception over the class itself the he can define more specific exceptions to be ignored over the methods itself , example of what i mean :
@CircuitBreaker(name = DummyService.BACKEND,recordExceptions = {IOException.class})
@Component
public class DummyServiceImpl {
@CircuitBreaker(name = DummyService.BACKEND,recordExceptions = {BussinessExcp.class})
public void doSomething(boolean throwBackendTrouble){
}
}
@RobWin what do u think ?
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 think that is reasonable within a class, but I don't think it should be allowed between different classes, as that probably is more likely to be a programming error. Should we search the class hierarchy as well?
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 think you can inject an ConfigurableEnvironment environment
into the BeanFactoryPostProcessor
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.
Interesting idea, I'll look into it.
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.
@RobWin The PropertySource/ConfigurableEnvironment worked as expected, so I've pushed that change now. Also fixed so non-conflicting annotations (that declare the same exceptions for the same name) are allowed. Tested it in the resilience4j-spring-cloud2-demo project as well.
Question: It's a bit unclear to me why there are both CIrcuitBreakerConfigurationProperties.getConfigs() and CircuitBreakerConfigurationProperties.getInstances()?
The annotations only set the instances properties at this point.
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 guess it's a hack and the Spring Boot guys will hate us for it ;)
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.
getConfigs()
returns a list of Configs was can be shared between instances.
For example:
resilience4j.circuitbreaker:
configs:
default:
slidingWindowSize: 100
permittedNumberOfCallsInHalfOpenState: 10
waitDurationInOpenState: 10000
failureRateThreshold: 60
eventConsumerBufferSize: 10
registerHealthIndicator: true
someShared:
slidingWindowSize: 50
permittedNumberOfCallsInHalfOpenState: 10
instances:
backendA:
baseConfig: default
waitDurationInOpenState: 5000
backendB:
baseConfig: someShared
getInstances()
returns the instance configurations which can also inherit from and overwrite shared configs
...a/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerAnnotationConfigScanner.java
Outdated
Show resolved
Hide resolved
@olovandersson Could you please check if the implementation also works when you use Spring Cloud Config and when you dynamically change a configuration property. We do have a demo for Spring Cloud Config -> https://github.com/resilience4j/resilience4j-spring-cloud2-demo |
...a/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerAnnotationConfigScanner.java
Outdated
Show resolved
Hide resolved
Also fixed so non-conflicting annotations can appear multiple times.
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.
Could you check indent config? tab and space are mix used.
...e4j/circuitbreaker/autoconfigure/CircuitBreakerAnnotationConfigScannerAutoConfiguration.java
Show resolved
Hide resolved
@@ -11,7 +13,7 @@ | |||
|
|||
@Configuration | |||
@ConditionalOnClass({Bulkhead.class, RefreshScope.class}) | |||
@AutoConfigureAfter(RefreshAutoConfiguration.class) | |||
@AutoConfigureAfter({CircuitBreakerAnnotationConfigScannerAutoConfiguration.class, RefreshAutoConfiguration.class}) |
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.
This is BulkheadAutoConfiguration
not for CircuitBreaker
. Please remove CircuitBreakerAnnotationConfigScannerAutoConfiguration
.
this.configurableEnvironment = configurableEnvironment; | ||
} | ||
|
||
public void setOrder(int order) { |
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 think it is not needed. If we want to change order, it would be better to get from CircuitBreakerProperties
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 think the Ordered inteface is needed so that this BeanFactoryPostProcessor is processed before others, or?
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.
Yes it is needed. But I mean setOrder()
is not needed. Like CircuitBreakerAspect
, we can set order with properties.
Line 23 in c9a6fa2
private int circuitBreakerAspectOrder = Ordered.LOWEST_PRECEDENCE - 2; |
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.
A user should not be able to reconfigure it. Otherwise it doesn't work anymore.
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.
yeah right. Then, we can change like below and remove setOrder()
method.
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
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 added the setOrder to add flexibility to the library. It might not be necessary, but BeanFactoryPostProcessors can do all sorts of stuff to your application context , and we cannot anticipate how every single use-case might affect one another. Many exising BeanFactoryPostProcessors have this ability. This way you give library users a bit of flexibility for instance in case there is some other BeanFactoryPostProcessor that they would want to affect this one. There are several built-in factories in Spring that has this flexibility. I would guess that 99 out of 100 users won't bother with this. Note that the default order is lowest, so it will get processed last.
We can't set the order with CircuitBreakerConfigurationProperties because this factory is in fact used to help populate those properties.
We could skip the order though, if I remember correctly, it works correctly without it. People who want to customize could then choose to subclass instead.
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 the Ordered interface as it wasn't really needed.
What's preferred in the project? I found existing source files with both tabs and spaces and mixed. So when I was trying to adapt, I didn't know what to choose. I usually use only spaces myself. |
Removed Ordered interface from CircuitBreakerAnnotationConfigScanner, it isn't needed and can be added by subclassing if required. Removed circuitbreaker config that got added to RefreshScopedBulkheadAutoConfiguration by mistake. Fixed indentation to only use spaces in some classes.
Changed it to spaces now. |
A question from my side. |
It's like before, the config file takes precedence over the annotations. I must admit that I had not thoroughly looked into why but I've done that now and it's because the ConfigFileApplicationListener, which is an EnvironmentPostProcessor initializes them before the Spring has started to create the ApplicationContext. Spring-cloud do special tricks in the PropertySourceBootstrapConfiguration class to insert the remote configuration in the correct location for the local properties list. So it works correctly there as well. |
@RobWin I've been thinking about one thing which isn't really an issue for this feature, but that's related. Currently the fallbackMethod gets invoked even for ignoreExceptions (which is sort of unexpected - this is not how the I think the hystrix behaviour is more natural, but at the same time existing resilence4j users might have started to depend on it's current behavior. As there probably are many users migrating from hystrix to resilience4j (especially since it's encouraged on the hystrix github page), it might be worth pointing out this difference in the documentation. |
Actually there are three type of exceptions.
A user can either define very fine granular which exceptions he wants to handle in fallback methods by defining very specific fallback method signatures. Or he can handle every exception in one fallback method. We thought it would be too confusing for the user to differiante between the three types. |
I thought we merged a PR which unwraps the cause from the InvocationException. But I think I understand now. You are rethrowing a checked exception from the fallback method. We might have to invoke |
Yes, basically. Except I got an UndeclaredThrowableException even though my exception was extending RuntimeException. Which I thought was very odd. Anyway, I can create a separate issue for that one. |
Yeah, I understand now why I got UndeclaredThrowableException. It's the InvocationTargetException of the fallback method that causes this. Anyway it's really easy to write a junit test for and fix it (we just have to catch the InvocationTargetException, unwrap and re-throw it). I've already done it locally. Is it ok if I include that fix in this pull request? |
A separate bugfix-PR might be faster to merge. |
I wonder how we can solve this in Ratpack, since the annotations are used in Spring Boot and Ratpack. |
I've been using Spring for more than a decade, but I haven't used Ratpack. Also I'll have a hard time justifying for my employer spending much time on it. I might be able to continue looking into this on my spare time, but it will be at a much lower pace. Any help from anyone more familiar with Ratpack would be much appreciated. Maybe some of the principles from the Spring solution can be re-used (like perhaps we could detect the annotations and merge the properties before the wiring starts)? |
@drmaas ? |
Hi, Either:
or
|
Ok, I can understand that. It would have been nice to get that feedback at an earlier stage though. But sometimes you have to dig into a problem to a problem to understand it's complexities. I do believe that there are additional benefits of having the exceptions (or predicates) close to the breaker annotations besides compile-time safety. Much of the configuration is down to trimming in values, whereas the exceptions could be tightly coupled to your business logic. So it makes sense to have it close to the code. But as I started thinking about ratpack issue, I also realized that it if the annotations should be able to do the things we talked about, they really need to be framework-specific and reside inside each framework module instead. Otherwise the development would always be held back by the maintenance of each framework (of which some could become unpopular after some time), as well as the abilities of the framework (since the framework provides the aspects for instance, not resilience4j). The customization thing sounds like a really good idea. Because I could for instance develop my own annotations extend the current aspect and hook in the stuff I developed here as an add-on. But with the customization possibilities, it would be possible to do it in a much cleaner way, so I hope you proceed with that one. |
Yes, sorry that you have invested so much time. But as you said, without it we wouldn't be smarter now. |
I got an idea now. Spring has a pattern of extending annotations, the Service annotation for instance is annotated with Component. And SpringBootApplication is annotated with SpringBootConfiguration, EnableAutoConfiguration etc. I could imagine that a common use case for resilience4j is that you want a specific set of aspects at multiple locations (for instance, some combination of Bulkhead and CircuitBreaker). What about adding support for such meta-annotations to the spring-module? They would then refer to the annotations in the existing resilience4j-annotations module. And maybe it would be possible to use that meta-annotation-support in combination with the Customizer functionality to enable custom configuration from such annotations? And to clarify, the idea isn't that the module should provide the annotations itself, but provide the support for writing such annotations in your project and plug them in in an easy way. |
I realize that #643 is about combining annotations. I do think it would make sense to consider whether you should put this in the generic annotation module because it will continue to lay a heavy burden on implementing the support in each supported framework. Other stuff in the library is built on top of the core, whereas those annotations are rather useless without different framework implementations. |
I've managed to find a solution that will work well even if annotations and circuit breakers created by code from the CircuitBreakerRegistry are mixed.