Skip to content

Commit ee5a465

Browse files
Robert WinklerRobWin
Robert Winkler
authored andcommitted
Issue ReactiveX#13 First draft to publish events as a reactive stream.
1 parent b85c272 commit ee5a465

20 files changed

+275
-146
lines changed

README.adoc

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,32 @@
44

55
image:https://travis-ci.org/javaslang/javaslang-circuitbreaker.svg?branch=master["Build Status", link="https://travis-ci.org/javaslang/javaslang-circuitbreaker"] image:https://coveralls.io/repos/javaslang/javaslang-circuitbreaker/badge.svg["Coverage Status", link="https://coveralls.io/r/javaslang/javaslang-circuitbreaker"] image:https://api.bintray.com/packages/robwin/maven/javaslang-circuitbreaker/images/download.svg[link="https://bintray.com/robwin/maven/javaslang-circuitbreaker/_latestVersion"] image:http://img.shields.io/badge/license-ASF2-blue.svg["Apache License 2", link="http://www.apache.org/licenses/LICENSE-2.0.txt"]
66

7-
This library is a lightweight, easy-to-use fault tolerance library inspired by https://github.com/Netflix/Hystrix[Netflix Hystrix], but designed solely for Java 8 and functional programming. Lightweight, because the library only uses SLF4J and a functional library for Java 8 called https://github.com/javaslang/javaslang[Javaslang]. Javaslang itself has no external library dependencies.
8-
The library provides several higher-order functions to decorate any function with a http://martinfowler.com/bliki/CircuitBreaker.html[Circuit Breaker]. The basic idea behind a CircuitBreaker is simple. You decorate a protected function (e.g. a remote call to a backend system) in a CircuitBreaker, which monitors for failures. Once the failures reach a certain failure threshold, the CircuitBreaker trips, and all further calls return with an exception, without the protected call being made at all. After a certain time duration, the CircuitBreaker allows calls again to see if the backend system is still unreliable or has become available again.
9-
In the following I call the higher-order functions decorators. The decorators return an enhanced version of your function. Furthermore, the library provides a decorator to retry failed functions. You can stack more than one decorator on any given function. That means, you can combine a Retry decorator with a CircuitBreaker decorator. Any decorated function can be invoked synchronously or asynchronously.
7+
This library is a lightweight, easy-to-use fault tolerance library inspired by https://github.com/Netflix/Hystrix[Netflix Hystrix], but designed solely for Java 8 and functional programming. Lightweight, because the library only uses https://github.com/javaslang/javaslang[Javaslang], https://github.com/ReactiveX/RxJava[RxJava] and SLF4J-API. These libraries do no have any other external library dependencies.
8+
9+
To highlight a few differences to Netflix Hystrix:
10+
11+
Executable logic can be passed as simple functions, lambda expressions or method references. In Hystrix, executable logic needs to be wrapped inside a HystrixCommand`.
12+
13+
This library provides higher-order functions to decorate any function, lambda expression or method reference with a http://martinfowler.com/bliki/CircuitBreaker.html[Circuit Breaker]. In the following I call the higher-order functions decorators. The decorators return an enhanced version of your function. Furthermore, the library provides a decorator to retry failed functions. You can stack more than one decorator on any given function. That means, you can combine a Retry decorator with a CircuitBreaker decorator. Any decorated function can be invoked synchronously or asynchronously.
14+
15+
Example:
16+
17+
[source,java]
18+
----
19+
CircuitBreaker circuitBreaker = CircuitBreakerRegistry.ofDefaults()
20+
.circuitBreaker("backendName");
21+
Retry retryContext = Retry.ofDefaults();
22+
23+
Try.CheckedSupplier<String> supplier = Retry.decorateCheckedSupplier(retryContext,
24+
CircuitBreaker.decorateCheckedSupplier(circuitBreaker, helloWorldService::returnHelloWorld));
25+
Try<String> result = Try.of(supplier)
26+
.recover(throwable -> "Hello from Recovery");
27+
----
28+
1029

1130
The CircuitBreaker is implemented via a finite state machine with three states: `CLOSED`, `OPEN` and `HALF_OPEN`.
1231

13-
image::src/docs/asciidoc/images/state_machine.jpg[]
32+
image::images/state_machine.jpg[]
1433

1534
The CircuitBreaker does not know anything about the backend's state by itself, but uses the information provided by the decorators via `CircuitBreaker::recordSuccess()` and `CircuitBreaker::recordFailure(throwable)`. See example:
1635

@@ -36,15 +55,14 @@ Then, all access to the backend is blocked for a (configurable) time duration. `
3655

3756
The CircuitBreaker uses a Ring Bit Buffer in the `CLOSED` state to store the success or failure statuses of the calls. A successful call is stored as a `0` bit and a failed call is stored as a `1` bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html[BitSet] to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.
3857

39-
image::src/docs/asciidoc/images/ring_buffer.jpg[Ring Bit Buffer]
58+
image::images/ring_buffer.jpg[Ring Bit Buffer]
4059

4160
The Ring Bit Buffer must be full, before the failure rate can be calculated.
4261
For example, if the size of the Ring Buffer is 10, then at least 10 calls must evaluated, before the failure rate can be calculated. If only 9 calls have been evaluated the CircuitBreaker will not trip open even if all 9 calls have failed.
4362

4463
After the time duration has elapsed, the CircuitBreaker state changes from `OPEN` to `HALF_OPEN` and allows calls to see if the backend is still unavailable or has become available again. The CircuitBreaker uses another (configurable) Ring Bit Buffer to evaluate the failure rate in the `HALF_OPEN` state. If the failure rate is above the configured threshold, the state changes back to `OPEN`. If the failure rate is below or equal to the threshold, the state changes back to `CLOSED`.
4564
`CircuitBreaker::recordFailure(exception)` checks if the exception should be recorded as a failure or should be ignored. You can configure a custom `Predicate` which decides whether an exception should be recorded as a failure. The default Predicate records all exceptions as a failure.
4665

47-
4866
== Usage guide
4967

5068
See http://javaslang.github.io/javaslang-circuitbreaker/0.5.0/[User Guide].

src/docs/asciidoc/introduction.adoc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
11
== Introduction
22

3-
This library is a lightweight, easy-to-use fault tolerance library inspired by https://github.com/Netflix/Hystrix[Netflix Hystrix], but designed solely for Java 8 and functional programming. Lightweight, because the library only uses SLF4J and a functional library for Java 8 called https://github.com/javaslang/javaslang[Javaslang]. Javaslang itself has no external library dependencies.
4-
The library provides several higher-order functions to decorate any function with a http://martinfowler.com/bliki/CircuitBreaker.html[Circuit Breaker]. In the following I call the higher-order functions decorators. The decorators return an enhanced version of your function. Furthermore, the library provides a decorator to retry failed functions. You can stack more than one decorator on any given function. That means, you can combine a Retry decorator with a CircuitBreaker decorator. Any decorated function can be invoked synchronously or asynchronously.
3+
This library is a lightweight, easy-to-use fault tolerance library inspired by https://github.com/Netflix/Hystrix[Netflix Hystrix], but designed solely for Java 8 and functional programming. Lightweight, because the library only uses https://github.com/javaslang/javaslang[Javaslang], https://github.com/ReactiveX/RxJava[RxJava] and SLF4J-API. These libraries do no have any other external library dependencies.
4+
5+
To highlight a few differences to Netflix Hystrix:
6+
7+
Executable logic can be passed as simple functions, lambda expressions or method references. In Hystrix, executable logic needs to be wrapped inside a HystrixCommand`.
8+
9+
This library provides higher-order functions to decorate any function, lambda expression or method reference with a http://martinfowler.com/bliki/CircuitBreaker.html[Circuit Breaker]. In the following I call the higher-order functions decorators. The decorators return an enhanced version of your function. Furthermore, the library provides a decorator to retry failed functions. You can stack more than one decorator on any given function. That means, you can combine a Retry decorator with a CircuitBreaker decorator. Any decorated function can be invoked synchronously or asynchronously.
10+
11+
Example:
12+
13+
[source,java]
14+
----
15+
CircuitBreaker circuitBreaker = CircuitBreakerRegistry.ofDefaults()
16+
.circuitBreaker("backendName");
17+
Retry retryContext = Retry.ofDefaults();
18+
19+
Try.CheckedSupplier<String> supplier = Retry.decorateCheckedSupplier(retryContext,
20+
CircuitBreaker.decorateCheckedSupplier(circuitBreaker, helloWorldService::returnHelloWorld));
21+
Try<String> result = Try.of(supplier)
22+
.recover(throwable -> "Hello from Recovery");
23+
----
24+
525

626
The CircuitBreaker is implemented via a finite state machine with three states: `CLOSED`, `OPEN` and `HALF_OPEN`.
727

src/docs/asciidoc/usage_guide.adoc

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,18 @@ The following example shows how to ignore an `IOException`, but all other except
8383
include::../../test/java/javaslang/circuitbreaker/CircuitBreakerTest.java[tags=shouldNotRecordIOExceptionAsAFailure]
8484
----
8585

86-
=== Customize the event listener
87-
The default event listener logs state transitions with INFO level.
88-
89-
----
90-
INFO i.g.r.c.i.DefaultCircuitBreakerEventListener - CircuitBreaker 'testName' changes state from CLOSED to OPEN
91-
----
92-
93-
If you want to use a custom event listener, you have to implement the functional interface `CircuitBreakerEventListener` which has a method `onCircuitBreakerEvent(CircuitBreakerEvent circuitBreakerEvent)`. The only event which currently exists is `CircuitBreakerStateTransitionEvent`.
86+
=== Observe the event stream
87+
A CircuitBreaker publishes a stream of CircuitBreakerEvents to any Observer/Consumer who subscribes. This library uses RxJava to to provide this functionality.
88+
If you want to consume events, you have to subscribe to the event stream. This library provides a consumer `CircuitBreakerEventConsumer` which can be used to store events in a circular buffer with a fixed capacity.
89+
You can use RxJava to filter certain events.
9490

9591
[source,java]
9692
----
97-
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
98-
.onCircuitBreakerEvent((event) -> LOG.info(event.toString()))
99-
.build();
93+
CircuitBreakerEventConsumer ringBuffer = new CircuitBreakerEventConsumer(10);
94+
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
95+
circuitBreaker.observeCircuitBreakerEvents()
96+
.filter(event -> event.getEventType() == Type.RECORDED_FAILURE)
97+
.subscribe(ringBuffer);
10098
----
10199

102100
=== Retry a failed function

src/main/java/javaslang/circuitbreaker/CircuitBreaker.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,36 +55,36 @@ public interface CircuitBreaker {
5555
void recordSuccess();
5656

5757
/**
58-
* Get the name of this CircuitBreaker
58+
* Returns the name of this CircuitBreaker
5959
*
6060
* @return the name of this CircuitBreaker
6161
*/
6262
String getName();
6363

6464
/**
65-
* Get the state of this CircuitBreaker
65+
* Returns the state of this CircuitBreaker
6666
*
6767
* @return the state of this CircuitBreaker
6868
*/
6969
State getState();
7070

7171
/**
72-
* Get the CircuitBreakerConfig of this CircuitBreaker.
72+
* Returns the CircuitBreakerConfig of this CircuitBreaker.
7373
*
7474
* @return the CircuitBreakerConfig of this CircuitBreaker
7575
*/
7676
CircuitBreakerConfig getCircuitBreakerConfig();
7777

7878
/**
79-
* Get the Metrics of this CircuitBreaker.
79+
* Returns the Metrics of this CircuitBreaker.
8080
*
8181
* @return the Metrics of this CircuitBreaker
8282
*/
8383
Metrics getMetrics();
8484

8585

8686
/**
87-
* Get an Observable of CircuitBreakerEvents which can be subscribed
87+
* Returns an Observable of CircuitBreakerEvents which can be subscribed
8888
*
8989
* @return an Observable of CircuitBreakerEvents which can be subscribed
9090
*/

src/main/java/javaslang/circuitbreaker/CircuitBreakerConfig.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919
package javaslang.circuitbreaker;
2020

21-
import javaslang.circuitbreaker.internal.DefaultCircuitBreakerEventListener;
22-
2321
import java.time.Duration;
2422
import java.util.function.Predicate;
2523

@@ -30,13 +28,11 @@ public class CircuitBreakerConfig {
3028
public static final int DEFAULT_WAIT_DURATION_IN_OPEN_STATE = 60; // Seconds
3129
public static final int DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE = 10;
3230
public static final int DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE = 100;
33-
public static final int DEFAULT_EXCEPTION_RING_BUFFER_SIZE = 10;
3431

3532
private final float failureRateThreshold;
3633
private final int ringBufferSizeInHalfOpenState;
3734
private final int ringBufferSizeInClosedState;
3835
private final Duration waitDurationInOpenState;
39-
private final CircuitBreakerEventListener circuitBreakerEventListener;
4036
private final Predicate<Throwable> recordFailurePredicate;
4137

4238
private CircuitBreakerConfig(Context context){
@@ -45,7 +41,6 @@ private CircuitBreakerConfig(Context context){
4541
this.ringBufferSizeInHalfOpenState = context.ringBufferSizeInHalfOpenState;
4642
this.ringBufferSizeInClosedState = context.ringBufferSizeInClosedState;
4743
this.recordFailurePredicate = context.recordFailurePredicate;
48-
this.circuitBreakerEventListener = context.circuitBreakerEventListener;
4944

5045
}
5146

@@ -65,10 +60,6 @@ public int getRingBufferSizeInClosedState() {
6560
return ringBufferSizeInClosedState;
6661
}
6762

68-
public CircuitBreakerEventListener getCircuitBreakerEventListener() {
69-
return circuitBreakerEventListener;
70-
}
71-
7263
public Predicate<Throwable> getRecordFailurePredicate() {
7364
return recordFailurePredicate;
7465
}
@@ -162,23 +153,6 @@ public Builder ringBufferSizeInClosedState(int ringBufferSizeInClosedState) {
162153
return this;
163154
}
164155

165-
/**
166-
* Deprecated: Better handle events by subscribing to the reactive events stream of a CircuitBreaker instance.
167-
*
168-
* Configures the CircuitBreakerEventListener which should handle CircuitBreaker events.
169-
*
170-
* @param circuitBreakerEventListener the CircuitBreakerEventListener which should handle CircuitBreaker events
171-
* @return the CircuitBreakerConfig.Builder
172-
*/
173-
@Deprecated
174-
public Builder onCircuitBreakerEvent(CircuitBreakerEventListener circuitBreakerEventListener) {
175-
if (circuitBreakerEventListener == null) {
176-
throw new IllegalArgumentException("circuitBreakerEventListener must not be null");
177-
}
178-
context.circuitBreakerEventListener = circuitBreakerEventListener;
179-
return this;
180-
}
181-
182156
/**
183157
* Configures a Predicate which evaluates if an exception should be recorded as a failure and thus increase the failure rate.
184158
* The Predicate must return true if the exception should count as a failure, otherwise it must return false.
@@ -206,7 +180,6 @@ private static class Context {
206180
int ringBufferSizeInHalfOpenState = DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE;
207181
int ringBufferSizeInClosedState = DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE;
208182
Duration waitDurationInOpenState = Duration.ofSeconds(DEFAULT_WAIT_DURATION_IN_OPEN_STATE);
209-
CircuitBreakerEventListener circuitBreakerEventListener = new DefaultCircuitBreakerEventListener();
210183
// The default exception predicate counts all exceptions as failures.
211184
Predicate<Throwable> recordFailurePredicate = (exception) -> true;
212185
}

src/main/java/javaslang/circuitbreaker/CircuitBreakerEvent.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,24 @@ public interface CircuitBreakerEvent {
2626
/**
2727
* Returns the name of the CircuitBreaker which has created the event.
2828
*
29-
* @return the name of the CircuitBreaker which has created the event.
29+
* @return the name of the CircuitBreaker which has created the event
3030
*/
3131
String getCircuitBreakerName();
32+
33+
/**
34+
* Returns the type of the CircuitBreaker event.
35+
*
36+
* @return tthe type of the CircuitBreaker event
37+
*/
38+
Type getEventType();
39+
40+
/**
41+
* Event types which are created by a CircuitBreaker.
42+
*/
43+
enum Type {
44+
/** A CircuitBreakerEvent which informs that a failure has been recorded */
45+
RECORDED_FAILURE,
46+
/** A CircuitBreakerEvent which informs that a failure has been recorded */
47+
STATE_TRANSITION
48+
}
3249
}

src/main/java/javaslang/circuitbreaker/CircuitBreakerEventListener.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/main/java/javaslang/circuitbreaker/CircuitBreakerRecordedFailureEvent.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
package javaslang.circuitbreaker;
2020

2121
/**
22-
* A CircuitBreakerEvent which informs about a state transition.
22+
* A CircuitBreakerEvent which informs that a failure has been recorded
2323
*/
2424
public class CircuitBreakerRecordedFailureEvent implements CircuitBreakerEvent{
2525

@@ -36,6 +36,11 @@ public String getCircuitBreakerName() {
3636
return circuitBreakerName;
3737
}
3838

39+
@Override
40+
public Type getEventType() {
41+
return Type.RECORDED_FAILURE;
42+
}
43+
3944
@Override
4045
public String toString(){
4146
return String.format("CircuitBreaker '%s' recorded: '%s'", getCircuitBreakerName(), throwable.toString());

src/main/java/javaslang/circuitbreaker/CircuitBreakerStateTransitionEvent.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public String getCircuitBreakerName() {
4040
return circuitBreakerName;
4141
}
4242

43+
@Override
44+
public Type getEventType() {
45+
return Type.STATE_TRANSITION;
46+
}
47+
4348
@Override
4449
public String toString(){
4550
return String.format("CircuitBreaker '%s' changes state from %s to %s", getCircuitBreakerName(), getStateTransition().getFromState(), getStateTransition().getToState());

0 commit comments

Comments
 (0)