From 78b2dff8e8f2be00d027a58a80f60c6546a08739 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Tue, 23 Apr 2019 10:57:49 +0200 Subject: [PATCH 1/9] Adding new circuitbreaker-failsafe with naive implementation - Started working on spring-cloud-incubator/spring-cloud-circuitbreaker/#30 --- pom.xml | 1 + .../pom.xml | 6 + spring-cloud-circuitbreaker-failsafe/pom.xml | 40 ++++ .../failsafe/FailsafeAutoConfiguration.java | 63 +++++++ .../failsafe/FailsafeCircuitBreaker.java | 74 ++++++++ .../FailsafeCircuitBreakerFactory.java | 67 +++++++ .../failsafe/FailsafeConfigBuilder.java | 172 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + ...FailsafeCircuitBreakerIntegrationTest.java | 146 +++++++++++++++ .../failsafe/FailsafeCircuitBreakerTest.java | 45 +++++ 10 files changed, 616 insertions(+) create mode 100644 spring-cloud-circuitbreaker-failsafe/pom.xml create mode 100644 spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeAutoConfiguration.java create mode 100644 spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java create mode 100644 spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java create mode 100644 spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java create mode 100644 spring-cloud-circuitbreaker-failsafe/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java create mode 100644 spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerTest.java diff --git a/pom.xml b/pom.xml index 18e0b55a..8f945d0b 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ spring-cloud-circuitbreaker-commons spring-cloud-starter-circuitbreaker spring-cloud-circuitbreaker-hystrix + spring-cloud-circuitbreaker-failsafe spring-cloud-circuitbreaker-sentinel docs spring-cloud-circuitbreaker-spring-retry diff --git a/spring-cloud-circuitbreaker-dependencies/pom.xml b/spring-cloud-circuitbreaker-dependencies/pom.xml index 7a01dce9..37288135 100644 --- a/spring-cloud-circuitbreaker-dependencies/pom.xml +++ b/spring-cloud-circuitbreaker-dependencies/pom.xml @@ -19,6 +19,7 @@ 0.13.1 + 2.0.1 1.5.1 @@ -39,6 +40,11 @@ resilience4j-reactor ${resilience4j.version} + + net.jodah + failsafe + ${failsafe.version} + com.alibaba.csp sentinel-core diff --git a/spring-cloud-circuitbreaker-failsafe/pom.xml b/spring-cloud-circuitbreaker-failsafe/pom.xml new file mode 100644 index 00000000..86ab69f8 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/pom.xml @@ -0,0 +1,40 @@ + + + + spring-cloud-circuitbreaker + org.springframework.cloud + 0.0.1.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-circuitbreaker-failsafe + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.cloud + spring-cloud-circuitbreaker-commons + + + net.jodah + failsafe + + + org.springframework.boot + spring-boot-starter-web + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeAutoConfiguration.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeAutoConfiguration.java new file mode 100644 index 00000000..2953daa0 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; + +import net.jodah.failsafe.Failsafe; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Jakub Marchwicki + */ +@Configuration +@ConditionalOnClass(Failsafe.class) +public class FailsafeAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(CircuitBreakerFactory.class) + public CircuitBreakerFactory failsafeCircuitBreakerFactory() { + return new FailsafeCircuitBreakerFactory(); + } + + @Configuration + public static class FailsafeCustomizerConfiguration { + + @Autowired(required = false) + private List> customizers = new ArrayList<>(); + + @Autowired(required = false) + private FailsafeCircuitBreakerFactory factory; + + @PostConstruct + public void init() { + customizers.forEach(customizer -> customizer.customize(factory)); + } + + } + +} diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java new file mode 100644 index 00000000..2b0e579e --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import java.util.function.Function; +import java.util.function.Supplier; + +import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.Fallback; +import net.jodah.failsafe.event.ExecutionAttemptedEvent; +import net.jodah.failsafe.function.CheckedFunction; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; + +/** + * @author Jakub Marchwicki + */ +public class FailsafeCircuitBreaker implements CircuitBreaker { + + private String id; + + private FailsafeConfigBuilder.FailsafeConfig config; + + // private Optional> retryTemplateCustomizer; + // + // private RetryTemplate retryTemplate; + + public FailsafeCircuitBreaker(String id, + FailsafeConfigBuilder.FailsafeConfig config) { + // , + // Optional> retryTemplateCustomizer) { + this.id = id; + this.config = config; + // this.retryTemplateCustomizer = retryTemplateCustomizer; + // this.retryTemplate = new RetryTemplate(); + } + + @Override + public T run(Supplier toRun, Function fallback) { + + // retryTemplate.setBackOffPolicy(config.getBackOffPolicy()); + // retryTemplate.setRetryPolicy(config.getRetryPolicy()); + // + // retryTemplateCustomizer + // .ifPresent(customizer -> customizer.customize(retryTemplate)); + + Fallback f = Fallback.of(extract(fallback)); + return Failsafe.with(f).get(ex -> toRun.get()); + // return retryTemplate.execute(context -> toRun.get(), + // context -> fallback.apply(context.getLastThrowable()), + // new DefaultRetryState(id, config.isForceRefreshState(), + // config.getStateClassifier())); + } + + private CheckedFunction, ? extends T> extract( + Function fallback) { + return execution -> fallback.apply(execution.getLastFailure()); + } + +} diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java new file mode 100644 index 00000000..d27baa70 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import net.jodah.failsafe.Policy; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.commons.Customizer; +import org.springframework.util.Assert; + +/** + * @author Jakub Marchwicki + */ +public class FailsafeCircuitBreakerFactory extends + CircuitBreakerFactory { + + private Function defaultConfig = id -> new FailsafeConfigBuilder( + id).build(); + + private Map> failsafeCustomizers = new HashMap<>(); + + @Override + protected FailsafeConfigBuilder configBuilder(String id) { + return new FailsafeConfigBuilder(id); + } + + @Override + public void configureDefault( + Function defaultConfiguration) { + this.defaultConfig = defaultConfiguration; + } + + @Override + public CircuitBreaker create(String id) { + Assert.hasText(id, "A circuit breaker must have an id"); + FailsafeConfigBuilder.FailsafeConfig config = getConfigurations() + .computeIfAbsent(id, defaultConfig); + return new FailsafeCircuitBreaker(id, config); + } + + public void addRetryTemplateCustomizers(Customizer customizer, + String... ids) { + for (String id : ids) { + this.failsafeCustomizers.put(id, customizer); + } + } + +} diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java new file mode 100644 index 00000000..0fbbe534 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import org.springframework.cloud.circuitbreaker.commons.ConfigBuilder; + +/** + * Allows consumers to easily construct a {@link FailsafeConfig} object. + * + * @author Jakub Marchwicki + */ +public class FailsafeConfigBuilder + implements ConfigBuilder { + + private String id; + + // private BackOffPolicy backOffPolicy = new NoBackOffPolicy(); + // + // private RetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(); + + // private boolean forceRefreshState = false; + + // private Classifier stateClassifier = new Classifier() { + // @Override + // public Boolean classify(Throwable classifiable) { + // return false; + // } + // }; + + /** + * Constructor. + * @param id The id of the circuit breaker. + */ + public FailsafeConfigBuilder(String id) { + this.id = id; + } + + // /** + // * Sets the backoff policy when retrying a failed request. + // * @param backOffPolicy The {@link BackOffPolicy} to use. + // * @return The builder. + // */ + // public FailsafeConfigBuilder backOffPolicy(BackOffPolicy backOffPolicy) { + // this.backOffPolicy = backOffPolicy; + // return this; + // } + // + // /** + // * Sets the {@link RetryPolicy} to use. The {@code RetryPolicy} set here will be + // * wrapped in a {@link CircuitBreakerRetryPolicy}. + // * @param retryPolicy The {@code RetryPolicy} to use. + // * @return The builder. + // */ + // public FailsafeConfigBuilder retryPolicy(RetryPolicy retryPolicy) { + // this.retryPolicy = new CircuitBreakerRetryPolicy(retryPolicy); + // return this; + // } + // + // /** + // * Forces a refresh on the {@link DefaultRetryState} object. + // * @param refersh True to refresh, false othrwise. + // * @return The builder. + // */ + // public FailsafeConfigBuilder forceRefreshState(boolean refersh) { + // this.forceRefreshState = forceRefreshState; + // return this; + // } + // + // /** + // * The {@link Classifier} used by the {@link DefaultRetryState} object. + // * @param classifier The {@code Classifier} to set. + // * @return The builder. + // */ + // public FailsafeConfigBuilder stateClassifier( + // Classifier classifier) { + // this.stateClassifier = classifier; + // return this; + // } + // + @Override + public FailsafeConfig build() { + FailsafeConfig config = new FailsafeConfig(); + // config.setBackOffPolicy(this.backOffPolicy); + // config.setId(id); + // config.setRetryPolicy(retryPolicy); + // config.setForceRefreshState(forceRefreshState); + // config.setStateClassifier(stateClassifier); + return config; + } + + public static class FailsafeConfig { + + private String id; + + // TODO do we need this? + // private RetryContext retryContext; + // + // private BackOffPolicy backOffPolicy; + // + // private RetryPolicy retryPolicy; + // + // private boolean forceRefreshState; + // + // private Classifier stateClassifier; + // + // boolean isForceRefreshState() { + // return forceRefreshState; + // } + // + // void setForceRefreshState(boolean forceRefreshState) { + // this.forceRefreshState = forceRefreshState; + // } + // + // Classifier getStateClassifier() { + // return stateClassifier; + // } + // + // void setStateClassifier(Classifier stateClassifier) { + // this.stateClassifier = stateClassifier; + // } + // + // RetryPolicy getRetryPolicy() { + // return retryPolicy; + // } + // + // void setRetryPolicy(RetryPolicy retryPolicy) { + // this.retryPolicy = retryPolicy; + // } + // + // String getId() { + // return id; + // } + // + // void setId(String id) { + // this.id = id; + // } + // + // RetryContext getRetryContext() { + // return retryContext; + // } + // + // void setRetryContext(RetryContext retryContext) { + // this.retryContext = retryContext; + // } + // + // BackOffPolicy getBackOffPolicy() { + // return backOffPolicy; + // } + // + // void setBackOffPolicy(BackOffPolicy backOffPolicy) { + // this.backOffPolicy = backOffPolicy; + // } + // + + } + +} diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/resources/META-INF/spring.factories b/spring-cloud-circuitbreaker-failsafe/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..59717f25 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.circuitbreaker.failsafe.FailsafeAutoConfiguration diff --git a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java new file mode 100644 index 00000000..911fa5a6 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import java.time.Duration; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Jakub Marchwicki + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = FailsafeCircuitBreakerIntegrationTest.Application.class) +@DirtiesContext +public class FailsafeCircuitBreakerIntegrationTest { + + @Autowired + Application.DemoControllerService service; + + @Test + public void testSlow() { + assertThat(service.slow()).isEqualTo("fallback"); + } + + @Test + public void testNormal() { + assertThat(service.normal()).isEqualTo("normal"); + } + + @Configuration + @EnableAutoConfiguration + @RestController + protected static class Application { + + @GetMapping("/slow") + public String slow() throws InterruptedException { + Thread.sleep(3000); + return "slow"; + } + + @GetMapping("/normal") + public String normal() { + return "normal"; + } + + @Bean + public RestTemplateBuilder restTemplateBuilder() { + return new RestTemplateBuilder().setReadTimeout(Duration.ofSeconds(1)); + } + + // @Bean + // public Customizer factoryCustomizer() { + // return factory -> { + // factory.configureDefault(id -> new FailsafeConfigBuilder(id) + // .retryPolicy(new TimeoutRetryPolicy()).build()); + // factory.configure( + // builder -> builder.retryPolicy(new SimpleRetryPolicy(1)).build(), + // "slow"); + // factory.addRetryTemplateCustomizers(retryTemplate -> retryTemplate + // .registerListener(new RetryListener() { + // + // @Override + // public boolean open( + // RetryContext context, RetryCallback callback) { + // return false; + // } + // + // @Override + // public void close( + // RetryContext context, RetryCallback callback, + // Throwable throwable) { + // + // } + // + // @Override + // public void onError( + // RetryContext context, RetryCallback callback, + // Throwable throwable) { + // + // } + // })); + // }; + // } + + @Service + public static class DemoControllerService { + + private TestRestTemplate rest; + + private CircuitBreakerFactory cbFactory; + + DemoControllerService(TestRestTemplate rest, + CircuitBreakerFactory cbFactory) { + this.rest = rest; + this.cbFactory = cbFactory; + } + + public String slow() { + return cbFactory.create("slow").run( + () -> rest.getForObject("/slow", String.class), t -> "fallback"); + } + + public String normal() { + return cbFactory.create("normal").run( + () -> rest.getForObject("/normal", String.class), + t -> "fallback"); + } + + } + + } + +} diff --git a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerTest.java b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerTest.java new file mode 100644 index 00000000..e009d1b0 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.circuitbreaker.failsafe; + +import org.junit.Test; + +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jakub Marchwicki + */ +public class FailsafeCircuitBreakerTest { + + @Test + public void run() { + CircuitBreaker cb = new FailsafeCircuitBreakerFactory().create("foo"); + String s = cb.run(() -> "foobar", t -> "fallback"); + assertThat(cb.run(() -> "foobar", t -> "fallback")).isEqualTo("foobar"); + } + + @Test + public void fallback() { + CircuitBreaker cb = new FailsafeCircuitBreakerFactory().create("foo"); + assertThat((String) cb.run(() -> { + throw new RuntimeException("Boom"); + }, t -> "fallback")).isEqualTo("fallback"); + } + +} From c87d0f460fccead4b93be6c367dfad29aeca8da2 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Tue, 23 Apr 2019 11:50:57 +0200 Subject: [PATCH 2/9] Configuration of RetryPolicy and CircuitBreaker --- .../failsafe/FailsafeCircuitBreaker.java | 5 +- .../failsafe/FailsafeConfigBuilder.java | 78 ++++++++++++------- ...FailsafeCircuitBreakerIntegrationTest.java | 45 ++++++++--- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java index 2b0e579e..fd3e7d26 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java @@ -58,8 +58,9 @@ public T run(Supplier toRun, Function fallback) { // retryTemplateCustomizer // .ifPresent(customizer -> customizer.customize(retryTemplate)); - Fallback f = Fallback.of(extract(fallback)); - return Failsafe.with(f).get(ex -> toRun.get()); + Fallback failsafeFallback = Fallback.of(extract(fallback)); + return Failsafe.with(failsafeFallback, config.getRetryPolicy(), + config.getCircuitBreaker()).get(ex -> toRun.get()); // return retryTemplate.execute(context -> toRun.get(), // context -> fallback.apply(context.getLastThrowable()), // new DefaultRetryState(id, config.isForceRefreshState(), diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java index 0fbbe534..c39c2abf 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java @@ -16,6 +16,9 @@ package org.springframework.cloud.circuitbreaker.failsafe; +import net.jodah.failsafe.CircuitBreaker; +import net.jodah.failsafe.RetryPolicy; + import org.springframework.cloud.circuitbreaker.commons.ConfigBuilder; /** @@ -30,7 +33,9 @@ public class FailsafeConfigBuilder // private BackOffPolicy backOffPolicy = new NoBackOffPolicy(); // - // private RetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(); + private RetryPolicy retryPolicy = new RetryPolicy<>(); + + private CircuitBreaker circuitBreaker = new CircuitBreaker<>(); // private boolean forceRefreshState = false; @@ -60,16 +65,17 @@ public FailsafeConfigBuilder(String id) { // return this; // } // - // /** - // * Sets the {@link RetryPolicy} to use. The {@code RetryPolicy} set here will be - // * wrapped in a {@link CircuitBreakerRetryPolicy}. - // * @param retryPolicy The {@code RetryPolicy} to use. - // * @return The builder. - // */ - // public FailsafeConfigBuilder retryPolicy(RetryPolicy retryPolicy) { - // this.retryPolicy = new CircuitBreakerRetryPolicy(retryPolicy); - // return this; - // } + + public FailsafeConfigBuilder retryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + return this; + } + + public FailsafeConfigBuilder circuitBreaker(CircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; + return this; + } + // // /** // * Forces a refresh on the {@link DefaultRetryState} object. @@ -96,8 +102,9 @@ public FailsafeConfigBuilder(String id) { public FailsafeConfig build() { FailsafeConfig config = new FailsafeConfig(); // config.setBackOffPolicy(this.backOffPolicy); - // config.setId(id); - // config.setRetryPolicy(retryPolicy); + config.setId(id); + config.setRetryPolicy(retryPolicy); + config.setCircuitBreaker(circuitBreaker); // config.setForceRefreshState(forceRefreshState); // config.setStateClassifier(stateClassifier); return config; @@ -112,7 +119,10 @@ public static class FailsafeConfig { // // private BackOffPolicy backOffPolicy; // - // private RetryPolicy retryPolicy; + private RetryPolicy retryPolicy; + + private CircuitBreaker circuitBreaker; + // // private boolean forceRefreshState; // @@ -134,22 +144,30 @@ public static class FailsafeConfig { // this.stateClassifier = stateClassifier; // } // - // RetryPolicy getRetryPolicy() { - // return retryPolicy; - // } - // - // void setRetryPolicy(RetryPolicy retryPolicy) { - // this.retryPolicy = retryPolicy; - // } - // - // String getId() { - // return id; - // } - // - // void setId(String id) { - // this.id = id; - // } - // + CircuitBreaker getCircuitBreaker() { + return circuitBreaker; + } + + void setCircuitBreaker(CircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; + } + + RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + String getId() { + return id; + } + + void setId(String id) { + this.id = id; + } + // RetryContext getRetryContext() { // return retryContext; // } diff --git a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java index 911fa5a6..a985d460 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java @@ -18,6 +18,7 @@ import java.time.Duration; +import net.jodah.failsafe.RetryPolicy; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,7 +27,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.commons.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; @@ -36,6 +39,10 @@ import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** @@ -53,6 +60,7 @@ public class FailsafeCircuitBreakerIntegrationTest { @Test public void testSlow() { assertThat(service.slow()).isEqualTo("fallback"); + service.verifyTimesSlowInvoked(); } @Test @@ -81,14 +89,20 @@ public RestTemplateBuilder restTemplateBuilder() { return new RestTemplateBuilder().setReadTimeout(Duration.ofSeconds(1)); } - // @Bean - // public Customizer factoryCustomizer() { - // return factory -> { - // factory.configureDefault(id -> new FailsafeConfigBuilder(id) - // .retryPolicy(new TimeoutRetryPolicy()).build()); - // factory.configure( - // builder -> builder.retryPolicy(new SimpleRetryPolicy(1)).build(), - // "slow"); + @Bean + public Customizer factoryCustomizer() { + return factory -> { + factory.configureDefault(id -> new FailsafeConfigBuilder(id).build()); + factory.configure( + builder -> builder + .retryPolicy(new RetryPolicy<>().withMaxAttempts(1)) + .circuitBreaker(new net.jodah.failsafe.CircuitBreaker<>() + .handle(Exception.class).withFailureThreshold(1) + .withDelay(Duration.ofMinutes(1))) + .build(), + "slow"); + }; + } // factory.addRetryTemplateCustomizers(retryTemplate -> retryTemplate // .registerListener(new RetryListener() { // @@ -124,13 +138,18 @@ public static class DemoControllerService { DemoControllerService(TestRestTemplate rest, CircuitBreakerFactory cbFactory) { - this.rest = rest; + this.rest = spy(rest); this.cbFactory = cbFactory; } public String slow() { - return cbFactory.create("slow").run( - () -> rest.getForObject("/slow", String.class), t -> "fallback"); + CircuitBreaker cb = cbFactory.create("slow"); + for (int i = 0; i < 10; i++) { + cb.run(() -> rest.getForObject("/slow", String.class), + t -> "fallback"); + } + return cb.run(() -> rest.getForObject("/slow", String.class), + t -> "fallback"); } public String normal() { @@ -139,6 +158,10 @@ public String normal() { t -> "fallback"); } + public void verifyTimesSlowInvoked() { + verify(rest, times(1)).getForObject(eq("/slow"), eq(String.class)); + } + } } From 8a0ab6d8afae608e93f538813c12b5b99f69d4e5 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Tue, 23 Apr 2019 14:55:37 +0200 Subject: [PATCH 3/9] Enable registering Failsafe execution customizers --- .../failsafe/FailsafeCircuitBreaker.java | 39 ++++++--------- .../FailsafeCircuitBreakerFactory.java | 10 ++-- ...FailsafeCircuitBreakerIntegrationTest.java | 50 ++++++++----------- 3 files changed, 42 insertions(+), 57 deletions(-) diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java index fd3e7d26..b40725f3 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java @@ -16,55 +16,48 @@ package org.springframework.cloud.circuitbreaker.failsafe; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.FailsafeExecutor; import net.jodah.failsafe.Fallback; import net.jodah.failsafe.event.ExecutionAttemptedEvent; import net.jodah.failsafe.function.CheckedFunction; import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; +import org.springframework.cloud.circuitbreaker.commons.Customizer; /** * @author Jakub Marchwicki */ public class FailsafeCircuitBreaker implements CircuitBreaker { - private String id; + private final String id; - private FailsafeConfigBuilder.FailsafeConfig config; + private final FailsafeConfigBuilder.FailsafeConfig config; - // private Optional> retryTemplateCustomizer; - // - // private RetryTemplate retryTemplate; + private final Optional> failsafeCustomizer; - public FailsafeCircuitBreaker(String id, - FailsafeConfigBuilder.FailsafeConfig config) { - // , - // Optional> retryTemplateCustomizer) { + public FailsafeCircuitBreaker(String id, FailsafeConfigBuilder.FailsafeConfig config, + Optional> failsafeCustomizer) { this.id = id; this.config = config; - // this.retryTemplateCustomizer = retryTemplateCustomizer; - // this.retryTemplate = new RetryTemplate(); + this.failsafeCustomizer = failsafeCustomizer; } @Override public T run(Supplier toRun, Function fallback) { - // retryTemplate.setBackOffPolicy(config.getBackOffPolicy()); - // retryTemplate.setRetryPolicy(config.getRetryPolicy()); - // - // retryTemplateCustomizer - // .ifPresent(customizer -> customizer.customize(retryTemplate)); - Fallback failsafeFallback = Fallback.of(extract(fallback)); - return Failsafe.with(failsafeFallback, config.getRetryPolicy(), - config.getCircuitBreaker()).get(ex -> toRun.get()); - // return retryTemplate.execute(context -> toRun.get(), - // context -> fallback.apply(context.getLastThrowable()), - // new DefaultRetryState(id, config.isForceRefreshState(), - // config.getStateClassifier())); + FailsafeExecutor failsafeExecutor = Failsafe.with(failsafeFallback, + config.getRetryPolicy(), config.getCircuitBreaker()); + + failsafeCustomizer + .ifPresent(customizer -> customizer.customize(failsafeExecutor)); + + return failsafeExecutor.get(ex -> toRun.get()); } private CheckedFunction, ? extends T> extract( diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java index d27baa70..48e2fb21 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java @@ -18,9 +18,10 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Function; -import net.jodah.failsafe.Policy; +import net.jodah.failsafe.FailsafeExecutor; import org.springframework.cloud.circuitbreaker.commons.CircuitBreaker; import org.springframework.cloud.circuitbreaker.commons.CircuitBreakerFactory; @@ -36,7 +37,7 @@ public class FailsafeCircuitBreakerFactory extends private Function defaultConfig = id -> new FailsafeConfigBuilder( id).build(); - private Map> failsafeCustomizers = new HashMap<>(); + private Map> failsafeCustomizers = new HashMap<>(); @Override protected FailsafeConfigBuilder configBuilder(String id) { @@ -54,10 +55,11 @@ public CircuitBreaker create(String id) { Assert.hasText(id, "A circuit breaker must have an id"); FailsafeConfigBuilder.FailsafeConfig config = getConfigurations() .computeIfAbsent(id, defaultConfig); - return new FailsafeCircuitBreaker(id, config); + return new FailsafeCircuitBreaker(id, config, + Optional.ofNullable(failsafeCustomizers.get(id))); } - public void addRetryTemplateCustomizers(Customizer customizer, + public void addRetryTemplateCustomizers(Customizer customizer, String... ids) { for (String id : ids) { this.failsafeCustomizers.put(id, customizer); diff --git a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java index a985d460..0e89659f 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java @@ -19,6 +19,7 @@ import java.time.Duration; import net.jodah.failsafe.RetryPolicy; +import net.jodah.failsafe.function.CheckedConsumer; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,7 +40,9 @@ import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.internal.verification.VerificationModeFactory.times; @@ -57,10 +60,14 @@ public class FailsafeCircuitBreakerIntegrationTest { @Autowired Application.DemoControllerService service; + @Autowired + CheckedConsumer onFailureConsumer; + @Test - public void testSlow() { + public void testSlow() throws Exception { assertThat(service.slow()).isEqualTo("fallback"); service.verifyTimesSlowInvoked(); + verify(onFailureConsumer, times(11)).accept(any()); } @Test @@ -90,7 +97,13 @@ public RestTemplateBuilder restTemplateBuilder() { } @Bean - public Customizer factoryCustomizer() { + public CheckedConsumer onFailureConsumer() { + return mock(CheckedConsumer.class); + } + + @Bean + public Customizer factoryCustomizer( + CheckedConsumer onFailureConsumer) { return factory -> { factory.configureDefault(id -> new FailsafeConfigBuilder(id).build()); factory.configure( @@ -101,33 +114,10 @@ public Customizer factoryCustomizer() { .withDelay(Duration.ofMinutes(1))) .build(), "slow"); + factory.addRetryTemplateCustomizers( + failsafe -> failsafe.onFailure(onFailureConsumer), "slow"); }; } - // factory.addRetryTemplateCustomizers(retryTemplate -> retryTemplate - // .registerListener(new RetryListener() { - // - // @Override - // public boolean open( - // RetryContext context, RetryCallback callback) { - // return false; - // } - // - // @Override - // public void close( - // RetryContext context, RetryCallback callback, - // Throwable throwable) { - // - // } - // - // @Override - // public void onError( - // RetryContext context, RetryCallback callback, - // Throwable throwable) { - // - // } - // })); - // }; - // } @Service public static class DemoControllerService { @@ -142,7 +132,7 @@ public static class DemoControllerService { this.cbFactory = cbFactory; } - public String slow() { + String slow() { CircuitBreaker cb = cbFactory.create("slow"); for (int i = 0; i < 10; i++) { cb.run(() -> rest.getForObject("/slow", String.class), @@ -152,13 +142,13 @@ public String slow() { t -> "fallback"); } - public String normal() { + String normal() { return cbFactory.create("normal").run( () -> rest.getForObject("/normal", String.class), t -> "fallback"); } - public void verifyTimesSlowInvoked() { + void verifyTimesSlowInvoked() { verify(rest, times(1)).getForObject(eq("/slow"), eq(String.class)); } From 87df5f76aa37bed693f6a20f950ab3ad39046291 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Tue, 23 Apr 2019 15:35:58 +0200 Subject: [PATCH 4/9] Adding spring-cloud-starter-circuitbreaker-failsafe starter --- spring-cloud-starter-circuitbreaker/pom.xml | 2 ++ .../pom.xml | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/pom.xml diff --git a/spring-cloud-starter-circuitbreaker/pom.xml b/spring-cloud-starter-circuitbreaker/pom.xml index a613c4a7..1023697c 100644 --- a/spring-cloud-starter-circuitbreaker/pom.xml +++ b/spring-cloud-starter-circuitbreaker/pom.xml @@ -12,11 +12,13 @@ org.springframework.cloud spring-cloud-starter-circuitbreaker 0.0.1.BUILD-SNAPSHOT + spring-cloud-starter-circuitbreaker-resilience4j spring-cloud-starter-circuitbreaker-hystrix spring-cloud-starter-circuitbreaker-sentinel spring-cloud-starter-circuitbreaker-spring-retry + spring-cloud-starter-circuitbreaker-failsafe pom diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/pom.xml b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/pom.xml new file mode 100644 index 00000000..6e461ab2 --- /dev/null +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/pom.xml @@ -0,0 +1,30 @@ + + + + spring-cloud-starter-circuitbreaker + org.springframework.cloud + 0.0.1.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-starter-circuitbreaker-failsafe + + + + org.springframework.cloud + spring-cloud-starter + + + org.springframework.cloud + spring-cloud-circuitbreaker-commons + + + net.jodah + failsafe + + + + + From ab6824ab53bda8bc70c3b1738876830dc2b2edc5 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Tue, 23 Apr 2019 15:41:17 +0200 Subject: [PATCH 5/9] Cleaning up module configuration --- .../failsafe/FailsafeConfigBuilder.java | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java index c39c2abf..900b2324 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java @@ -31,22 +31,10 @@ public class FailsafeConfigBuilder private String id; - // private BackOffPolicy backOffPolicy = new NoBackOffPolicy(); - // private RetryPolicy retryPolicy = new RetryPolicy<>(); private CircuitBreaker circuitBreaker = new CircuitBreaker<>(); - // private boolean forceRefreshState = false; - - // private Classifier stateClassifier = new Classifier() { - // @Override - // public Boolean classify(Throwable classifiable) { - // return false; - // } - // }; - /** * Constructor. * @param id The id of the circuit breaker. @@ -55,17 +43,6 @@ public FailsafeConfigBuilder(String id) { this.id = id; } - // /** - // * Sets the backoff policy when retrying a failed request. - // * @param backOffPolicy The {@link BackOffPolicy} to use. - // * @return The builder. - // */ - // public FailsafeConfigBuilder backOffPolicy(BackOffPolicy backOffPolicy) { - // this.backOffPolicy = backOffPolicy; - // return this; - // } - // - public FailsafeConfigBuilder retryPolicy(RetryPolicy retryPolicy) { this.retryPolicy = retryPolicy; return this; @@ -76,37 +53,12 @@ public FailsafeConfigBuilder circuitBreaker(CircuitBreaker circuitBreaker return this; } - // - // /** - // * Forces a refresh on the {@link DefaultRetryState} object. - // * @param refersh True to refresh, false othrwise. - // * @return The builder. - // */ - // public FailsafeConfigBuilder forceRefreshState(boolean refersh) { - // this.forceRefreshState = forceRefreshState; - // return this; - // } - // - // /** - // * The {@link Classifier} used by the {@link DefaultRetryState} object. - // * @param classifier The {@code Classifier} to set. - // * @return The builder. - // */ - // public FailsafeConfigBuilder stateClassifier( - // Classifier classifier) { - // this.stateClassifier = classifier; - // return this; - // } - // @Override public FailsafeConfig build() { FailsafeConfig config = new FailsafeConfig(); - // config.setBackOffPolicy(this.backOffPolicy); config.setId(id); config.setRetryPolicy(retryPolicy); config.setCircuitBreaker(circuitBreaker); - // config.setForceRefreshState(forceRefreshState); - // config.setStateClassifier(stateClassifier); return config; } @@ -114,36 +66,10 @@ public static class FailsafeConfig { private String id; - // TODO do we need this? - // private RetryContext retryContext; - // - // private BackOffPolicy backOffPolicy; - // private RetryPolicy retryPolicy; private CircuitBreaker circuitBreaker; - // - // private boolean forceRefreshState; - // - // private Classifier stateClassifier; - // - // boolean isForceRefreshState() { - // return forceRefreshState; - // } - // - // void setForceRefreshState(boolean forceRefreshState) { - // this.forceRefreshState = forceRefreshState; - // } - // - // Classifier getStateClassifier() { - // return stateClassifier; - // } - // - // void setStateClassifier(Classifier stateClassifier) { - // this.stateClassifier = stateClassifier; - // } - // CircuitBreaker getCircuitBreaker() { return circuitBreaker; } @@ -168,23 +94,6 @@ void setId(String id) { this.id = id; } - // RetryContext getRetryContext() { - // return retryContext; - // } - // - // void setRetryContext(RetryContext retryContext) { - // this.retryContext = retryContext; - // } - // - // BackOffPolicy getBackOffPolicy() { - // return backOffPolicy; - // } - // - // void setBackOffPolicy(BackOffPolicy backOffPolicy) { - // this.backOffPolicy = backOffPolicy; - // } - // - } } From 2f00d0d2c1a0e06bf672c69db7e60541813e3096 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Fri, 19 Jul 2019 13:09:34 +0200 Subject: [PATCH 6/9] Renaming customizer method --- .../circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java | 2 +- .../failsafe/FailsafeCircuitBreakerIntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java index 48e2fb21..9a17525a 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java @@ -59,7 +59,7 @@ public CircuitBreaker create(String id) { Optional.ofNullable(failsafeCustomizers.get(id))); } - public void addRetryTemplateCustomizers(Customizer customizer, + public void addFailsafeCustomizers(Customizer customizer, String... ids) { for (String id : ids) { this.failsafeCustomizers.put(id, customizer); diff --git a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java index 0e89659f..efda7094 100644 --- a/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java @@ -114,7 +114,7 @@ public Customizer factoryCustomizer( .withDelay(Duration.ofMinutes(1))) .build(), "slow"); - factory.addRetryTemplateCustomizers( + factory.addFailsafeCustomizers( failsafe -> failsafe.onFailure(onFailureConsumer), "slow"); }; } From b2f7de82a8740ca9ef134e4de9f98f3c61995440 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Fri, 19 Jul 2019 13:10:00 +0200 Subject: [PATCH 7/9] Adding Failsafe documentation --- .../spring-cloud-circuitbreaker-failsafe.adoc | 68 +++++++++++++++++++ .../asciidoc/spring-cloud-circuitbreaker.adoc | 4 ++ 2 files changed, 72 insertions(+) create mode 100644 docs/src/main/asciidoc/spring-cloud-circuitbreaker-failsafe.adoc diff --git a/docs/src/main/asciidoc/spring-cloud-circuitbreaker-failsafe.adoc b/docs/src/main/asciidoc/spring-cloud-circuitbreaker-failsafe.adoc new file mode 100644 index 00000000..7a50df81 --- /dev/null +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker-failsafe.adoc @@ -0,0 +1,68 @@ +=== Configuring Failsafe Circuit Breakers + +Failsafe is a lightweight, zero-dependency library for handling failures in Java 8+, +with a concise API for handling everyday use cases and the flexibility to handle everything else. +Failsafe provides https://github.com/jhalterman/failsafe#circuit-breakers[circuit breakers] along side with +https://github.com/jhalterman/failsafe#retries[retries] and +https://github.com/jhalterman/failsafe#fallbacks[fallbacks], which can be composed +together to form policies which wrap the execution logic. + +All circuit breakers will be created using the `Failsafe` and a combination of the default implementation of +https://github.com/jhalterman/failsafe/blob/master/src/main/java/net/jodah/failsafe/Fallback.java[`Fallback`], +https://github.com/jhalterman/failsafe/blob/master/src/main/java/net/jodah/failsafe/RetryPolicy.java[`RetryPolicy`], +https://github.com/jhalterman/failsafe/blob/master/src/main/java/net/jodah/failsafe/CircuitBreaker.java[`CircuitBreaker`] - in that order. +All these these classes can be configured using `FailsafeConfigBuilder`. + +==== Default Configuration + +To provide a default configuration for all of your circuit breakers create a `Customize` bean that is passed a +`FailsafeCircuitBreakerFactory`. +The `configureDefault` method can be used to provide a default configuration. + +==== +[source,java] +---- +@Bean +public Customizer defaultCustomizer() { + return factory -> factory.configureDefault(id -> new FailsafeConfigBuilder(id) + .retryPolicy(new RetryPolicy<>().withMaxAttempts(2)) + .build()); +} +---- +==== + +==== Specific Circuit Breaker Configuration + +Similarly to providing a default configuration, you can create a `Customize` bean this is passed a +`FailsafeCircuitBreakerFactory`. + +==== +[source,java] +---- +@Bean +public Customizer slowCustomizer() { + return factory -> factory.configure( + builder -> builder + .retryPolicy(new RetryPolicy<>().withMaxAttempts(1)) + .circuitBreaker(new net.jodah.failsafe.CircuitBreaker<>() + .handle(Exception.class).withFailureThreshold(1) + .withDelay(Duration.ofMinutes(1))) + .build(), + "slow"); +---- +==== + +In addition to configuring the circuit breaker that is created you can also customize the circuit breaker after it +has been created but before it is returned to the caller. To do this you can use the `addFailsafeCustomizers` +method. This can be useful for adding event handlers to the `Failsafe`. + +==== +[source,java] +---- +@Bean +public Customizer slowCustomizer() { + return factory -> factory.addFailsafeCustomizers( + failsafe -> failsafe.onFailure(onFailureConsumer), "slow"); +} +---- +==== diff --git a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc index ab0b546c..305379be 100755 --- a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc @@ -22,6 +22,7 @@ breaker implementation that best fits your needs for your app. * https://github.com/resilience4j/resilience4j[Resilience4J] * https://github.com/alibaba/Sentinel[Sentinel] * https://github.com/spring-projects/spring-retry[Spring Retry] +* https://github.com/jhalterman/failsafe[Failsafe] == Core Concepts @@ -103,6 +104,9 @@ include::spring-cloud-circuitbreaker-sentinel.adoc[] include::spring-cloud-circuitbreaker-spring-retry.adoc[] +include::spring-cloud-circuitbreaker-failsafe.adoc[] + + == Building include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-jdk8.adoc[] From 17fec8d19c152274c1eeb5a13d973237a5244019 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Sun, 10 Nov 2019 22:47:29 +0100 Subject: [PATCH 8/9] Adding starter to dependencies module --- spring-cloud-circuitbreaker-dependencies/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spring-cloud-circuitbreaker-dependencies/pom.xml b/spring-cloud-circuitbreaker-dependencies/pom.xml index 146ca052..7c5a95fd 100644 --- a/spring-cloud-circuitbreaker-dependencies/pom.xml +++ b/spring-cloud-circuitbreaker-dependencies/pom.xml @@ -59,6 +59,11 @@ spring-cloud-circuitbreaker-spring-retry ${project.version} + + org.springframework.cloud + spring-cloud-circuitbreaker-failsafe + ${project.version} + org.springframework.cloud spring-cloud-starter-circuitbreaker-resilience4j @@ -69,6 +74,11 @@ spring-cloud-starter-circuitbreaker-reactor-resilience4j ${project.version} + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-failsafe + ${project.version} + From bde461ce7d75aa198b025e1dbe0ba03eb23e5e92 Mon Sep 17 00:00:00 2001 From: kubamarchwicki Date: Sun, 10 Nov 2019 22:56:54 +0100 Subject: [PATCH 9/9] Bumping failsafe to latest 2.3.1 --- spring-cloud-circuitbreaker-dependencies/pom.xml | 2 +- spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml | 2 +- .../.flattened-pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-cloud-circuitbreaker-dependencies/pom.xml b/spring-cloud-circuitbreaker-dependencies/pom.xml index 7c5a95fd..3f175bcb 100644 --- a/spring-cloud-circuitbreaker-dependencies/pom.xml +++ b/spring-cloud-circuitbreaker-dependencies/pom.xml @@ -19,7 +19,7 @@ 1.1.0 - 2.0.1 + 2.3.1 diff --git a/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml b/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml index 20aa292f..4f40b28e 100644 --- a/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml +++ b/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml @@ -112,7 +112,7 @@ net.jodah failsafe - 2.0.1 + 2.3.1 compile diff --git a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/.flattened-pom.xml b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/.flattened-pom.xml index 59692ae6..035321c8 100644 --- a/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/.flattened-pom.xml +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/.flattened-pom.xml @@ -105,7 +105,7 @@ net.jodah failsafe - 2.0.1 + 2.3.1 compile