- This documentation is created for reference from the Spring cloud POC we have developed in this turotial
| Application | Port | Info |
|---|---|---|
| Limits Microservice | 8080, 8081, etc. | POC app developed |
| Spring Cloud Config Server | 8888 | It is default port for config srver as per spring docs. |
| Currency Exchange Microservice | 8000, 8001, 8002, etc. | POC app developed |
| Currency Conversion Microservice | 8100, 8101, 8102, etc. | POC app developed |
| Netflix Eureka | 8761 | Naming Server |
| API Gateway | 8765 | Entry point to all external requests. |
| Zipkin | 9411 | Distributed Tracing Server |
Purpose / Feature
Steps
<microservice-name>[-<env>].propertiesspring-cloud-config-server in pom.xml.@EnableConfigServer annotation on SpringBoot main App to enable application as config server.spring.cloud.config.server.git.uri=https://github.com/SRVivek1/spring-cloud-config-server-git-repo.gitMaven / External dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
Code changes
import org.springframework.cloud.config.server.EnableConfigServer; /* Enable config server. */
@EnableConfigServer
@SpringBootApplication
public class B2SbootCloudConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(B2SbootCloudConfigServerApplication.class, args);
}
}
spring.application.name=b2-sboot-cloud-config-server
# Server port for config server
server.port=8888
# Start: Git repo config
# Github.com
spring.cloud.config.server.git.uri=https://github.com/SRVivek1/spring-cloud-config-server-git-repo.git
# local git
#spring.cloud.config.server.git.uri=file:///path/to/git/spring-cloud-config-server-git-repo
# windows
#spring.cloud.config.server.git.uri=file:///c:/path/to/git/spring-cloud-config-server-git-repo
# End: Git repo config
<microservice-name>[-<env>].properties in the base directory. # limits-service properties for differnt environments
./limits-service.properties
./limits-service-dev.properties
./limits-service-qa.properties
./limits-service-prod.properties
# currency-exchange-service default property
./currency-exchange-service.properties
# properties in limit-service default properties
limits-service.properties:limits-service.minimum=8
limits-service.properties:limits-service.maximum=888
# properties in limit-service DEV properties
limits-service-dev.properties:limits-service.minimum=1
limits-service-dev.properties:limits-service.maximum=111
# properties in limit-service QA properties
limits-service-qa.properties:limits-service.minimum=3
limits-service-qa.properties:limits-service.maximum=333
# properties in limit-service PROD properties
limits-service-prod.properties:limits-service.minimum=6
limits-service-prod.properties:limits-service.maximum=666
Notes:
@EnableConfigServer annotation:
References:
Purpose / Feature
Steps
spring-cloud-starter-configdependency in POM.xml.prefix property in annotation as microservice-name.spring.cloud.config.name=limtis-servicespring.config.import=optional:configserver:http://localhost:8888/Maven / External dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
Code changes
configurationProperties class.
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "limits-service")
@Component
public class LimitsServiceConfiguration {
// it will match to property named - limits-service.minimum
private int minimum;
// it will match to property named - limits-service.maximum
private int maximum;
// constructors, getter-setters.
}
None specific to config client. @RestController
public class LimitsServiceController {
@Autowired
LimitsServiceConfiguration limitsServiceConfiguration;
/**
* Read property value from application.properties.
* @return
*/
@GetMapping("/limits")
public Limits retrieveLimits() {
return new Limits(limitsServiceConfiguration.getMinimum(), limitsServiceConfiguration.getMaximum());
}
}
public class Limits {
private int minimum;
private int maximum;
public Limits(int minimum, int maximum) {
super();
this.minimum = minimum;
this.maximum = maximum;
}
//getter-setters & other methods
}
#spring.profiles.active=dev,qa
# Start: Cloud config client
# Mention profile for default use.
# spring.cloud.config.profile=default
spring.cloud.config.profile=dev
#spring.cloud.config.profile=qa
# Provide name for this services, used to match for available properties file.
# for dev profile it will check the file name - limits-service-dev.properties in config. server.
spring.cloud.config.name=limits-service
# this part is mandatory if config client dependency is added in POM to start the app.
#spring.config.import=optional:configserver:
# 8888 - it's default port for config server
spring.config.import=optional:configserver:http://localhost:8888/
# To enable/disable remote configuration, by default is enabled
#spring.cloud.config.enabled=false
# End: Cloud config client
# Start: local service configuration
limits-service.minimum=20
limits-service.maximum=21
app-config.minimum=10
app-config.maximum=11
# End: local service configuration
Notes:
spring.config.import property.application.properties: spring.config.import=optional:configserver:
spring.cloud.config.uri or add the url to the spring.config.import statement such as, spring.config.import=optional:configserver:http://myhost:8888.spring.config.import.References:
spring-boot-starter-webRestTemplateBuilder to build RestTemplate object.restTemplate.getForEntity("http://localhost:8000/jpa/currency-exchange/from/{from}/to/{to}", CurrencyConversion.class, uriVariables); <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
RestTemplate @Configuration
public class RestTemplateConfiguration {
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
import org.springframework.web.client.RestTemplate; @RestController
public class CurrencyConversionController {
private static final Logger logger = LoggerFactory.getLogger(CurrencyConversionController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("/currency-conversion/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversion(@PathVariable String from, @PathVariable String to,
@PathVariable BigDecimal quantity) {
logger.info("Executing CurrencyConversionController.calculateCurrencyConversion(..) API.");
// Standardize
from = from.toUpperCase();
to = to.toUpperCase();
final Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("from", from);
uriVariables.put("to", to);
// Send request to Currency exchange micro-service
// final ResponseEntity<CurrencyConversion> response = new RestTemplate().getForEntity(
final ResponseEntity<CurrencyConversion> response = restTemplate.getForEntity(
"http://localhost:8000/jpa/currency-exchange/from/{from}/to/{to}", CurrencyConversion.class,
uriVariables);
final CurrencyConversion currencyConversion = response.getBody();
logger.debug("Response from currency-exchange : {}", currencyConversion);
currencyConversion.setQuantity(quantity);
currencyConversion.setTotalCalculatedAmount(quantity.multiply(currencyConversion.getConversionMultiples()));
logger.debug("Response returned : {}", currencyConversion);
return currencyConversion;
}
}
import java.math.BigDecimal; public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiples;
private BigDecimal quantity;
private BigDecimal totalCalculatedAmount;
private String environment;
// contructors, setter-getters
}
Note: When using
RestTemplatewe have to write boiler plate code to call rest service. To overcome this, we can use another spring dependencyspring-cloud-openfeign.
RestTemplate to invoke a rest service.@EnableFeignClients.@FeignClient and define a method signature as in target rest service.
@FeignClient(name = "b3-currency-exchange-service")@FeignClient(name = "b3-currency-exchange-service", url = "localhost:8000")@Autowired it.
private CurrencyExchangeProxy currencyExchangeProxy;final CurrencyConversion currencyConversionExchange = currencyExchangeProxy.retrieveExchangeRateFromDatabase(from, to); <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(via org.springframework.cloud.openfeign.FeignClient @FeignClient).org.springframework.context.annotation.Configuration @Configuration classes.import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients // Enables feign clients in application
@SpringBootApplication
public class B4CurrencyConversionServiceApplication {
public static void main(String[] args) {
SpringApplication.run(B4CurrencyConversionServiceApplication.class, args);
}
}
currency-exchange service, to support auto population of response data.import java.math.BigDecimal; public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiples;
private BigDecimal quantity;
private BigDecimal totalCalculatedAmount;
private String environment;
// constructors, setter-getters.
}
import org.springframework.cloud.openfeign.FeignClient; @FeignClient(name = "b3-currency-exchange-service", url = "localhost:8000")
public interface CurrencyExchangeProxy {
/**
* Method as defined in the host service.
* @param from
* @param to
* @return
*/
@GetMapping("/jpa/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeRateFromDatabase(@PathVariable String from, @PathVariable String to);
}
import org.springframework.cloud.openfeign.FeignClient; @RestController
public class CurrencyConversionController {
private static final Logger logger = LoggerFactory.getLogger(CurrencyConversionController.class);
@Autowired
private CurrencyExchangeProxy currencyExchangeProxy;
/**
* Currency conversion using feign client.
* @param from
* @param to
* @param quantity
* @return
*/
@GetMapping("/currency-conversion-feign/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversionFeign(@PathVariable String from, @PathVariable String to,
@PathVariable BigDecimal quantity) {
logger.info("Executing CurrencyConversionController.calculateCurrencyConversionFeign(..) API.");
// Standardize
from = from.toUpperCase();
to = to.toUpperCase();
// Send request to Currency exchange micro-service
final CurrencyConversion currencyConversionExchange = currencyExchangeProxy
.retrieveExchangeRateFromDatabase(from, to);
logger.debug("Response from currency-exchange : {}", currencyConversionExchange);
final CurrencyConversion currencyConversion = new CurrencyConversion(currencyConversionExchange.getId(), from,
to, quantity, currencyConversionExchange.getConversionMultiples(),
quantity.multiply(currencyConversionExchange.getConversionMultiples()),
currencyConversionExchange.getEnvironment());
logger.debug("Response returned : {}", currencyConversionExchange);
return currencyConversion;
}
}
Note: The
CurrencyConversion.javabean has all the properties which will be returned from `currency-exchange' service.
/ context. http://localhost:8761/spring-cloud-starter-netflix-eureka-server.application.properties
eureka.client.register-with-eureka=falseeureka.client.fetch-registry=false <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer
@SpringBootApplication
public class B6NamingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(B6NamingServiceApplication.class, args);
}
}
spring.application.name=b6-naming-service
server.port=8761
# Eureka : start
# Indicates whether or not this instance should register its information with eureka server for discovery by others.
eureka.client.register-with-eureka=false
# Indicates whether this client should fetch eureka registry information from eureka server.
eureka.client.fetch-registry=false
# when guessing a hostname, the IP address of the server should be used in preference to the hostname reported by the OS
# eureka.instance.prefer-ip-address=true
# The hostname if it can be determined at configuration time (otherwise it will be guessed from OS primitives).
# eureka.instance.hostname=localhost
# Eureka : end
eureka.client.service-url.defaultZone=http://localhost:8761/eureka <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
</dependency>
# Start: Eureka client config
# Map of availability zone to list of fully qualified URLs to communicate with eureka server.
# Each value can be a single URL or a comma separated list of alternative locations.
# Typically the eureka server URLs carry protocol,host,port,context and version information if any.
# Example: https://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# End: Eureka client config
Note: If
Eureka clientdependency is present inPOM.xml, spring will automatically try to register this service to Naming server by looking forEureka Serveron it's defaultEureka port - 8761.
Purpose / Feature
Steps
spring-cloud-starter-loadbalancer dependency in POM.xml.application.propeties.annotation and remove url property.Maven / External dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Code / Config changes
import org.springframework.cloud.openfeign.FeignClient;@FeignClient with only service name. //@FeignClient(name = "b3-currency-exchange-service", url = "localhost:8000")
/* Find service details from name server using service name. */
@FeignClient(name = "b3-currency-exchange-service")
public interface CurrencyExchangeProxy {
/**
* Method as defined in the host service.
* @param from
* @param to
* @return
*/
@GetMapping("/jpa/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeRateFromDatabase(@PathVariable String from, @PathVariable String to);
}
Application Config: application.properties
# load balancer
logging.level.com.netflix.discovery=debug
# Start: Eureka client config
# Map of availability zone to list of fully qualified URLs to communicate with eureka server.
# Each value can be a single URL or a comma separated list of alternative locations.
# Typically the eureka server URLs carry protocol,host,port,context and version information if any.
# Example: https://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# Enables the Eureka health check handler.
eureka.client.healthcheck.enabled=true
eureka.instance.lease-renewal-interval-in-seconds=60
eureka.instance.lease-expiration-duration-in-seconds=60
# End: Eureka client config
# Start: Spring Load Balancer
# Enable load balancer.
spring.cloud.loadbalancer.enabled=true
# Enables LoadBalancer retries.
spring.cloud.loadbalancer.retry.enabled=true
# End: Spring Load Balancer
# Start: Cloud config client
# by default it's enabled
spring.cloud.config.enabled=true
# Name of the service to be shared wit config server
spring.cloud.config.name=currency-conversion-service
# default active profile
spring.cloud.config.profile=dev
# 8888 - it's default port for config server
spring.config.import=optional:configserver:http://localhost:8888
# End: Cloud config client
Note: With service name it finds the server details from
Eureka Server.spring-load-balanceris mandatory dependency with feign client.
application.properties <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring.application.name=b7-api-gateway
server.port=8765
# Start: Eureka client config
# Map of availability zone to list of fully qualified URLs to communicate with eureka server.
# Each value can be a single URL or a comma separated list of alternative locations.
# Typically the eureka server URLs carry protocol,host,port,context and version information if any.
# Example: https://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# Enables the Eureka health check handler.
eureka.client.healthcheck.enabled=true
eureka.instance.lease-renewal-interval-in-seconds=60
eureka.instance.lease-expiration-duration-in-seconds=60
# End: Eureka client config
# Start: Api Gateway
# Flag that enables Discovery Client gateway integration.
# This allows us to invoke service using service-name registered in Eureka
# http://localhost:8765/B3-CURRENCY-EXCHANGE-SERVICE/currency-exchange/from/usd/to/inr
spring.cloud.gateway.discovery.locator.enabled=true
# Option to lower case serviceId in predicates and filters, defaults to false.
# Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
# End: Api Gateway
Note: This is an important note.
Notes:
disabled. It need to be enabed explicitly in application.properties. Check above config for details.References:
Routers and Filters provides the option to intercept and process the requests.method, header, cookie, path, host etc..section 8 above for API Gateway coniguration.application.properties.
gateway discovery locator configuration in application.properties to auto discover clients from Eureka server.o.s.c.gateway.route.RouteLocator class with method accepting RouteLocatorBuilder class as argument.RouteLocatorBuilder instance, we can customize routes, e.g.
lb://<service-name-in-eureka> to redirect and do load-balancing.HTTPS requests, requires SSL confguration. <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
import org.springframework.cloud.gateway.route.RouteLocator;import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder.Builder;Bean of RouteLocator class. /**
* Configuration class to build custom routes and request customization.
*/
@Configuration
public class ApiGatewayConfiguration {
/**
* Define routes.
* @param routeLocatorBuilder
* @return
*/
@Bean
RouteLocator getwayRoute(RouteLocatorBuilder routeLocatorBuilder) {
/*
* Redirect request to external service. Also, optionally we can add http
* headers and request params in the request.
*/
Builder routeLocator = routeLocatorBuilder.routes()
.route((p) -> p.path("/get")
.filters(f -> f.addRequestHeader("MY-HEADER", "MY-CUSTOM-HEADER")
.addRequestParameter("MY-REQUEST-PARAM", "MY-CUSTOM-REQUEST-PARAM"))
.uri("http://httpbin.org:80/"));
/**
* Route URLs to load balancer and eureka service name
*/
routeLocator = routeLocator.route(p -> p.path("/currency-exchange/**").uri("lb://b3-currency-exchange-service"))
.route(p -> p.path("/currency-conversion-feign/**")
.uri("lb://b5-currency-conversion-service-openfeign"));
/**
* Rewrite URL and copy the path
*/
routeLocator.route(p -> p.path("/ccfs/**")
.filters(f -> f.rewritePath("/ccfs/(?<segment>.*)", "/currency-conversion-feign/${segment}"))
.uri("lb://b5-currency-conversion-service-openfeign"));
return routeLocator.build();
}
}
spring.application.name=b8-api-gateway-routes
server.port=8765
# Start: Eureka client config
# Map of availability zone to list of fully qualified URLs to communicate with eureka server.
# Each value can be a single URL or a comma separated list of alternative locations.
# Typically the eureka server URLs carry protocol,host,port,context and version information if any.
# Example: https://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# Enables the Eureka health check handler.
eureka.client.healthcheck.enabled=true
eureka.instance.lease-renewal-interval-in-seconds=60
eureka.instance.lease-expiration-duration-in-seconds=60
# End: Eureka client config
# Start: Api Gateway
# Flag that enables Discovery Client gateway integration.
# This allows us to invoke service using service-name registered in Eureka
# http://localhost:8765/B3-CURRENCY-EXCHANGE-SERVICE/currency-exchange/from/usd/to/inr
# Disbling - to use Routes and Filters
#spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.enabled=false
# Option to lower case serviceId in predicates and filters, defaults to false.
# Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
# Disbling - to use Routes and Filters
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=false
# Option to lower case serviceId in predicates and filters, defaults to false.
# Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
# spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
# End: Api Gateway
Note: When using routes in API Gateway, eureka discover client must be disabled in
application.properties.
section 8 & section 9 above for API Gateway coniguration.o.s.c.g.filter.GlobalFilter class.filter(....) method and add the business logic. <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
import some.dependent.resourceimport org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;o.s.c.g.filter.GlobalFilter class and filter(...) method. @Component
public class LoggingGlobalFilter implements GlobalFilter {
private static Logger logger = LoggerFactory.getLogger(LoggingGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// log the request
logger.info("Request -> Method: {}, Path: {}", exchange.getRequest().getMethod(),
exchange.getRequest().getPath());
return chain.filter(exchange);
}
}
Resilience4j is a replacement of netflix-hystrix for circuit breaker framework.POM.xml.
org.springframework.boot:spring-boot-starter-actuatororg.springframework.boot:spring-boot-starter-weborg.springframework.boot:spring-boot-starter-aopio.github.resilience4j:resilience4j-spring-boot2:2.2.0@Retry(name = "default").
reties for the API with default configuration with max attempt 3.reties.
@Retry(name = "b9-cb-retries").application.properties to control max retries.
resilience4j.retry.instances.b9-cb-retries.maxAttempts=5 #NEW#resilience4j.retry.instances.b9-cb-retries.maxRetryAttempts=5 #OLDapplication.properties to wait duration between each retries, in this example - 1 seconds.
resilience4j.retry.instances.b9-cb-retries.wait-duration=1sresilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true@Retry(name = "default", fallbackMethod="hardcodedResponse")hardcodedResponse with method argument accepting Throwable intance.fallback for different APIs. <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.2.0</version>
</dependency>
import io.github.resilience4j.retry.annotation.Retry;@Retry annotation. @RestController
public class HelloWorldController {
private static final Logger logger = LoggerFactory.getLogger(HelloWorldController.class);
private Integer counter = 1;
/**
* Retry default return failure after 3 retries.
* @return
*/
@GetMapping("/greet")
@Retry(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod")
public String greeting() {
logger.info("***** HelloWorldController.greeting() method called.");
logger.info("***** Request id : {}", counter);
try {
if (counter % 5 == 0) {
throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter);
}
} catch (Exception ex) {
logger.info("**** Failed for request id : {}", counter);
throw ex;
} finally {
counter++;
}
return "Guten Morgen";
}
/**
* Custom retry as configured in the application.properties.
* @return
*/
@GetMapping("/greet-cr")
@Retry(name = "b9-cb-retries", fallbackMethod = "hardcodedResponseFallbackMethod")
public String greetingCustomRetries() {
logger.info("***** HelloWorldController.greeting() method called.");
logger.info("***** Request id : {}", counter);
try {
if (counter % 6 != 0) {
throw new CustomRetryRuntimeException("curcuite breacker test. Request Id : " + counter);
}
} catch (Exception ex) {
logger.info("**** Failed for request id : {}", counter);
throw ex;
} finally {
counter++;
}
return "Guten Morgen";
}
/**
* Circuit breaker fallback method.
* @param ex
*/
public String hardcodedResponseFallbackMethod(Exception ex) {
if (ex instanceof DefaultRetryRuntimeException) {
return "Guten Morgen, default resonse for DefaultRetryRuntimeException";
} else if (ex instanceof CustomRetryRuntimeException) {
return "Guten Morgen, default resonse for CustomRetryRuntimeException";
} else {
return "default response.";
}
}
}
spring.application.name=b9-curcuit-breacker
# Start: Circuit breaker config
# custom Retry - Max Retries for 5
resilience4j.retry.instances.b9-cb-retries.max-attempts=5
# Wait duration between each retries
resilience4j.retry.instances.b9-cb-retries.wait-duration=1s
# Increase the wait duration exponentionally between each reties
resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true
# End: Circuit breaker config
Note: This is an important note.
Notes:
References:
Using Resilience4j - retry. So all settings will remain same except for that instead of *@Retry annotation we'll use @CircuitBreaker annotation.fallbackMethod.Section 11. Using Resilience4j - retry for project setup.@Retry annotation with @CircuitBreaker annotation.
@CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod")resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.2.0</version>
</dependency>
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;@CircuitBreaker annotation. @RestController
public class HelloWorldControllerCircuitBreaker {
private static final Logger logger = LoggerFactory.getLogger(HelloWorldControllerCircuitBreaker.class);
private Integer counter = 1;
/**
* Retry default return failure after 3 retries.
* @return
*/
@GetMapping("/cb/greet")
// @Retry(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod")
@CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod")
public String greeting() {
logger.info("***** HelloWorldController.greeting() method called.");
logger.info("***** Request id : {}", counter);
try {
if (counter % 2 == 0) {
throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter);
}
} catch (Exception ex) {
logger.info("**** Failed for request id : {}", counter);
throw ex;
} finally {
counter++;
}
return "Guten Morgen";
}
/**
* Circuit breaker fallback method.
* @param ex
*/
public String hardcodedResponseFallbackMethod(Exception ex) {
if (ex instanceof DefaultRetryRuntimeException) {
return "Guten Morgen, default resonse for DefaultRetryRuntimeException";
} else if (ex instanceof CustomRetryRuntimeException) {
return "Guten Morgen, default resonse for CustomRetryRuntimeException";
} else {
return "default response.";
}
}
}
spring.application.name=b9-curcuit-breacker
# Start: Circuit breaker config
# custom Retry - Max Retries for 5
resilience4j.retry.instances.b9-cb-retries.max-attempts=5
# Wait duration between each retries
resilience4j.retry.instances.b9-cb-retries.wait-duration=1s
# Increase the wait duration exponentionally between each reties
resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true
# End: Circuit breaker config
Note: This is an important note.
Notes:
Reference section.References:
Section 11. Using Resilience4j - retry for project setup. <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.2.0</version>
</dependency>
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;@CircuitBreaker annotation. @RestController
public class HelloWorldControllerCircuitBreaker {
private static final Logger logger = LoggerFactory.getLogger(HelloWorldControllerCircuitBreaker.class);
private Integer counter = 1;
/**
* Retry default return failure after 3 retries.
*
* @return
*/
@GetMapping("/cb/greet")
@CircuitBreaker(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod")
@Retry(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod")
public String greeting() {
logger.info("***** HelloWorldController.greeting() method called.");
logger.info("***** Request id : {}", counter);
try {
if (counter % 2 == 0) {
throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter);
}
} catch (Exception ex) {
logger.info("**** Failed for request id : {}", counter);
throw ex;
} finally {
counter++;
}
return "Guten Morgen";
}
/**
* Testing other annotations.
*
* @return
*/
@GetMapping("/cb/greet-adv")
@CircuitBreaker(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod")
// we can also add fallbackMethod = "RateLimiterFallbackMethod"
@RateLimiter(name = "greeting-api")
// we can also add fallbackMethod = "bulkHeadFallbackMethod"
@Bulkhead(name = "greeting-api")
@Retry(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod")
// We can also add fallbackMethod = "timeLimiterFallbackMethod"
@TimeLimiter(name = "greeting-api")
public String greetingAdvance() {
logger.info("***** HelloWorldController.greeting() method called.");
logger.info("***** Request id : {}", counter);
try {
if (counter % 2 == 0) {
throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter);
}
} catch (Exception ex) {
logger.info("**** Failed for request id : {}", counter);
throw ex;
} finally {
counter++;
}
return "Guten Morgen";
}
/**
* Circuit breaker fallback method.
*
* @param ex
*/
public String hardcodedResponseFallbackMethod(Exception ex) {
if (ex instanceof DefaultRetryRuntimeException) {
return "Guten Morgen, default resonse for DefaultRetryRuntimeException";
} else if (ex instanceof CustomRetryRuntimeException) {
return "Guten Morgen, default resonse for CustomRetryRuntimeException";
} else {
return "default response.";
}
}
}
spring.application.name=b9-curcuit-breacker
# Start: Circuit breaker config
# custom Retry - Max Retries for 5
resilience4j.retry.instances.b9-cb-retries.max-attempts=5
# Wait duration between each retries
resilience4j.retry.instances.b9-cb-retries.wait-duration=1s
# Increase the wait duration exponentionally between each reties
resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true
# Start: Enable health checks for actuator
management.health.circuitbreakers.enabled: true
management.health.ratelimiters.enabled: true
resilience4j.ratelimiter.instances.default.register-health-indicator=true
resilience4j.ratelimiter.instances.greeting-api.register-health-indicator=true
resilience4j.ratelimiter.instances.b9-cb-retries.register-health-indicator=true
# End: Enable health checks for actuator
# Ratelimiter - allows max of 2 reuests in every 10 seconds.
resilience4j.ratelimiter.instances.greeting-api.limit-for-period=2
resilience4j.ratelimiter.instances.greeting-api.limit-refresh-period=10s
resilience4j.ratelimiter.instances.greeting-api.timeout-duration=3s
resilience4j.ratelimiter.instances.greeting-api.event-consumer-buffer-size=100
# BulkHead - Allows maximum of only 10 concurrent calls.
resilience4j.bulkhead.instances.greeting-api.max-concurrent-calls=10
resilience4j.thread-pool-bulkhead.instances.greeting-api.core-thread-pool-size=2
resilience4j.thread-pool-bulkhead.instances.greeting-api.max-thread-pool-size=5
resilience4j.thread-pool-bulkhead.instances.greeting-api.queue-capacity=1
resilience4j.thread-pool-bulkhead.instances.greeting-api.writable-stack-trace-enabled=true
resilience4j.thread-pool-bulkhead.instances.greeting-api.keep-alive-duration=600s
# TimeLimier -
resilience4j.timelimiter.instances.greeting-api.timeout-duration=2s
resilience4j.timelimiter.instances.greeting-api.cancel-running-future=true
# End: Circuit breaker config
Note: This is an important note.
Notes:
Reference section.References: