@Test public void shouldThrowCircuitBreakerOpenException() { // tag::shouldThrowCircuitBreakerOpenException[] // Given CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(2) .waitDurationInOpenState(Duration.ofMillis(1000)) .build(); CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("uniqueName"); // Simulate a failure attempt circuitBreaker.recordFailure(new RuntimeException()); // CircuitBreaker is still CLOSED, because 1 failure is allowed assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // Simulate a failure attempt circuitBreaker.recordFailure(new RuntimeException()); // CircuitBreaker is OPEN, because the failure rate is above 50% assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // When I decorate my function and invoke the decorated function Try<String> result = Try.of(CircuitBreaker.decorateCheckedSupplier(() -> "Hello", circuitBreaker)) .map(value -> value + " world"); // Then the call fails, because CircuitBreaker is OPEN assertThat(result.isFailure()).isTrue(); // Exception is CircuitBreakerOpenException assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class); // end::shouldThrowCircuitBreakerOpenException[] }
@Test public void shouldReturnFailureWithCircuitBreakerOpenException() { // Given // Create a custom configuration for a CircuitBreaker CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(2) .ringBufferSizeInHalfOpenState(2) .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofMillis(1000)) .build(); // Create a CircuitBreakerRegistry with a custom global configuration CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); circuitBreaker.recordFailure(new RuntimeException()); circuitBreaker.recordFailure(new RuntimeException()); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // When Try.CheckedRunnable checkedRunnable = CircuitBreaker.decorateCheckedRunnable( () -> { throw new RuntimeException("BAM!"); }, circuitBreaker); Try result = Try.run(checkedRunnable); // Then assertThat(result.isFailure()).isTrue(); assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class); }
@Test public void shouldChainDecoratedFunctions() throws ExecutionException, InterruptedException { // tag::shouldChainDecoratedFunctions[] // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); CircuitBreaker anotherCircuitBreaker = circuitBreakerRegistry.circuitBreaker("anotherTestName"); // When I create a Supplier and a Function which are decorated by different CircuitBreakers Try.CheckedSupplier<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(() -> "Hello", circuitBreaker); Try.CheckedFunction<String, String> decoratedFunction = CircuitBreaker.decorateCheckedFunction((input) -> input + " world", anotherCircuitBreaker); // and I chain a function with map Try<String> result = Try.of(decoratedSupplier).mapTry(decoratedFunction::apply); // Then assertThat(result.isSuccess()).isTrue(); assertThat(result.get()).isEqualTo("Hello world"); // end::shouldChainDecoratedFunctions[] }
@Test public void shouldReturnSuccess() { // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // When Supplier<String> checkedSupplier = CircuitBreaker.decorateSupplier(() -> "Hello world", circuitBreaker); // Then assertThat(checkedSupplier.get()).isEqualTo("Hello world"); }
@Test public void shouldReturnFailureWithRuntimeException() { // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // When Try.CheckedRunnable checkedRunnable = CircuitBreaker.decorateCheckedRunnable( () -> { throw new RuntimeException("BAM!"); }, circuitBreaker); Try result = Try.run(checkedRunnable); // Then assertThat(result.isFailure()).isTrue(); assertThat(result.failed().get()).isInstanceOf(RuntimeException.class); }
@Test public void shouldInvokeAsyncApply() throws ExecutionException, InterruptedException { // tag::shouldInvokeAsyncApply[] // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // When Supplier<String> decoratedSupplier = CircuitBreaker.decorateSupplier( () -> "This can be any method which returns: 'Hello", circuitBreaker); CompletableFuture<String> future = CompletableFuture.supplyAsync(decoratedSupplier).thenApply(value -> value + " world'"); // Then assertThat(future.get()).isEqualTo("This can be any method which returns: 'Hello world'"); // end::shouldInvokeAsyncApply[] }
@Test public void shouldInvokeMap() { // tag::shouldInvokeMap[] // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); // When I decorate my function Try.CheckedSupplier<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier( () -> "This can be any method which returns: 'Hello", circuitBreaker); // and chain an other function with map Try<String> result = Try.of(decoratedSupplier).map(value -> value + " world'"); // Then the Try Monad returns a Success<String>, if all functions ran successfully. assertThat(result.isSuccess()).isTrue(); assertThat(result.get()).isEqualTo("This can be any method which returns: 'Hello world'"); // end::shouldInvokeMap[] }
@Test public void shouldNotRecordIOExceptionAsAFailure() { // tag::shouldNotRecordIOExceptionAsAFailure[] // Given CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(2) .ringBufferSizeInHalfOpenState(2) .waitDurationInOpenState(Duration.ofMillis(1000)) .recordFailure( throwable -> API.Match(throwable) .of(Case(instanceOf(WebServiceException.class), true), Case($(), false))) .build(); CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); // Simulate a failure attempt circuitBreaker.recordFailure(new RuntimeException()); // CircuitBreaker is still CLOSED, because 1 failure is allowed assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // When Try.CheckedRunnable checkedRunnable = CircuitBreaker.decorateCheckedRunnable( () -> { throw new SocketTimeoutException("BAM!"); }, circuitBreaker); Try result = Try.run(checkedRunnable); // Then assertThat(result.isFailure()).isTrue(); // CircuitBreaker is still CLOSED, because SocketTimeoutException has not been recorded as a // failure assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThat(result.failed().get()).isInstanceOf(IOException.class); // end::shouldNotRecordIOExceptionAsAFailure[] }
@Test public void shouldInvokeRecoverFunction() { // tag::shouldInvokeRecoverFunction[] // Given CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); // When I decorate my function and invoke the decorated function Try.CheckedSupplier<String> checkedSupplier = CircuitBreaker.decorateCheckedSupplier( () -> { throw new RuntimeException("BAM!"); }, circuitBreaker); Try<String> result = Try.of(checkedSupplier).recover(throwable -> "Hello Recovery"); // Then the function should be a success, because the exception could be recovered assertThat(result.isSuccess()).isTrue(); // and the result must match the result of the recovery function. assertThat(result.get()).isEqualTo("Hello Recovery"); // end::shouldInvokeRecoverFunction[] }