1. Overview
In this tutorial, we'll introduce the Spring Cloud Circuit Breaker project and learn how we can make use of it.
First, we're going to see what the Spring Cloud Circuit Breaker offers in addition to existing circuit breaker implementations. Next, we'll learn how to use the Spring Boot auto-configuration mechanism to integrate one or more circuit breakers into our application.
Note that we've got more information about what a circuit breaker is and how they work in Introduction to Hystrix, Spring Cloud Netflix Hystrix, and Guide to Resilience4j.
2. Spring Cloud Circuit Breaker
Until recently, Spring Cloud only provided us one way to add circuit breakers in our applications. This was through the use of Netflix Hystrix as part of the Spring Cloud Netflix project.
The Spring Cloud Netflix project is really just an annotation-based wrapper library around Hystrix. Therefore, these two libraries are tightly-coupled. This means we can't switch to another circuit breaker implementation without changing the application.
The Spring Cloud Circuit Breaker project solves this. It provides an abstraction layer across different circuit breaker implementations. It's a pluggable architecture. So, we can code against the provided abstraction/interface and switch to another implementation based on our needs.
For our examples, we'll focus only on the Resilience4J implementation. However, these techniques can be used for other plugins.
3. Auto Configuration
In order to use a specific circuit breaker implementations in our application, we need to add the appropriate Spring starter. In our case, let's use spring-cloud-starter-circuitbreaker-resilience4j:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> <version>1.0.2.RELEASE</version> </dependency>
Unfortunately, the Spring Cloud Circuit Breaker project is not yet part of the Spring Cloud BOM. It's only published in the Spring snapshots repository. Therefore, we need to specify it:
<repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/libs-snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>false</enabled> </releases> </repository> </repositories>
The auto-configuration mechanism configures the necessary circuit breaker beans if it sees one of the starters in the classpath.
If we wanted to disable the Resilience4J auto-configuration, we could set the spring.cloud.circuitbreaker.resilience4j.enabled property to false.
4. A Simple Circuit Breaker Example
Let's create a web application using Spring Boot to allow us to explore how the Spring Cloud Circuit Breaker library works.
We'll build a simple web service returning a list of albums. Let's suppose the raw list is provided by a third-party service. For simplicity, we'll use an external dummy API provided by Jsonplaceholder to retrieve the list:
https://jsonplaceholder.typicode.com/albums
4.1. Create a Circuit Breaker
Let's create our first circuit breaker. We'll start by injecting an instance of the CircuitBreakerFactory bean:
@Service public class AlbumService { @Autowired private CircuitBreakerFactory circuitBreakerFactory; //... }
Now, we can easily create a circuit breaker using the CircuitBreakerFactory#create method. It takes the circuit breaker identifier as an argument:
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuitbreaker");
4.2. Wrap a Task in a Circuit Breaker
In order to wrap and run a task protected by the circuit breaker, we need to call the run method which takes a Supplier as an argument.
public String getAlbumList() { CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuitbreaker"); String url = "https://jsonplaceholder.typicode.com/albums"; return circuitBreaker.run(() -> restTemplate.getForObject(url, String.class)); }
The circuit breaker runs our method for us and provides fault tolerance.
Sometimes, our external service could take too long to respond, throw an unexpected exception or the external service or host does not exist. In that case, we can provide a fallback as a second argument to the run method:
public String getAlbumList() { CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuitbreaker"); String url = "http://localhost:1234/not-real"; return circuitBreaker.run(() -> restTemplate.getForObject(url, String.class), throwable -> getDefaultAlbumList()); }
The lambda for the fallback receives the Throwable as an input, describing the error. This means we can provide different fallback results to the caller, based on the type of exception that triggered the fallback.
In this case, we won't take the exception into account. We'll just return a cached list of albums.
If the external call ends with an exception and no fallback is provided, a NoFallbackAvailableException is thrown by Spring.
4.3. Build a Controller
Now, let's finish our example and create a simple controller that calls the service methods and presents the results through a browser:
@RestController public class Controller { @Autowired private Service service; @GetMapping("/albums") public String albums() { return service.getAlbumList(); } }
Finally, let's call the REST service and see the results:
[GET] http://localhost:8080/albums
5. Global Custom Configuration
Usually, the default configuration is not enough. For this reason, we need to create circuit breakers with custom configurations based on our use cases.
In order to override the default configuration, we need to specify our own beans and properties in a @Configuration class.
Here, we're going to define a global configuration for all circuit breakers. For this reason, we need to define a Customizer<CircuitBreakerFactory> bean. So let's use the Resilience4JCircuitBreakerFactory implementation.
First, we'll define circuit breaker and time limiter configuration classes as per the Resilience4j tutorial:
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofMillis(1000)) .slidingWindowSize(2) .build(); TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofSeconds(4)) .build();
Next, let's embed the configuration in a Customizer bean by using the Resilience4JCircuitBreakerFactory.configureDefault method:
@Configuration public class Resilience4JConfiguration { @Bean public Customizer<Resilience4JCircuitBreakerFactory> globalCustomConfiguration() { // the circuitBreakerConfig and timeLimiterConfig objects return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .timeLimiterConfig(timeLimiterConfig) .circuitBreakerConfig(circuitBreakerConfig) .build()); } }
6. Specific Custom Configuration
Of course, we can have multiple circuit breakers in our application. Therefore, in some cases, we need a specific configuration for every circuit breaker.
Similarly, we can define one or more Customizer beans. Then, we can provide a different configuration for each one by using the Resilience4JCircuitBreakerFactory.configure method:
@Bean public Customizer<Resilience4JCircuitBreakerFactory> specificCustomConfiguration1() { // the circuitBreakerConfig and timeLimiterConfig objects return factory -> factory.configure(builder -> builder.circuitBreakerConfig(circuitBreakerConfig) .timeLimiterConfig(timeLimiterConfig).build(), "circuitBreaker"); }
Here we provide a second parameter, the id of the circuit breaker we're configuring.
We can also set up multiple circuit breakers with the same configuration by providing a list of circuit breaker ids to the same method:
@Bean public Customizer<Resilience4JCircuitBreakerFactory> specificCustomConfiguration2() { // the circuitBreakerConfig and timeLimiterConfig objects return factory -> factory.configure(builder -> builder.circuitBreakerConfig(circuitBreakerConfig) .timeLimiterConfig(timeLimiterConfig).build(), "circuitBreaker1", "circuitBreaker2", "circuitBreaker3"); }
7. Alternative Implementations
We've seen how to use the Resilience4j implementation to create one or more circuit breakers with Spring Cloud Circuit Breaker.
However, there are other implementations supported by Spring Cloud Circuit Breaker that we can leverage in our application:
It's worth mentioning that we can mix and match different circuit breaker implementations in our application. We're not just limited to one library.
The above libraries have more capabilities than we've explored here. However, Spring Cloud Circuit Breaker is an abstraction over only the circuit breaker part. For example, Resilience4j also provides other modules like RateLimiter, Bulkhead, Retry in addition to the CircuitBreaker and TimeLimiter modules used in this article.
8. Conclusion
In this article, we discovered the Spring Cloud Circuit Breaker project.
First, we learned what the Spring Cloud Circuit Breaker is, and how it allows us to add circuit breakers to our application.
Next, we leveraged the Spring Boot auto-configuration mechanism in order to show how to define and integrate circuit breakers. Also, we demonstrated how the Spring Cloud Circuit Breaker works through a simple REST service.
Finally, we learned to configure all circuit breakers together, as well as individually.
As always, the source code for this tutorial is available over on GitHub.