I have a simple microservices based app. It has 3 microservices. Movie-Catalog-Service, Ratings-Data-Service, Movie-Info-Service. Movie-Catalog-Service is dependent on Ratings-Data-Service and Movie-Info-Service. I have a getUserRating()
inside the UserRatingInfo
servcie class which is protected by a CircuitBreaker. When I deliberately have not started the Ratings-Data-Service and hit /catalog/10 I see:
java.lang.IllegalStateException: No instances available for ratings-data-service
instead of the fallback method being executed.
package com.example.discoveryserver2;
import .springframework.boot.SpringApplication;
import .springframework.boot.autoconfigure.SpringBootApplication;
import .springframework.cloudflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServer2Application {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServer2Application.class, args);
}
}
package com.example.moviecatalogservice2;
import .springframework.boot.SpringApplication;
import .springframework.boot.autoconfigure.SpringBootApplication;
import .springframework.cloud.client.loadbalancer.LoadBalanced;
import .springframework.context.annotation.Bean;
import .springframework.web.client.RestTemplate;
@SpringBootApplication
public class MovieCatalogService2Application {
public static void main(String[] args) {
SpringApplication.run(MovieCatalogService2Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
package com.example.movieinfoservice2;
import .springframework.boot.SpringApplication;
import .springframework.boot.autoconfigure.SpringBootApplication;
import .springframework.context.annotation.Bean;
import .springframework.http.client.HttpComponentsClientHttpRequestFactory;
import .springframework.web.client.RestTemplate;
@SpringBootApplication
public class MovieInfoService2Application {
public static void main(String[] args) {
SpringApplication.run(MovieInfoService2Application.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(3000);
return new RestTemplate();
}
}
package com.example.ratingsdataservice2;
import .springframework.boot.SpringApplication;
import .springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RatingsDataService2Application {
public static void main(String[] args) {
SpringApplication.run(RatingsDataService2Application.class, args);
}
}
package com.example.moviecatalogservice2.resources;
import java.util.List;
import java.util.stream.Collectors;
import .springframework.beans.factory.annotation.Autowired;
import .springframework.web.bind.annotation.PathVariable;
import .springframework.web.bind.annotation.RequestMapping;
import .springframework.web.bind.annotation.RestController;
import com.example.moviecatalogservice2.models.CatalogItem;
import com.example.moviecatalogservice2.models.UserRating;
import com.example.moviecatalogservice2.services.MovieInfo;
import com.example.moviecatalogservice2.services.UserRatingInfo;
@RestController
@RequestMapping("/catalog")
public class CatalogResource {
@Autowired
private MovieInfo movieInfo;
@Autowired
private UserRatingInfo userRatingInfo;
@RequestMapping("/{userId}")
public List<CatalogItem> getCatalog(@PathVariable("userId") String userId) {
UserRating userRating = userRatingInfo.getUserRating(userId);
return userRating.getRatings().stream()
.map(rating -> {
return movieInfo.getCatalogItem(rating);
})
.collect(Collectors.toList());
}
}
package com.example.moviecatalogservice2.services;
import java.util.Arrays;
import .springframework.beans.factory.annotation.Autowired;
import .springframework.stereotype.Service;
import .springframework.web.client.RestTemplate;
import com.example.moviecatalogservice2.models.Rating;
import com.example.moviecatalogservice2.models.UserRating;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
public class UserRatingInfo {
@Autowired
private RestTemplate restTemplate;
@CircuitBreaker(name = "ratings-data-service-cb", fallbackMethod = "getFallbackUserRating")
public UserRating getUserRating(String userId) {
UserRating userRating = restTemplate.getForObject("http://ratings-data-service/ratingsdata/user/{userId}", UserRating.class, userId);
return userRating;
}
public UserRating getFallbackUserRating(String userId, Exception exc) {
UserRating userRating = new UserRating();
userRating.setUserId(userId);
userRating.setRatings(Arrays.asList(new Rating("100", 0)));
return userRating;
}
}
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=";
xsi:schemaLocation=".0.0 .0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>movie-catalog-service-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>movie-catalog-service-2</name>
<description>Demo spring project for practicing Resilience4j</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<spring-cloud.version>2024.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties:
spring.application.name=movie-catalog-service
server.port = 8081
# This property allows us to specify the hostname that the service registers with Eureka.
eureka.instance.hostname = localhost
# Circuit Breaker for movie-info-service-cb
resilience4j.circuitbreaker.instances.movie-info-service-cb.sliding-window-type=count_based
resilience4j.circuitbreaker.instances.movie-info-service-cb.sliding-window-size=15
resilience4j.circuitbreaker.instances.movie-info-service-cb.minimum-number-of-calls=10
resilience4j.circuitbreaker.instances.movie-info-service-cb.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.movie-info-service-cb.slow-call-rate-threshold=90
resilience4j.circuitbreaker.instances.movie-info-service-cb.slow-call-duration-threshold=4000
resilience4j.circuitbreaker.instances.movie-info-service-cb.wait-duration-in-open-state=20000
resilience4j.circuitbreaker.instances.movie-info-service-cb.max-wait-duration-in-half-open-state=6000
resilience4j.circuitbreaker.instances.movie-info-service-cb.permitted-number-of-calls-in-half-open-state=3
# Circuit Breaker for ratings-data-service-cb
resilience4j.circuitbreaker.instances.ratings-data-service-cb.sliding-window-type=time_based
resilience4j.circuitbreaker.instances.ratings-data-service-cb.sliding-window-size=60
resilience4j.circuitbreaker.instances.ratings-data-service-cb.minimum-number-of-calls=10
resilience4j.circuitbreaker.instances.ratings-data-service-cb.failure-rate-threshold=40
resilience4j.circuitbreaker.instances.ratings-data-service-cb.slow-call-rate-threshold=60
resilience4j.circuitbreaker.instances.ratings-data-service-cb.slow-call-duration-threshold=2000
resilience4j.circuitbreaker.instances.ratings-data-service-cb.wait-duration-in-open-state=10000
resilience4j.circuitbreaker.instances.ratings-data-service-cb.max-wait-duration-in-half-open-state=6000
resilience4j.circuitbreaker.instances.ratings-data-service-cb.permitted-number-of-calls-in-half-open-state=3