Spring cloud gateway with Resilience4j circuit breaker

In that post we will cover how to use resilience4j circuit breaker with spring cloud gateway for the back-end services behind the gateway by utilizing the following :

In that post we will show the case of how you can mix the usage of the Resilience4j spring boot starter and spring cloud circuit breaker starter so you can configure externally through spring configuration your circuit breakers definitions if you do not want to use the code configuration approach provided by Spring cloud circuit breaker starter through Customizers.

The whole code sample is in github .

First we are create spring cloud gateway application with one micro service route configuration which we will use to enable circuit breaker resilience over it by applying Resilience4j circuit breaker protection over it so we will cover the following points :

  • How to enable Resilience4j circuit-breaker in Spring cloud Gateway
  • How we can externally configure the defined Resilience4j circuit breaker
  • How we can test it by using Mock Server Test containers and the proper setup for it

How to enable Resilience4j circuit-breaker in Spring cloud Gateway:

You need to add the following dependencies to your spring cloud gateway application to enable Resilience4j circuit breaker integration

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

We have a sample back-end service rout configuration like the following which will be overridden in our test setup later :

spring:
  application:
    name: gateway-service
  output.ansi.enabled: ALWAYS
  cloud:
    gateway:
      routes:
        - id: test-service-withResilient4j
          uri: http://localhost:8091
          predicates:
            - Path=/testService/**
          filters:
            - RewritePath=/testService/(?<path>.*), /$\{path}

How we can externally configure the defined Resilience4j circuit breaker :

Now Spring cloud circuit breaker starter allow you to configure your Resilience4j circuit breaker definition through Customizer usage which is code first approach but what If you want to have it externally configured so you can control the configuration externally through distributed configuration service , here Resilience4j spring boot starter come to play which enable the external spring configuration of your circuit-breakers.

Spring boot starter of resilience4j will create CircuitBreakerRegistery bean based into your external configuration then you can inject it to the resilience4j factory of spring cloud starter to enable that integration and that is it!

Resilience4j external configuration will be like :

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowSize: 10
      minimumNumberOfCalls: 5
      permittedNumberOfCallsInHalfOpenState: 3
      automaticTransitionFromOpenToHalfOpenEnabled: true
      waitDurationInOpenState: 2s
      failureRateThreshold: 50
      eventConsumerBufferSize: 10
      recordExceptions:
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
      ignoreExceptions:
        - java.lang.IllegalStateException
    shared:
      slidingWindowSize: 100
      permittedNumberOfCallsInHalfOpenState: 30
      waitDurationInOpenState: 1s
      failureRateThreshold: 50
      eventConsumerBufferSize: 10
      ignoreExceptions:
        - java.lang.IllegalStateException
  instances:
    backendA:
      baseConfig: default
    backendB:
      slidingWindowSize: 10
      minimumNumberOfCalls: 10
      permittedNumberOfCallsInHalfOpenState: 3
      waitDurationInOpenState: 1s
      failureRateThreshold: 50
      eventConsumerBufferSize: 10

The integration between resilience4j loaded Circuit breaker registry and spring cloud ReactiveResilience4JCircuitBreakerFactory will be done as the following :

	@Bean
	public ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory(CircuitBreakerRegistry circuitBreakerRegistry) {
		ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory();
		reactiveResilience4JCircuitBreakerFactory.configureCircuitBreakerRegistry(circuitBreakerRegistry);
		return reactiveResilience4JCircuitBreakerFactory;
	}

How we can test it by using Mock Server Test containers and the proper setup for it :

Now it is time to test that integration and see how it works :

  • we will use mock server test containers to mock the target back end service reached out through the gateway
  • we will stub some responses
  • then trigger the test and monitor the logs to see how the ciruitbreaker react and off course you can get a lot of metrics through Resilience4j supported metrics exposure and events

Add the following dependencies to enable the usage of mock server

       <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>mockserver</artifactId>
            <version>1.12.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mock-server</groupId>
            <artifactId>mockserver-client-java</artifactId>
            <version>3.10.8</version>
            <scope>test</scope>
        </dependency>

Now you need to configure the test container starup plus injecting the custom configuration of the route after the startup of the mock server :

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@RunWith(SpringRunner.class)
@ContextConfiguration(initializers = {GatewayCircuitBreakerTest.Initializer.class})
public class GatewayCircuitBreakerTest {

	private static final Logger LOGGER = LoggerFactory.getLogger(GatewayCircuitBreakerTest.class);
	private static MockServerContainer mockServerContainer;

	static {
		mockServerContainer = new MockServerContainer();
		mockServerContainer.start();

	}

	static class Initializer
			implements ApplicationContextInitializer<ConfigurableApplicationContext> {
		public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
			TestPropertyValues.of(
					"spring.cloud.gateway.routes[0].id=test-service-withResilient4j",
					"spring.cloud.gateway.routes[0].uri=" + mockServerContainer.getEndpoint(),
					"spring.cloud.gateway.routes[0].predicates[0]=" + "Path=/testService/**",
					"spring.cloud.gateway.routes[0].filters[0]=" + "RewritePath=/testService/(?<path>.*), /$\\{path}",
					"spring.cloud.gateway.routes[0].filters[1].name=" + "CircuitBreaker",
					"spring.cloud.gateway.routes[0].filters[1].args.name=" + "backendA",
					"spring.cloud.gateway.routes[0].filters[1].args.fallbackUri=" + "forward:/fallback/testService"
			).applyTo(configurableApplicationContext.getEnvironment());
		}
	}

	private MockServerClient client = new MockServerClient(mockServerContainer.getContainerIpAddress(), mockServerContainer.getServerPort());
	@Autowired
	private TestRestTemplate template;

	@AfterClass
    public static void close(){
	    mockServerContainer.close();
    }
}


Finally you can trigger test of the protected endpoint in class GatewayCircuitBreakerTest and see it is going through Resilience4j circuit-breaker by analyzing the reported events in your console :

References:

3 comments

  1. Homeh how are you? Thanks for the article, there is very little spring gateway content with resilence4j. I downloaded your project and ran it without any changes. But all executions return 200, (I did not notice the fallback call). So I increased the time from 200ms to 1000ms, so I got an error message.

    21:04:42.642 — [ parallel-4] : CircuitBreaker ‘backendA’ recorded an exception as success:
    java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 1000ms in ‘circuitBreaker’ (and no fallback has been configured)

    can you help me?

    Like

  2. Hey Deluxeuo,

    you need to add the following 2 properties to make slow call detection enabled for ur circuit breaker configuration :

    slowCallDurationThreshold: 200ms
    slowCallRateThreshold: 30

    pushed and update on the code sample , example on the code is here : https://github.com/Romeh/spring-cloud-gateway-resilience4j/blob/master/src/main/resources/application.yml#L38

    Then u should see fallback messages printed out in that case with more than 200ms timeout

    Like

Leave a Reply