Skip to content

Commit 04ed487

Browse files
Add ResilienceCircuitBreakerFactory class
1 parent 275c854 commit 04ed487

8 files changed

+64
-73
lines changed

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/CircuitBreakerConfigParser.java

+1-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
@Slf4j
1111
public class CircuitBreakerConfigParser {
1212

13-
public static final String DEFAULT_CONFIG_KEY = "default";
1413
private static final Set<String> nonThresholdKeys =
1514
Set.of("enabled", "defaultCircuitBreakerKey", "defaultThresholds");
1615

@@ -32,7 +31,6 @@ public class CircuitBreakerConfigParser {
3231
"permittedNumberOfCallsInHalfOpenState";
3332
private static final String SLIDING_WINDOW_TYPE = "slidingWindowType";
3433
public static final String ENABLED = "enabled";
35-
public static final String DEFAULT_CIRCUIT_BREAKER_KEY = "defaultCircuitBreakerKey";
3634
public static final String DEFAULT_THRESHOLDS = "defaultThresholds";
3735

3836
public static <T> CircuitBreakerConfiguration.CircuitBreakerConfigurationBuilder<T> parseConfig(
@@ -43,10 +41,6 @@ public static <T> CircuitBreakerConfiguration.CircuitBreakerConfigurationBuilder
4341
builder.enabled(config.getBoolean(ENABLED));
4442
}
4543

46-
if (config.hasPath(DEFAULT_CIRCUIT_BREAKER_KEY)) {
47-
builder.defaultCircuitBreakerKey(config.getString(DEFAULT_CIRCUIT_BREAKER_KEY));
48-
}
49-
5044
if (config.hasPath(DEFAULT_THRESHOLDS)) {
5145
builder.defaultThresholds(
5246
buildCircuitBreakerThresholds(config.getConfig(DEFAULT_THRESHOLDS)));
@@ -62,7 +56,7 @@ public static <T> CircuitBreakerConfiguration.CircuitBreakerConfigurationBuilder
6256
key -> key, // Circuit breaker key
6357
key -> buildCircuitBreakerThresholds(config.getConfig(key))));
6458
builder.circuitBreakerThresholdsMap(circuitBreakerThresholdsMap);
65-
log.info("Loaded circuit breaker configs: {}", builder);
59+
log.debug("Loaded circuit breaker configs: {}", builder);
6660
return builder;
6761
}
6862

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/CircuitBreakerConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.hypertrace.circuitbreaker.grpcutils;
22

3+
import io.grpc.Status;
34
import java.util.Map;
45
import java.util.function.BiFunction;
6+
import java.util.function.Function;
57
import lombok.Builder;
68
import lombok.Value;
79
import org.hypertrace.core.grpcutils.context.RequestContext;
@@ -13,9 +15,14 @@ public class CircuitBreakerConfiguration<T> {
1315
BiFunction<RequestContext, T, String> keyFunction;
1416
@Builder.Default boolean enabled = false;
1517
// Default value be "global" if not override.
16-
@Builder.Default String defaultCircuitBreakerKey = "global";
18+
String defaultCircuitBreakerKey = "global";
1719
// Standard/default thresholds
1820
CircuitBreakerThresholds defaultThresholds;
1921
// Custom overrides for specific cases (less common)
2022
@Builder.Default Map<String, CircuitBreakerThresholds> circuitBreakerThresholdsMap = Map.of();
23+
24+
// New exception builder logic
25+
@Builder.Default
26+
Function<String, RuntimeException> exceptionBuilder =
27+
reason -> Status.RESOURCE_EXHAUSTED.withDescription(reason).asRuntimeException();
2128
}

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/resilience/ResilienceCircuitBreakerConfigConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.hypertrace.circuitbreaker.grpcutils.CircuitBreakerThresholds;
77

88
/** Utility class to parse CircuitBreakerConfiguration to Resilience4j CircuitBreakerConfig */
9-
public class ResilienceCircuitBreakerConfigConverter {
9+
class ResilienceCircuitBreakerConfigConverter {
1010

1111
public static Map<String, CircuitBreakerConfig> getCircuitBreakerConfigs(
1212
Map<String, CircuitBreakerThresholds> configurationMap) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.hypertrace.circuitbreaker.grpcutils.resilience;
2+
3+
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
4+
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
5+
import java.time.Clock;
6+
import java.util.Map;
7+
import org.hypertrace.circuitbreaker.grpcutils.CircuitBreakerConfiguration;
8+
9+
public class ResilienceCircuitBreakerFactory {
10+
public static ResilienceCircuitBreakerInterceptor getResilienceCircuitBreakerInterceptor(
11+
CircuitBreakerConfiguration<?> circuitBreakerConfiguration, Clock clock) {
12+
Map<String, CircuitBreakerConfig> resilienceCircuitBreakerConfigMap =
13+
ResilienceCircuitBreakerConfigConverter.getCircuitBreakerConfigs(
14+
circuitBreakerConfiguration.getCircuitBreakerThresholdsMap());
15+
CircuitBreakerRegistry resilicenceCircuitBreakerRegistry =
16+
new ResilienceCircuitBreakerRegistryProvider(
17+
circuitBreakerConfiguration.getDefaultThresholds())
18+
.getCircuitBreakerRegistry();
19+
ResilienceCircuitBreakerProvider resilienceCircuitBreakerProvider =
20+
new ResilienceCircuitBreakerProvider(
21+
resilicenceCircuitBreakerRegistry, resilienceCircuitBreakerConfigMap);
22+
return new ResilienceCircuitBreakerInterceptor(
23+
circuitBreakerConfiguration, clock, resilienceCircuitBreakerProvider);
24+
}
25+
}

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/resilience/ResilienceCircuitBreakerInterceptor.java

+3-28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package org.hypertrace.circuitbreaker.grpcutils.resilience;
22

33
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
4-
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
5-
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
64
import io.grpc.CallOptions;
75
import io.grpc.Channel;
86
import io.grpc.ClientCall;
@@ -24,40 +22,17 @@
2422
@Slf4j
2523
public class ResilienceCircuitBreakerInterceptor extends CircuitBreakerInterceptor {
2624

27-
private final CircuitBreakerRegistry resilicenceCircuitBreakerRegistry;
28-
private final Map<String, CircuitBreakerConfig> resilienceCircuitBreakerConfigMap;
2925
private final ResilienceCircuitBreakerProvider resilienceCircuitBreakerProvider;
3026
private final CircuitBreakerConfiguration<?> circuitBreakerConfiguration;
3127
private final Clock clock;
3228

33-
public ResilienceCircuitBreakerInterceptor(
34-
CircuitBreakerConfiguration<?> circuitBreakerConfiguration, Clock clock) {
35-
this.circuitBreakerConfiguration = circuitBreakerConfiguration;
36-
this.clock = clock;
37-
this.resilienceCircuitBreakerConfigMap =
38-
ResilienceCircuitBreakerConfigConverter.getCircuitBreakerConfigs(
39-
circuitBreakerConfiguration.getCircuitBreakerThresholdsMap());
40-
this.resilicenceCircuitBreakerRegistry =
41-
new ResilienceCircuitBreakerRegistryProvider(
42-
circuitBreakerConfiguration.getDefaultThresholds())
43-
.getCircuitBreakerRegistry();
44-
this.resilienceCircuitBreakerProvider =
45-
new ResilienceCircuitBreakerProvider(
46-
resilicenceCircuitBreakerRegistry, resilienceCircuitBreakerConfigMap);
47-
}
48-
49-
public ResilienceCircuitBreakerInterceptor(
29+
ResilienceCircuitBreakerInterceptor(
5030
CircuitBreakerConfiguration<?> circuitBreakerConfiguration,
5131
Clock clock,
52-
CircuitBreakerRegistry resilicenceCircuitBreakerRegistry,
5332
ResilienceCircuitBreakerProvider resilienceCircuitBreakerProvider) {
5433
this.circuitBreakerConfiguration = circuitBreakerConfiguration;
55-
this.resilienceCircuitBreakerConfigMap =
56-
ResilienceCircuitBreakerConfigConverter.getCircuitBreakerConfigs(
57-
circuitBreakerConfiguration.getCircuitBreakerThresholdsMap());
58-
this.resilicenceCircuitBreakerRegistry = resilicenceCircuitBreakerRegistry;
59-
this.resilienceCircuitBreakerProvider = resilienceCircuitBreakerProvider;
6034
this.clock = clock;
35+
this.resilienceCircuitBreakerProvider = resilienceCircuitBreakerProvider;
6136
}
6237

6338
@Override
@@ -100,7 +75,7 @@ public void sendMessage(ReqT message) {
10075
circuitBreaker.getState() == CircuitBreaker.State.HALF_OPEN
10176
? "Circuit Breaker is HALF-OPEN and rejecting excess requests"
10277
: "Circuit Breaker is OPEN and blocking requests";
103-
throw Status.RESOURCE_EXHAUSTED.withDescription(rejectionReason).asRuntimeException();
78+
throw config.getExceptionBuilder().apply(rejectionReason);
10479
}
10580
super.sendMessage(message);
10681
}

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/resilience/ResilienceCircuitBreakerProvider.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
/** Utility class to provide Resilience4j CircuitBreaker */
1212
@Slf4j
13-
public class ResilienceCircuitBreakerProvider {
13+
class ResilienceCircuitBreakerProvider {
1414

1515
private final CircuitBreakerRegistry circuitBreakerRegistry;
1616
private final Map<String, CircuitBreakerConfig> circuitBreakerConfigMap;
@@ -27,11 +27,7 @@ public CircuitBreaker getCircuitBreaker(String circuitBreakerKey) {
2727
return circuitBreakerCache.computeIfAbsent(
2828
circuitBreakerKey,
2929
key -> {
30-
CircuitBreaker circuitBreaker =
31-
Optional.ofNullable(circuitBreakerConfigMap.get(circuitBreakerKey))
32-
.map(config -> circuitBreakerRegistry.circuitBreaker(circuitBreakerKey, config))
33-
.orElseGet(() -> circuitBreakerRegistry.circuitBreaker(circuitBreakerKey));
34-
30+
CircuitBreaker circuitBreaker = getCircuitBreakerFromConfigMap(circuitBreakerKey);
3531
circuitBreaker
3632
.getEventPublisher()
3733
.onStateTransition(
@@ -54,4 +50,10 @@ public CircuitBreaker getCircuitBreaker(String circuitBreakerKey) {
5450
return circuitBreaker;
5551
});
5652
}
53+
54+
private CircuitBreaker getCircuitBreakerFromConfigMap(String circuitBreakerKey) {
55+
return Optional.ofNullable(circuitBreakerConfigMap.get(circuitBreakerKey))
56+
.map(config -> circuitBreakerRegistry.circuitBreaker(circuitBreakerKey, config))
57+
.orElseGet(() -> circuitBreakerRegistry.circuitBreaker(circuitBreakerKey));
58+
}
5759
}

grpc-circuitbreaker-utils/src/main/java/org/hypertrace/circuitbreaker/grpcutils/resilience/ResilienceCircuitBreakerRegistryProvider.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
/** Utility class to provide Resilience4j CircuitBreakerRegistry */
99
@Slf4j
10-
public class ResilienceCircuitBreakerRegistryProvider {
10+
class ResilienceCircuitBreakerRegistryProvider {
1111
private final CircuitBreakerThresholds circuitBreakerThresholds;
1212

1313
public ResilienceCircuitBreakerRegistryProvider(
@@ -24,12 +24,12 @@ public CircuitBreakerRegistry getCircuitBreakerRegistry() {
2424
.onEntryAdded(
2525
entryAddedEvent -> {
2626
CircuitBreaker addedCircuitBreaker = entryAddedEvent.getAddedEntry();
27-
log.info("CircuitBreaker {} added", addedCircuitBreaker.getName());
27+
log.debug("CircuitBreaker {} added", addedCircuitBreaker.getName());
2828
})
2929
.onEntryRemoved(
3030
entryRemovedEvent -> {
3131
CircuitBreaker removedCircuitBreaker = entryRemovedEvent.getRemovedEntry();
32-
log.info("CircuitBreaker {} removed", removedCircuitBreaker.getName());
32+
log.debug("CircuitBreaker {} removed", removedCircuitBreaker.getName());
3333
});
3434
return circuitBreakerRegistry;
3535
}

grpc-circuitbreaker-utils/src/test/java/org/hypertrace/circuitbreaker/grpcutils/resilience/ResilienceCircuitBreakerInterceptorTest.java

+15-27
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
import java.time.Clock;
1212
import java.time.Instant;
1313
import java.time.ZoneOffset;
14-
import java.util.Map;
1514
import java.util.concurrent.TimeUnit;
16-
import org.hypertrace.circuitbreaker.grpcutils.CircuitBreakerConfigParser;
1715
import org.hypertrace.circuitbreaker.grpcutils.CircuitBreakerConfiguration;
1816
import org.junit.jupiter.api.BeforeEach;
1917
import org.junit.jupiter.api.Test;
@@ -42,13 +40,8 @@ void setUp() {
4240

4341
fixedClock = Clock.fixed(Instant.now(), ZoneOffset.UTC);
4442
when(mockChannel.newCall(any(), any())).thenReturn(mockClientCall);
45-
// when(mockCircuitBreakerRegistryProvider.getCircuitBreakerRegistry())
46-
// .thenReturn(mockCircuitBreakerRegistry);
4743
when(mockCircuitBreakerProvider.getCircuitBreaker(anyString())).thenReturn(mockCircuitBreaker);
4844
when(mockCircuitBreakerConfig.getDefaultCircuitBreakerKey()).thenReturn("global");
49-
when(mockCircuitBreakerConfig.getCircuitBreakerThresholdsMap())
50-
.thenReturn(
51-
Map.of("global", CircuitBreakerConfigParser.buildCircuitBreakerDefaultThresholds()));
5245
}
5346

5447
@Test
@@ -58,10 +51,7 @@ void testSendMessage_CallsSuperSendMessage_Success() {
5851

5952
ResilienceCircuitBreakerInterceptor interceptor =
6053
new ResilienceCircuitBreakerInterceptor(
61-
mockCircuitBreakerConfig,
62-
fixedClock,
63-
mockCircuitBreakerRegistry,
64-
mockCircuitBreakerProvider);
54+
mockCircuitBreakerConfig, fixedClock, mockCircuitBreakerProvider);
6555

6656
ClientCall<Object, Object> interceptedCall =
6757
interceptor.createInterceptedCall(
@@ -77,12 +67,14 @@ void testSendMessage_CallsSuperSendMessage_Success() {
7767
void testSendMessage_CircuitBreakerRejectsRequest() {
7868
when(mockCircuitBreaker.tryAcquirePermission()).thenReturn(false);
7969
when(mockCircuitBreaker.getState()).thenReturn(CircuitBreaker.State.OPEN);
70+
when(mockCircuitBreakerConfig.getExceptionBuilder())
71+
.thenReturn(
72+
reason ->
73+
new StatusRuntimeException(
74+
Status.RESOURCE_EXHAUSTED.withDescription(reason), mock(Metadata.class)));
8075
ResilienceCircuitBreakerInterceptor interceptor =
8176
new ResilienceCircuitBreakerInterceptor(
82-
mockCircuitBreakerConfig,
83-
fixedClock,
84-
mockCircuitBreakerRegistry,
85-
mockCircuitBreakerProvider);
77+
mockCircuitBreakerConfig, fixedClock, mockCircuitBreakerProvider);
8678

8779
ClientCall<Object, Object> interceptedCall =
8880
interceptor.createInterceptedCall(
@@ -102,12 +94,14 @@ void testSendMessage_CircuitBreakerRejectsRequest() {
10294
void testSendMessage_CircuitBreakerInHalfOpenState() {
10395
when(mockCircuitBreaker.tryAcquirePermission()).thenReturn(false);
10496
when(mockCircuitBreaker.getState()).thenReturn(CircuitBreaker.State.HALF_OPEN);
97+
when(mockCircuitBreakerConfig.getExceptionBuilder())
98+
.thenReturn(
99+
reason ->
100+
new StatusRuntimeException(
101+
Status.RESOURCE_EXHAUSTED.withDescription(reason), mock(Metadata.class)));
105102
ResilienceCircuitBreakerInterceptor interceptor =
106103
new ResilienceCircuitBreakerInterceptor(
107-
mockCircuitBreakerConfig,
108-
fixedClock,
109-
mockCircuitBreakerRegistry,
110-
mockCircuitBreakerProvider);
104+
mockCircuitBreakerConfig, fixedClock, mockCircuitBreakerProvider);
111105

112106
ClientCall<Object, Object> interceptedCall =
113107
interceptor.createInterceptedCall(
@@ -128,10 +122,7 @@ void testWrapListenerWithCircuitBreaker_Success() {
128122
when(mockCircuitBreaker.tryAcquirePermission()).thenReturn(true);
129123
ResilienceCircuitBreakerInterceptor interceptor =
130124
new ResilienceCircuitBreakerInterceptor(
131-
mockCircuitBreakerConfig,
132-
fixedClock,
133-
mockCircuitBreakerRegistry,
134-
mockCircuitBreakerProvider);
125+
mockCircuitBreakerConfig, fixedClock, mockCircuitBreakerProvider);
135126

136127
ClientCall<Object, Object> interceptedCall =
137128
interceptor.createInterceptedCall(
@@ -155,10 +146,7 @@ void testWrapListenerWithCircuitBreaker_Failure() {
155146
when(mockCircuitBreaker.tryAcquirePermission()).thenReturn(true);
156147
ResilienceCircuitBreakerInterceptor interceptor =
157148
new ResilienceCircuitBreakerInterceptor(
158-
mockCircuitBreakerConfig,
159-
fixedClock,
160-
mockCircuitBreakerRegistry,
161-
mockCircuitBreakerProvider);
149+
mockCircuitBreakerConfig, fixedClock, mockCircuitBreakerProvider);
162150

163151
ClientCall<Object, Object> interceptedCall =
164152
interceptor.createInterceptedCall(

0 commit comments

Comments
 (0)