- 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>].properties
spring-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.git
Maven / 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-config
dependency in POM.xml.prefix
property in annotation as microservice-name.spring.cloud.config.name=limtis-service
spring.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-web
RestTemplateBuilder
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
RestTemplate
we 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.java
bean 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=false
eureka.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 client
dependency is present inPOM.xml
, spring will automatically try to register this service to Naming server by looking forEureka Server
on 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-balancer
is 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.resource
import 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-actuator
org.springframework.boot
:spring-boot-starter-web
org.springframework.boot
:spring-boot-starter-aop
io.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=1s
resilience4j.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: