Check out the full series of Questions Management tutorial here.
Applying Circuit Breaker for services in Questions Management application is very important to ensure that problems occur that need solutions to solve quickly and promptly. I will use Hystrix to do this.
You can learn more about Hystrix and Hystrix for Reactive application if you want.
In the Questions Management application, API and Composite services are services that will call other services to retrieve data. Therefore, I will declare using Hystrix dependency in these services as follows:
1 2 3 4 |
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> |
Next, I will go to each service to enable Circuit Breaker and correct the call to other services that support Hystrix as follows:
API Category Service
ApiCategoryServiceApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.huongdanjava.categoryservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @EnableCircuitBreaker @SpringBootApplication public class ApiCategoryServiceApplication { public static void main(String[] args) { SpringApplication.run(ApiCategoryServiceApplication.class, args); } } |
CoreCategoryServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
... @Service public class CoreCategoryServiceImpl implements CoreCategoryService { ... @Override public Flux<Category> findAll() { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Flux<Category> findAll = webClient.get() .uri("/category/all") .retrieve() .bodyToFlux(Category.class); return HystrixCommands.from(findAll) .fallback(Flux.empty()) .commandName("ACS.CoreCategoryService.findAll") .toFlux(); } @Override public Mono<Category> save(Category category) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Category> save = webClient.post() .uri("/category/add") .body(BodyInserters.fromObject(category)) .retrieve() .bodyToMono(Category.class); return HystrixCommands.from(save) .fallback(Mono.empty()) .commandName("ACS.CoreCategoryService.save") .toMono(); } @Override public Mono<Category> findCategoryById(String categoryId) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Category> findCategoryById = webClient.get() .uri("/category/" + categoryId) .retrieve() .bodyToMono(Category.class); return HystrixCommands.from(findCategoryById) .fallback(Mono.empty()) .commandName("ACS.CoreCategoryService.findCategoryById") .toMono(); } @Override public Mono<Category> updateCategory(String categoryId, Category category) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Category> updateCategory = webClient.put() .uri("/category/" + categoryId) .body(BodyInserters.fromObject(category)) .retrieve() .bodyToMono(Category.class); return HystrixCommands.from(updateCategory) .fallback(Mono.empty()) .commandName("ACS.CoreCategoryService.updateCategory") .toMono(); } @Override public Mono<HttpStatus> deleteCategory(String categoryId) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<HttpStatus> deleteCategory = webClient.delete() .uri("/category/" + categoryId) .exchange() .map(response -> response.statusCode()); return HystrixCommands.from(deleteCategory) .fallback(Mono.empty()) .commandName("coreCategoryService.deleteCategory") .toMono(); } } |
API Option Service
ApiOptionServiceApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.huongdanjava.optionservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @EnableCircuitBreaker @SpringBootApplication public class ApiOptionServiceApplication { public static void main(String[] args) { SpringApplication.run(ApiOptionServiceApplication.class, args); } } |
CompositeOptionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... @Service public class CompositeOptionServiceImpl implements CompositeOptionService { ... @Override public Mono<Option> addNewOption(Option option) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.post() .uri("/option/add") .body(BodyInserters.fromObject(option)) .retrieve(); Mono<Option> addNewOption = responseSpec.bodyToMono(Option.class); return HystrixCommands.from(addNewOption) .fallback(Mono.empty()) .commandName("AOS.CompositeOptionService.addNewOption") .toMono(); } } |
CoreOptionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
... @Service public class CoreOptionServiceImpl implements CoreOptionService { ... @Override public Mono<Option> updateOption(String optionId, Option option) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Option> updateOption = webClient.put() .uri("/option/" + optionId) .body(BodyInserters.fromObject(option)) .retrieve() .bodyToMono(Option.class); return HystrixCommands.from(updateOption) .fallback(Mono.empty()) .commandName("AOS.CoreOptionService.updateOption") .toMono(); } @Override public Mono<HttpStatus> deleteOption(String optionId) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<HttpStatus> deleteOption = webClient.delete() .uri("/option/" + optionId) .exchange() .map(response -> response.statusCode()); return HystrixCommands.from(deleteOption) .fallback(Mono.empty()) .commandName("AOS.CoreOptionService.deleteOption") .toMono(); } } |
API Question Service
ApiQuestionServiceApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.huongdanjava.questionservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @EnableCircuitBreaker @SpringBootApplication public class ApiQuestionServiceApplication { public static void main(String[] args) { SpringApplication.run(ApiQuestionServiceApplication.class, args); } } |
CompositeQuestionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
... @Service public class CompositeQuestionServiceImpl implements CompositeQuestionService { ... @Override public Flux<CompositeQuestion> findAllQuestions() { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/question/all") .retrieve(); Flux<CompositeQuestion> findAllQuestions = responseSpec.bodyToFlux(CompositeQuestion.class); return HystrixCommands.from(findAllQuestions) .fallback(Flux.empty()) .commandName("AQS.CompositeQuestionService.findAllQuestions") .toFlux(); } @Override public Mono<CompositeQuestion> findQuestionById(String id) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/question/" + id) .retrieve(); Mono<CompositeQuestion> findQuestionById = responseSpec.bodyToMono(CompositeQuestion.class); return HystrixCommands.from(findQuestionById) .fallback(Mono.empty()) .commandName("AQS.CompositeQuestionService.findQuestionById") .toMono(); } @Override public Mono<CompositeQuestion> createNewQuestion(Question question) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.post() .uri("/question/add") .body(BodyInserters.fromObject(question)) .retrieve(); Mono<CompositeQuestion> createNewQuestion = responseSpec.bodyToMono(CompositeQuestion.class); return HystrixCommands.from(createNewQuestion) .fallback(Mono.empty()) .commandName("AQS.CompositeQuestionService.createNewQuestion") .toMono(); } @Override public Mono<CompositeQuestion> updateQuestion(String questionId, Question question) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.put() .uri("/question/" + questionId) .body(BodyInserters.fromObject(question)) .retrieve(); Mono<CompositeQuestion> updateQuestion = responseSpec.bodyToMono(CompositeQuestion.class); return HystrixCommands.from(updateQuestion) .fallback(Mono.empty()) .commandName("AQS.CompositeQuestionService.updateQuestion") .toMono(); } @Override public Mono<HttpStatus> deleteQuestion(String questionId) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<HttpStatus> deleteQuestion = webClient.delete() .uri("/question/" + questionId) .exchange() .map(response -> response.statusCode()); return HystrixCommands.from(deleteQuestion) .fallback(Mono.empty()) .commandName("AQS.CompositeQuestionService.deleteQuestion") .toMono(); } } |
Composite Question Service
CompositeQuestionServiceApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.huongdanjava.questionservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @EnableCircuitBreaker @SpringBootApplication public class CompositeQuestionServiceApplication { public static void main(String[] args) { SpringApplication.run(CompositeQuestionServiceApplication.class, args); } } |
CoreCategoryServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... @Service public class CoreCategoryServiceImpl implements CoreCategoryService { ... @Override public Mono<Category> findById(String categoryId) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/category/" + categoryId) .retrieve(); Mono<Category> findById = responseSpec.bodyToMono(Category.class); return HystrixCommands.from(findById) .fallback(Mono.empty()) .commandName("CQS.CoreCategoryService.findById") .toMono(); } } |
CoreOptionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
... @Service public class CoreOptionServiceImpl implements CoreOptionService { ... @Override public Flux<Option> getOptions(String questionId) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/option?questionId=" + questionId) .retrieve(); Flux<Option> getOptions = responseSpec.bodyToFlux(Option.class); return HystrixCommands.from(getOptions) .fallback(Flux.empty()) .commandName("CQS.CoreOptionService.getOptions") .toFlux(); } @Override public Mono<Void> deleteByQuestionId(String questionId) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.delete() .uri("/option?questionId=" + questionId) .retrieve(); Mono<Void> deleteByQuestionId = responseSpec.bodyToMono(Void.class); return HystrixCommands.from(deleteByQuestionId) .fallback(Mono.empty()) .commandName("CQS.CoreOptionService.deleteByQuestionId") .toMono(); } } |
CoreQuestionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
... @Service public class CoreQuestionServiceImpl implements CoreQuestionService { ... @Override public Flux<Question> findAllQuestions() { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/question/all") .retrieve(); Flux<Question> findAllQuestions = responseSpec.bodyToFlux(Question.class); return HystrixCommands.from(findAllQuestions) .fallback(Flux.empty()) .commandName("CQS.CoreQuestionService.findAllQuestions") .toFlux(); } @Override public Mono<Question> findQuestionById(String id) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/question/" + id) .retrieve(); Mono<Question> findQuestionById = responseSpec.bodyToMono(Question.class); return HystrixCommands.from(findQuestionById) .fallback(Mono.empty()) .commandName("CQS.CoreQuestionService.findQuestionById") .toMono(); } @Override public Mono<Question> addNewQuestion(Question question) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Question> addNewQuestion = webClient.post() .uri("/question/add") .body(BodyInserters.fromObject(question)) .retrieve() .bodyToMono(Question.class); return HystrixCommands.from(addNewQuestion) .fallback(Mono.empty()) .commandName("CQS.CoreQuestionService.addNewQuestion") .toMono(); } @Override public Mono<Question> updateQuestion(String questionId, Question question) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Question> updateQuestion = webClient.put() .uri("/question/" + questionId) .body(BodyInserters.fromObject(question)) .retrieve() .bodyToMono(Question.class); return HystrixCommands.from(updateQuestion) .fallback(Mono.empty()) .commandName("CQS.CoreQuestionService.updateQuestion") .toMono(); } @Override public Mono<Void> deleteById(String questionId) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Void> deleteById = webClient.delete() .uri("/question/" + questionId) .retrieve() .bodyToMono(Void.class); return HystrixCommands.from(deleteById) .fallback(Mono.empty()) .commandName("CQS.CoreQuestionService.deleteById") .toMono(); } } |
Composite Option Service
CompositeOptionServiceApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.huongdanjava.optionservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @EnableCircuitBreaker @SpringBootApplication public class CompositeOptionServiceApplication { public static void main(String[] args) { SpringApplication.run(CompositeOptionServiceApplication.class, args); } } |
CoreOptionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... @Service public class CoreOptionServiceImpl implements CoreOptionService { ... @Override public Mono<Option> addNewOption(Option option) { WebClient webClient = WebClient.builder() .baseUrl(getServiceUrl()) .build(); Mono<Option> addNewOption = webClient.post() .uri("/option/add") .body(BodyInserters.fromObject(option)) .retrieve() .bodyToMono(Option.class); return HystrixCommands.from(addNewOption) .fallback(Mono.empty()) .commandName("COS.CoreOptionService.addNewOption") .toMono(); } } |
CoreQuestionServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... @Service public class CoreQuestionServiceImpl implements CoreQuestionService { ... @Override public Mono<Question> findById(String questionId) { WebClient client = WebClient.builder() .baseUrl(getServiceUrl()) .build(); WebClient.ResponseSpec responseSpec = client.get() .uri("/question/" + questionId) .retrieve(); Mono<Question> findById = responseSpec.bodyToMono(Question.class); return HystrixCommands.from(findById) .fallback(Mono.empty()) .commandName("COS.CoreQuestionService.findById") .toMono(); } } |
That’s it! You can test this Circuit Breaker as follows:
– Start all applications up:
– Request to get information of a question:
– Turn off Core Option Service:
– Request to question above again, you will see that the result still returns as usual without the option information as follows: