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 6223eebe..e5034d87 100755 --- a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc @@ -7,6 +7,9 @@ include::spring-cloud-circuitbreaker-resilience4j.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[] diff --git a/pom.xml b/pom.xml index 55a529ea..66160b2b 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ spring-cloud-starter-circuitbreaker docs spring-cloud-circuitbreaker-spring-retry + spring-cloud-circuitbreaker-failsafe diff --git a/spring-cloud-circuitbreaker-dependencies/pom.xml b/spring-cloud-circuitbreaker-dependencies/pom.xml index 3dbd7eed..3f175bcb 100644 --- a/spring-cloud-circuitbreaker-dependencies/pom.xml +++ b/spring-cloud-circuitbreaker-dependencies/pom.xml @@ -19,6 +19,7 @@ 1.1.0 + 2.3.1 @@ -43,6 +44,11 @@ resilience4j-micrometer ${resilience4j.version} + + net.jodah + failsafe + ${failsafe.version} + org.springframework.cloud spring-cloud-circuitbreaker-resilience4j @@ -53,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 @@ -63,6 +74,11 @@ spring-cloud-starter-circuitbreaker-reactor-resilience4j ${project.version} + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-failsafe + ${project.version} + diff --git a/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml b/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml new file mode 100644 index 00000000..4f40b28e --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/.flattened-pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-circuitbreaker + 1.0.0.BUILD-SNAPSHOT + .. + + org.springframework.cloud + spring-cloud-circuitbreaker-failsafe + 1.0.0.BUILD-SNAPSHOT + Spring Cloud parent pom, managing plugins and dependencies for Spring + Cloud projects + https://spring.io/spring-cloud/spring-cloud-circuitbreaker/spring-cloud-circuitbreaker-failsafe + + Pivotal Software, Inc. + https://www.spring.io + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + Copyright 2014-2015 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. + + + + + dsyer + Dave Syer + dsyer at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + lead + + + + sgibb + Spencer Gibb + sgibb at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + lead + + + + mgrzejszczak + Marcin Grzejszczak + mgrzejszczak at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + rbaxter + Ryan Baxter + rbaxter at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + omaciaszeksharma + Olga Maciaszek-Sharma + omaciaszeksharma at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + + scm:git:git://github.com/spring-cloud/spring-cloud-build.git/spring-cloud-circuitbreaker/spring-cloud-circuitbreaker-failsafe + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-build.git/spring-cloud-circuitbreaker/spring-cloud-circuitbreaker-failsafe + https://github.com/spring-cloud/spring-cloud-build/spring-cloud-circuitbreaker/spring-cloud-circuitbreaker-failsafe + + + + org.springframework.cloud + spring-cloud-commons + 2.2.0.BUILD-SNAPSHOT + compile + + + org.springframework.boot + spring-boot-starter + 2.2.1.RELEASE + compile + + + net.jodah + failsafe + 2.3.1 + compile + + + org.springframework.boot + spring-boot-starter-web + 2.2.1.RELEASE + compile + true + + + diff --git a/spring-cloud-circuitbreaker-failsafe/pom.xml b/spring-cloud-circuitbreaker-failsafe/pom.xml new file mode 100644 index 00000000..f84cff62 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/pom.xml @@ -0,0 +1,41 @@ + + + + spring-cloud-circuitbreaker + org.springframework.cloud + 1.0.0.BUILD-SNAPSHOT + .. + + 4.0.0 + + spring-cloud-circuitbreaker-failsafe + + + + org.springframework.cloud + spring-cloud-commons + + + org.springframework.boot + spring-boot-starter + + + 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..9d9a28b8 --- /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.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.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..55644d30 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreaker.java @@ -0,0 +1,68 @@ +/* + * 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.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.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.Customizer; + +/** + * @author Jakub Marchwicki + */ +public class FailsafeCircuitBreaker implements CircuitBreaker { + + private final String id; + + private final FailsafeConfigBuilder.FailsafeConfig config; + + private final Optional> failsafeCustomizer; + + public FailsafeCircuitBreaker(String id, FailsafeConfigBuilder.FailsafeConfig config, + Optional> failsafeCustomizer) { + this.id = id; + this.config = config; + this.failsafeCustomizer = failsafeCustomizer; + } + + @Override + public T run(Supplier toRun, Function fallback) { + + Fallback failsafeFallback = Fallback.of(extract(fallback)); + 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( + 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..557a8ae8 --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerFactory.java @@ -0,0 +1,69 @@ +/* + * 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.Optional; +import java.util.function.Function; + +import net.jodah.failsafe.FailsafeExecutor; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.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, + Optional.ofNullable(failsafeCustomizers.get(id))); + } + + public void addFailsafeCustomizers(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..ffbcd4bd --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/main/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeConfigBuilder.java @@ -0,0 +1,99 @@ +/* + * 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 net.jodah.failsafe.CircuitBreaker; +import net.jodah.failsafe.RetryPolicy; + +import org.springframework.cloud.client.circuitbreaker.ConfigBuilder; + +/** + * Allows consumers to easily construct a {@link FailsafeConfig} object. + * + * @author Jakub Marchwicki + */ +public class FailsafeConfigBuilder + implements ConfigBuilder { + + private String id; + + private RetryPolicy retryPolicy = new RetryPolicy<>(); + + private CircuitBreaker circuitBreaker = new CircuitBreaker<>(); + + /** + * Constructor. + * @param id The id of the circuit breaker. + */ + public FailsafeConfigBuilder(String id) { + this.id = id; + } + + public FailsafeConfigBuilder retryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + return this; + } + + public FailsafeConfigBuilder circuitBreaker(CircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; + return this; + } + + @Override + public FailsafeConfig build() { + FailsafeConfig config = new FailsafeConfig(); + config.setId(id); + config.setRetryPolicy(retryPolicy); + config.setCircuitBreaker(circuitBreaker); + return config; + } + + public static class FailsafeConfig { + + private String id; + + private RetryPolicy retryPolicy; + + private CircuitBreaker circuitBreaker; + + 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; + } + + } + +} 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..8a2dff2e --- /dev/null +++ b/spring-cloud-circuitbreaker-failsafe/src/test/java/org/springframework/cloud/circuitbreaker/failsafe/FailsafeCircuitBreakerIntegrationTest.java @@ -0,0 +1,159 @@ +/* + * 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 net.jodah.failsafe.RetryPolicy; +import net.jodah.failsafe.function.CheckedConsumer; +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.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.Customizer; +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.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; +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; + + @Autowired + CheckedConsumer onFailureConsumer; + + @Test + public void testSlow() throws Exception { + assertThat(service.slow()).isEqualTo("fallback"); + service.verifyTimesSlowInvoked(); + verify(onFailureConsumer, times(11)).accept(any()); + } + + @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 CheckedConsumer onFailureConsumer() { + return mock(CheckedConsumer.class); + } + + @Bean + public Customizer factoryCustomizer( + CheckedConsumer onFailureConsumer) { + 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.addFailsafeCustomizers( + failsafe -> failsafe.onFailure(onFailureConsumer), "slow"); + }; + } + + @Service + public static class DemoControllerService { + + private TestRestTemplate rest; + + private CircuitBreakerFactory cbFactory; + + DemoControllerService(TestRestTemplate rest, + CircuitBreakerFactory cbFactory) { + this.rest = spy(rest); + this.cbFactory = cbFactory; + } + + String slow() { + 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"); + } + + String normal() { + return cbFactory.create("normal").run( + () -> rest.getForObject("/normal", String.class), + t -> "fallback"); + } + + void verifyTimesSlowInvoked() { + verify(rest, times(1)).getForObject(eq("/slow"), eq(String.class)); + } + + } + + } + +} 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..b38673f9 --- /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.client.circuitbreaker.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"); + } + +} diff --git a/spring-cloud-starter-circuitbreaker/pom.xml b/spring-cloud-starter-circuitbreaker/pom.xml index 41ee38e4..ecfb3b78 100644 --- a/spring-cloud-starter-circuitbreaker/pom.xml +++ b/spring-cloud-starter-circuitbreaker/pom.xml @@ -14,6 +14,7 @@ spring-cloud-starter-circuitbreaker-resilience4j spring-cloud-starter-circuitbreaker-spring-retry spring-cloud-starter-circuitbreaker-reactor-resilience4j + spring-cloud-starter-circuitbreaker-failsafe pom 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 new file mode 100644 index 00000000..035321c8 --- /dev/null +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/.flattened-pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-starter-circuitbreaker + 1.0.0.BUILD-SNAPSHOT + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-failsafe + 1.0.0.BUILD-SNAPSHOT + Spring Cloud parent pom, managing plugins and dependencies for Spring + Cloud projects + https://spring.io/spring-cloud/spring-cloud-circuitbreaker/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe + + Pivotal Software, Inc. + https://www.spring.io + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + Copyright 2014-2015 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. + + + + + dsyer + Dave Syer + dsyer at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + lead + + + + sgibb + Spencer Gibb + sgibb at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + lead + + + + mgrzejszczak + Marcin Grzejszczak + mgrzejszczak at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + rbaxter + Ryan Baxter + rbaxter at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + omaciaszeksharma + Olga Maciaszek-Sharma + omaciaszeksharma at pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + developer + + + + + scm:git:git://github.com/spring-cloud/spring-cloud-build.git/spring-cloud-circuitbreaker/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-build.git/spring-cloud-circuitbreaker/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe + https://github.com/spring-cloud/spring-cloud-build/spring-cloud-circuitbreaker/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe + + + + org.springframework.cloud + spring-cloud-starter + 2.2.0.BUILD-SNAPSHOT + compile + + + net.jodah + failsafe + 2.3.1 + compile + + + 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..2d5a93fd --- /dev/null +++ b/spring-cloud-starter-circuitbreaker/spring-cloud-starter-circuitbreaker-failsafe/pom.xml @@ -0,0 +1,26 @@ + + + + spring-cloud-starter-circuitbreaker + org.springframework.cloud + 1.0.0.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-starter-circuitbreaker-failsafe + + + + org.springframework.cloud + spring-cloud-starter + + + net.jodah + failsafe + + + + +