Trying to create Retry object from application.yml - spring-boot

I'm relatively new to Spring-Boot + resilience4j and I'm trying to create a Retry object using the config in my .yml file. Currently I'm trying to decorate a Mono with very similar syntax to what is given in the docs:
Retry retry = Retry.of("backendName", sampleRetryConfig);
Mono.fromCallable(backendService::doSomething)
.transformDeferred(RetryOperator.of(retry))
In the above code snippet I'm explicitly declaring the sampleRetryConfig in the code and using that to create my Retry, but is there a way for me to create the Retry object using the RetryConfig pulled from my .yml file?
resilience4j.retry:
instances:
apiRetry:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: true
ignoreExceptions:
- example.exceptions
Support seems to be there for using the #Retry annotation, but I haven't found anything about support for what I'm trying to do.

Late answer but here's what I ended up doing. Using the RetryRegistry and declaring the Retry objects as beans I was able to make it work.
Here's the contents of the .yml
resilience4j:
retry:
api-path:
maxAttempts: 1
waitDuration: 1s
ignoreExceptions:
And the class where the retry beans were created:
private final RetryRegistry retryRegistry;
#Bean
public Retry apiPathRetry() {
return retryRegistry.retry("api-path");
}
Then finally, using the objects in a class.
return Mono.fromSupplier(() -> method)
.flatMap(genericData-> {business logic})
.transformDeferred(RetryOperator.of(apiPathRetry));

Related

How to enable deliveryAttemptHeader to get DefaultErrorHandler retry count when using spring cloud stream kafka

I am using spring-cloud-stream-binder-kafka and have implemented stateful retry using DefaultErrorHandler, I found that by enabling deliveryAttemptHeader of container properties I can access the retry count or deliveryAttempt count from message header but I am not able to enable it.
I tried to set the value to true as below
#Bean
public ContainerCustomizer<String, Message, ConcurrentMessageListenerContainer<String, Message>> containerCustomizer(
ConcurrentKafkaListenerContainerFactory<String, Message> factory) {
ContainerCustomizer<String, Message, ConcurrentMessageListenerContainer<String, Message>> custCustomizer = container -> {
container.getContainerProperties().setDeliveryAttemptHeader(true);
};
factory.setContainerCustomizer(custCustomizer);
return custCustomizer;
}
With this configuration when I start the application and debug KafkaMessageListenerContainer.java#L1078 I still see that deliveryAttemptHeader is disabled, and also the ContainerCustomizer instance that I have created is also not getting called.
spring-cloud-stream does not use Boot's container factory; use its ListenerContainerCustomizer instead.
https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#_advanced_consumer_configuration

Change timeout for Spring Cloud Circuit Breaker at runtime?

I'm using Spring Cloud Circuit Breaker 2.0.0 (resilience4j implementation) for circuit breaking and timeouts in my application. I've created the following configuration:
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory ->
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build())
.circuitBreakerConfig(..)
.build());
}
Now I want to write an integration test to verify that my behavior is correct when a timeout occurs. For this to work, I'd like to temporarily change the timeout duration specified in the configuration above to something like 1 millisecond instead of 10 seconds.
So my question is: How can I change the value of the timeout of the TimeLimiterConfig(temporarily) when I'm writing a Spring Boot integration test?
You can use the #Value Spring annotation that retrieves the value at a configuration file from your resource folder src/main/resources/common.properties.
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer(
#Value("${duration.milli:600}") int durationMilli) {
return factory ->
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMilli(durationMilli)).build())
.circuitBreakerConfig(..)
.build());
}
Then you set the value at src/main/resources/common.properties
duration.milli=600
When you are doing your test you can configure another resource file at the test folder src/test/resources/common.properties with a different value.
duration.milli=1

Using TestChannelBinderConfiguration but two handlers get registered

I have a Spring Cloud Stream application that processes (credit card) events -- some of which are processed synchronously and some asynchronously. I came up with roughly the following in Kotlin:
spring-cloud-starter-stream-rabbit = 3.1.0
#Service
class CardEventProcessor(
private val streamBridge: StreamBridge,
) : Consumer<AsyncCardEvent> {
fun process(cardEvent: SyncCardEvent): Result { return businessLogic() }
fun processAsynchronously(cardEvent: AsyncCardEvent) {
streamBridge.send("cardEventProcessor-out-0", cardEvent)
}
override fun accept(cardEvent: AsyncCardEvent) { businessLogic() }
}
and have configured it like so:
rabbitmq: ...
cloud:
stream:
bindings:
cardEventProcessor-in-0:
destination: cardevents
group: CardEventProcessor
cardEventProcessor-out-0:
destination: cardevents
It all seems to work fine, except in integration tests the processing fails after the second async card event. I was able to debug / reduce the issue down to two handlers being registered in UnicastingDispatcher, which has a round robin strategy: one for TestChannelBinder and another for OutputDestination$lamdba.
This is what my integration test class looks like:
#SpringBootTest
#Transactional
#AutoConfigureMockMvc
#AutoConfigureEmbeddedDatabase
#Import(TestChannelBinderConfiguration::class)
class IntegrationTests {
#Test
fun `Use case one`() {
sendFirstAsyncRequest() // processed correctly in CardEventProcessor.accept()
sendSecondAsyncRequest() // message never arrives in CardEventProcessor.accept()
}
}
I was following the Testing section in Spring cloud stream docs and can't figure out what I'm missing to get this working. The example there is a Function<> not a Consumer<> and I produce within the same #Service class as the consumer (because the queue is just an implementation detail to solve async, not the typical use case of queues between micro-services) but as far as I understand that should work, and does in fact work when not running as integration test.
I saw Disable Spring Cloud Stream Rabbit for tests but didn't want to depend on the deprecated spring-cloud-stream-test-support and the other two suggestions didn't work either. Any ideas?

Get Instance of circuit breaker from configuration file

This is my configuration file.
resilience4j.circuitbreaker:
instances:
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 10
permittedNumberOfCallsInHalfOpenState: 3
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
I am trying to create bean of my feign client using Resilience4jFeign but the circuit breaker object is initialized with default configuration as the name suggest CircuitBreaker.ofDefaults. I can't find any ways to get my instance of circuit breaker from configuration to an object.
#Bean
public CommunicationServiceProxy communicationServiceProxy(){
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendB");
FeignDecorators decorators = FeignDecorators.builder()
.withCircuitBreaker(circuitBreaker)
.withFallbackFactory(CommunicationFallBack::new)
.build();
return Resilience4jFeign
.builder(decorators)
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(CommunicationServiceProxy.class, "http://localhost:8081");
}
I found the solution and it was using the CircuitBreakerRegistry.
Instead of creating registry using
CircuitBreakerRegistry.ofDefaults()
one need to autowired them.
#Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;

How to configure LoggingMeterRegistry step duration in Spring Boot 2.x?

I am trying to configure the LoggingMeterRegistry to log metrics for my Spring Boot 2.1.6 application. I want the metrics to be logged every hour.
In my application.yml, I've the following configured
management:
metrics:
export:
logging:
enabled: true
step: 60m
But in the logs I see the metrics being logged every minute. I've tried the other variation for the property key as well e.g.
management.metrics.export.logging:
enabled: true
step: 60m
I have also tried various formats for the duration string e.g. 1h, PT60M but with no success. The metrics are logged at 1 minute intervals.
I was looking at the code here StepDurationConverterTest and here StepDurationConverter that converts the step duration String to a Duration object and looks like both formats 60m and 1h should work.
Any ideas why I can't seem to change the logging interval?
I think the problem here is there's no
org.springframework.boot.actuate.autoconfigure.metrics.export.logging
package like there is for other MeterRegistrys (eg org.springframework.boot.actuate.autoconfigure.metrics.export.jmx).
Ie theres no auto configuration for the properties in Spring Boot. This is probably because the LoggingMeterRegistry is marked as #Incubating
You need to manually configure the LoggingMeterRegistry as a bean and create your own #ConfigurationProperties LoggingProperties and LoggingPropertiesConfigAdapter to get this to work. Or just hardcode the step period you want.
To configure step count duration in micrometer:
Please follow below step:
#Configuration
public class LoggingMeterRegistryConfig {
#Bean
public LoggingMeterRegistry loggingMeterRegistry() {
LoggingRegistryConfig config = new LoggingRegistryConfig() {
#Override
public String get(String s) {
return null;
}
#Override
public Duration step() {
return Duration.ofMinutes(2);
}
};
return LoggingMeterRegistry.builder(config).clock(Clock.SYSTEM).threadFactory(new NamedThreadFactory("logging-metrics-publisher")).build();
}
}
The following #Bean supplies config from Spring Environment allowing you to specify a property logging.step: 1h to get your desired period.
#Bean
LoggingMeterRegistry loggingMeterRegistry(Environment env) {
LoggingRegistryConfig springBasedConfig = prop -> env.getProperty(prop, String.class);
return new LoggingMeterRegistry(springBasedConfig, Clock.SYSTEM);
}

Resources