Skip to content

Commit 8ec1dad

Browse files
author
Robert Winkler
committed
First implementation of issue ReactiveX#3
1 parent 5e3b02c commit 8ec1dad

File tree

11 files changed

+196
-8
lines changed

11 files changed

+196
-8
lines changed

src/main/java/io/github/robwin/circuitbreaker/CircuitBreakerConfig.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import java.util.ArrayList;
2222
import java.util.List;
23+
import java.util.Optional;
24+
2325

2426
public class CircuitBreakerConfig {
2527

@@ -30,13 +32,19 @@ public class CircuitBreakerConfig {
3032
private final int maxFailures;
3133
// The wait interval which specifies how long the CircuitBreaker should stay OPEN
3234
private final int waitInterval;
35+
// The CircuitBreakerEventListener which should handle CircuitBreaker events.
36+
private Optional<CircuitBreakerEventListener> circuitBreakerEventListener;
3337
// Exceptions which do not count as failures and thus not trigger the circuit breaker.
3438
private final List<Class<? extends Throwable>> ignoredExceptions;
3539

36-
private CircuitBreakerConfig(int maxFailures, int waitInterval, List<Class<? extends Throwable>> ignoredExceptions){
40+
private CircuitBreakerConfig(int maxFailures,
41+
int waitInterval,
42+
List<Class<? extends Throwable>> ignoredExceptions,
43+
Optional<CircuitBreakerEventListener> circuitBreakerEventListener){
3744
this.maxFailures = maxFailures;
3845
this.waitInterval = waitInterval;
3946
this.ignoredExceptions = ignoredExceptions;
47+
this.circuitBreakerEventListener = circuitBreakerEventListener;
4048
}
4149

4250
public Integer getMaxFailures() {
@@ -51,6 +59,10 @@ public List<Class<? extends Throwable>> getIgnoredExceptions() {
5159
return ignoredExceptions;
5260
}
5361

62+
public Optional<CircuitBreakerEventListener> getCircuitBreakerEventListener() {
63+
return circuitBreakerEventListener;
64+
}
65+
5466
/**
5567
* Returns a builder to create a custom CircuitBreakerConfig.
5668
*
@@ -63,8 +75,15 @@ public static CircuitBreakerConfig.Builder custom(){
6375
public static class Builder {
6476
private int maxFailures = DEFAULT_MAX_FAILURES;
6577
private int waitInterval = DEFAULT_WAIT_INTERVAL;
78+
private Optional<CircuitBreakerEventListener> circuitBreakerEventListener = Optional.empty();
6679
private List<Class<? extends Throwable>> ignoredExceptions = new ArrayList<>();
6780

81+
/**
82+
* Configures the maximum number of allowed failures.
83+
*
84+
* @param maxFailures the maximum number of allowed failures
85+
* @return the CircuitBreakerConfig.Builder
86+
*/
6887
public Builder maxFailures(int maxFailures) {
6988
if (maxFailures < 1) {
7089
throw new IllegalArgumentException("maxFailures must be greater than or equal to 1");
@@ -73,6 +92,12 @@ public Builder maxFailures(int maxFailures) {
7392
return this;
7493
}
7594

95+
/**
96+
* Configures the wait interval in milliseconds which specifies how long the CircuitBreaker should stay OPEN
97+
*
98+
* @param waitInterval the wait interval in milliseconds which specifies how long the CircuitBreaker should stay OPEN
99+
* @return the CircuitBreakerConfig.Builder
100+
*/
76101
public Builder waitInterval(int waitInterval) {
77102
if (waitInterval < 100) {
78103
throw new IllegalArgumentException("waitInterval must be at least 100[ms]");
@@ -81,6 +106,12 @@ public Builder waitInterval(int waitInterval) {
81106
return this;
82107
}
83108

109+
/**
110+
* Configures an Exception which does not count as a failure and thus does not trigger the circuit breaker.
111+
*
112+
* @param ignoredException an Exception which should not count as a failure
113+
* @return the CircuitBreakerConfig.Builder
114+
*/
84115
public Builder ignoredException(Class<? extends Throwable> ignoredException) {
85116
if (ignoredException == null) {
86117
throw new IllegalArgumentException("ignoredException must not be null");
@@ -89,6 +120,12 @@ public Builder ignoredException(Class<? extends Throwable> ignoredException) {
89120
return this;
90121
}
91122

123+
/**
124+
* Configures Exceptions which do not count as failures and thus does not trigger the circuit breaker.
125+
*
126+
* @param ignoredExceptions Exceptions which should not count as failures
127+
* @return the CircuitBreakerConfig.Builder
128+
*/
92129
public Builder ignoredExceptions(List<Class<? extends Throwable>> ignoredExceptions) {
93130
if (ignoredExceptions == null) {
94131
throw new IllegalArgumentException("ignoredExceptions must not be null");
@@ -97,8 +134,29 @@ public Builder ignoredExceptions(List<Class<? extends Throwable>> ignoredExcepti
97134
return this;
98135
}
99136

137+
/**
138+
* Configures the CircuitBreakerEventListener which should handle CircuitBreaker events.
139+
*
140+
* @param circuitBreakerEventListener the CircuitBreakerEventListener which should handle CircuitBreaker events.
141+
* @return the CircuitBreakerConfig.Builder
142+
*/
143+
public Builder onCircuitBreakerEvent(CircuitBreakerEventListener circuitBreakerEventListener) {
144+
if (circuitBreakerEventListener == null) {
145+
throw new IllegalArgumentException("circuitBreakerEventListener must not be null");
146+
}
147+
this.circuitBreakerEventListener = Optional.of(circuitBreakerEventListener);
148+
return this;
149+
}
150+
151+
/**
152+
* Builds a CircuitBreakerConfig
153+
*
154+
* @return the CircuitBreakerConfig
155+
*/
100156
public CircuitBreakerConfig build() {
101-
return new CircuitBreakerConfig(maxFailures, waitInterval, ignoredExceptions);
157+
return new CircuitBreakerConfig(maxFailures, waitInterval, ignoredExceptions, circuitBreakerEventListener);
102158
}
159+
160+
103161
}
104162
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
*
3+
* Copyright 2015 Robert Winkler
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.robwin.circuitbreaker;
20+
21+
public interface CircuitBreakerEvent {
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
*
3+
* Copyright 2015 Robert Winkler
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.robwin.circuitbreaker;
20+
21+
@FunctionalInterface
22+
public interface CircuitBreakerEventListener {
23+
24+
void onCircuitBreakerEvent(CircuitBreakerEvent circuitBreakerEvent);
25+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
*
3+
* Copyright 2015 Robert Winkler
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.robwin.circuitbreaker;
20+
21+
public class CircuitBreakerStateChangeEvent implements CircuitBreakerEvent{
22+
23+
private CircuitBreaker.State previousState, newState;
24+
25+
public CircuitBreakerStateChangeEvent(CircuitBreaker.State previousState, CircuitBreaker.State newState) {
26+
this.previousState = previousState;
27+
this.newState = newState;
28+
}
29+
30+
public CircuitBreaker.State getPreviousState() {
31+
return previousState;
32+
}
33+
34+
public CircuitBreaker.State getNewState() {
35+
return newState;
36+
}
37+
}

src/main/java/io/github/robwin/circuitbreaker/CircuitBreakerStateMachine.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,24 @@ public String toString() {
105105
return String.format("CircuitBreaker '%s'", this.name);
106106
}
107107

108-
void resetState() {
108+
void resetState(CircuitBreakerState currentState) {
109109
stateReference.set(new ClosedState(this));
110+
circuitBreakerConfig.getCircuitBreakerEventListener()
111+
.ifPresent(listener ->
112+
listener.onCircuitBreakerEvent(new CircuitBreakerStateChangeEvent(currentState.getState(), State.CLOSED)));
110113
}
111114

112115
void transitionToOpenState(CircuitBreakerState currentState) {
113116
stateReference.set(new OpenState(this, currentState));
117+
circuitBreakerConfig.getCircuitBreakerEventListener()
118+
.ifPresent(listener ->
119+
listener.onCircuitBreakerEvent(new CircuitBreakerStateChangeEvent(currentState.getState(), State.OPEN)));
114120
}
115121

116122
void transitionToHalfClosedState(CircuitBreakerState currentState) {
117123
stateReference.set(new HalfClosedState(this, currentState));
124+
circuitBreakerConfig.getCircuitBreakerEventListener()
125+
.ifPresent(listener ->
126+
listener.onCircuitBreakerEvent(new CircuitBreakerStateChangeEvent(currentState.getState(), State.HALF_CLOSED)));
118127
}
119128
}

src/main/java/io/github/robwin/circuitbreaker/ClosedState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void recordFailure() {
5555
*/
5656
@Override
5757
public void recordSuccess() {
58-
stateMachine.resetState();
58+
stateMachine.resetState(this);
5959
}
6060

6161
/**

src/main/java/io/github/robwin/circuitbreaker/HalfClosedState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void recordFailure() {
5151
*/
5252
@Override
5353
public void recordSuccess() {
54-
stateMachine.resetState();
54+
stateMachine.resetState(this);
5555
}
5656

5757
/**

src/main/java/io/github/robwin/circuitbreaker/OpenState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void recordFailure() {
5353
*/
5454
@Override
5555
public void recordSuccess() {
56-
stateMachine.resetState();
56+
stateMachine.resetState(this);
5757
}
5858

5959
/**

src/test/java/io/github/robwin/circuitbreaker/CircuitBreakerConfigTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@
1818
*/
1919
package io.github.robwin.circuitbreaker;
2020

21+
import javaslang.control.Match;
2122
import org.junit.Test;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import static org.assertj.core.api.BDDAssertions.then;
2227

2328
public class CircuitBreakerConfigTest {
2429

30+
private static Logger LOG = LoggerFactory.getLogger(CircuitBreakerConfigTest.class);
31+
2532
@Test(expected = IllegalArgumentException.class)
2633
public void zeroMaxFailuresShouldFail() {
2734
CircuitBreakerConfig.custom().maxFailures(0).build();
@@ -31,4 +38,34 @@ public void zeroMaxFailuresShouldFail() {
3138
public void zeroWaitIntervalShouldFail() {
3239
CircuitBreakerConfig.custom().waitInterval(0).build();
3340
}
41+
42+
@Test()
43+
public void shouldSetMaxFailures() {
44+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().maxFailures(5).build();
45+
then(circuitBreakerConfig.getMaxFailures()).isEqualTo(5);
46+
}
47+
48+
@Test()
49+
public void shouldSetWaitInterval() {
50+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().waitInterval(1000).build();
51+
then(circuitBreakerConfig.getWaitInterval()).isEqualTo(1000);
52+
}
53+
54+
@Test
55+
public void shouldAddACircuitBreakerEventListener() {
56+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
57+
.onCircuitBreakerEvent((CircuitBreakerEvent circuitBreakerEvent)
58+
-> Match.of(circuitBreakerEvent)
59+
.whenType(CircuitBreakerStateChangeEvent.class)
60+
.then((event) -> event.getNewState().toString()))
61+
.build();
62+
then(circuitBreakerConfig.getCircuitBreakerEventListener().isPresent()).isTrue();
63+
}
64+
65+
@Test
66+
public void circuitBreakerEventListenerShouldBeEmpty() {
67+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
68+
.build();
69+
then(circuitBreakerConfig.getCircuitBreakerEventListener().isPresent()).isFalse();
70+
}
3471
}

src/test/java/io/github/robwin/circuitbreaker/MonitorRegistryTest.java renamed to src/test/java/io/github/robwin/circuitbreaker/CircuitBreakerRegistryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import static org.assertj.core.api.BDDAssertions.assertThat;
2525

2626

27-
public class MonitorRegistryTest {
27+
public class CircuitBreakerRegistryTest {
2828

2929
private CircuitBreakerRegistry circuitBreakerRegistry;
3030

0 commit comments

Comments
 (0)