Session 6.2: Resilient microservices patterns
You will use the project you cloned for this session (same as Session 6.1)
If you want to create a new Spring Boot project that implements resilient microservices patterns from scratch, you need to install the following dependencies for both the Product and Inventory services:
- Spring Web
- Resilience4j
- starter actuator
- aop
For the last, you may need to add it manually
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
Implementing the Circuit Breaker pattern
We will implement a circuit breaker for the request from the product-service
to inventory-service
. A simple diagram of implementing the circuit breaker is shown in the following figure:
To keep the code changes traceable and facilitate the comprehension of the circuit breaker pattern, we have created a new request handler in ProductController.java
, to which we have applied the @CircuitBreaker
annotation and we have also defined a corresponding fallback method that returns a default object in case the request fails.
... // -------------------------------- Circuit Breaker /* @GetMapping("/products/quantityCB/{id}") @CircuitBreaker(name = "inventory", fallbackMethod = "fallbackQuantityCB") public Optional<ProductQuantityDto> getProductWithQuantityDelay(@PathVariable String id){ return productService.getProductWithQuantityCB(id); } public Optional<ProductQuantityDto> fallbackQuantityCB(Exception e) { log.info("We are not able to fetch the quality of the product"); return Optional.of(new ProductQuantityDto("00", "p-000-00", "type of product", "description of product", BigDecimal.valueOf(0), 0 )); } */ // -------------------------------- Circuit Breaker ...
We have also defined a new function to handle it in ProductService.java
. It is the same as the getProductWithQuantity
function.
... // -------------------------------- Circuit Breaker /* public Optional<ProductQuantityDto> getProductWithQuantityCB(String id){ Optional<Product> product = productRepository.findById(id); return product.map(this::mapToProductQuantityDto); */ // -------------------------------- Circuit Breaker ...
1. Uncomment the aforementioned request handler in ProductController.java
and the function in ProductService.java
.
2. Check the configuration of the circuit breaker in the application.properties
of the product-service
. They are annotated to facilitate your understanding.
3. Run all your services if they are not already running, you also need to rerun the product-service
since it was modified.
4. Visit http://localhost:8761/ to make sure that your services are running and requested with Eureka.
5. Send the following requests, in RestClientFile.rest
. The first will invoke a request handler that does not implement a circuit breaker, but the second is implementing a circuit breaker to be activated in case the request fails. However, since the inventory-service
is running and the request will not fail, both of these requests should work and return the same result.
### Get a product with quantity no CB -> produce exception GET http://localhost:8082/api/products/quantity/01 ### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01
Note: if your API is running and the route to both product-service
and inventory-service
are registered with it, you can use the 8080 port instead of 8082.
6. Visit http://localhost:8082/actuator/health to check the status of your circuit breaker. It should be CLOSED (working normally).
7. Stop the inventory-service
, and now any request to it should fail.
8. Resend the following request, the request will fail, which will trigger the fallback function and returns the default message in case of failure. We are using a default object for that instead of sending a string message.
### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01
9. Visit http://localhost:8082/actuator/health to check the status of your circuit breaker. Did its status change? How many requests do we need to send to change its status to OPEN/HALF_OPEN? Try to send the same request several times and monitor its status by visiting http://localhost:8082/actuator/health (you need to refresh the page).
10. Resend the following request, just to compare how your application will respond when you do not use a circuit breaker for requests that may fail.
### Get a product with quantity no CB -> produce exception GET http://localhost:8082/api/products/quantity/01
11. Rerun the inventory-service
and wait 30 seconds until it registers itself with Eureka.
12. Resend the following request, how many requests do we need to send to change its status to CLOSED?
### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01
Implementing the Time Limiter pattern
Unlike the Circuit Breaker pattern, the time limiter pattern will not wait for the request to fail, but it will wait a defined time until it terminates the request and triggers a fallback method.
1. Comment out the request handler in ProductController.java
and the function in ProductService.java
related to the Circuit Breaker, and uncomment the request handler in ProductController.java
and the function in ProductService.java
related to the Circuit Breaker and TimeLimiter.
2. Check the code of the request handler and its associated function, it is annotated.
3. Check the configuration of the TimeLimiter in the application.properties
of the product-service
.
4. Run all your services if they are not already running, you also need to rerun the product-service
since it was modified.
5. Resend the following request, the request will not fail this time but it will be terminated by the TimeLimiter and the fallback function will be triggered, which will return the default object.
### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01
Implementing the Retry pattern
1. Check the configurations of the Retry in the application.properties
of the product-service
.
2. Comment out the request handler in ProductController.java related to the Circuit Breaker + TimeLimiter, and uncomment the request handler in ProductController.java related to Retry. You can use the same function in ProductService.java for Retry, i.e., you do not need to modify anything in ProductService.java for this step.
3. Run all your services if they are not already running, you also need to rerun the product-service
since it was modified.
4. Send the following request, and keep your eyes on the terminal of the products-service
, which will show the number of retry attempts and the time they were triggered.
### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01
Implementing the Rate Limiter pattern
1. Check the configurations of the Rate Limiter pattern in the application.properties
of the product-service
.
2. Comment out the request handler in ProductController.java related to Retry, and uncomment the request handler in ProductController.java related to Rate Limiter. Comment out the Thread.sleep(8000);
in the function in ProductService.java, as follows:
public Optional<ProductQuantityDto> getProductWithQuantityCB(String id){ Optional<Product> product = productRepository.findById(id); //Thread.sleep(8000); // just to cause a delay of 8 seconds return product.map(this::mapToProductQuantityDto); }
3. Run all your services if they are not already running, you also need to rerun the product-service
since it was modified.
4. Send the following request several times, and keep your eyes on the terminal, which will show that only two requests can be sent within 5 seconds. Then, you have to wait for the next time period (also 5 seconds) to start to be able to send the request again.
### Get a product with quantity with CB returns the default response GET http://localhost:8082/api/products/quantityCB/01