-
Notifications
You must be signed in to change notification settings - Fork 0
Production Patterns
This guide details recommended patterns, client integrations, and architectural pitfalls when running Fuse in production.
Rule: Always maintain separate, distinct CircuitBreaker instances for each external downstream dependency or subsystem.
flowchart TD
API["API Gateway / Controller"]
subgraph Breakers["Dedicated Circuit Breakers"]
CB_PAY["Payment Service Breaker"]
CB_INV["Inventory Service Breaker"]
CB_NOTIF["Notification Breaker"]
end
subgraph Downstream["External Downstream Services"]
SVC_PAY["Payment Gateway"]
SVC_INV["Inventory Database"]
SVC_NOTIF["Email / SMS Provider"]
end
API --> CB_PAY --> SVC_PAY
API --> CB_INV --> SVC_INV
API --> CB_NOTIF --> SVC_NOTIF
Never share a single CircuitBreaker instance across multiple disparate services. If a non-critical service (such as an analytics or notification provider) degrades, a shared circuit breaker will trip and mistakenly isolate your critical payment or order processing APIs.
Because Fuse has zero runtime dependencies, you do not need complex starter dependencies. Simply declare circuit breakers as standard Spring @Bean singletons in a configuration class:
@Configuration
public class CircuitBreakerConfiguration {
@Bean
public CircuitBreaker paymentCircuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig.builder()
.failureThreshold(5)
.successThreshold(3)
.timeout(Duration.ofSeconds(30))
.recordException(ex -> ex instanceof IOException || ex instanceof TimeoutException)
.build();
return new DefaultCircuitBreaker("payment-service", config);
}
@Bean
public CircuitBreaker emailCircuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig.builder()
.failureThreshold(10)
.successThreshold(2)
.timeout(Duration.ofSeconds(15))
.build();
return new DefaultCircuitBreaker("email-service", config);
}
}Wrap outgoing HTTP invocations cleanly using Java's built-in java.net.http.HttpClient:
@Service
public class RemoteBillingClient {
private final HttpClient httpClient;
private final CircuitBreaker circuitBreaker;
public RemoteBillingClient(CircuitBreaker paymentCircuitBreaker) {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
this.circuitBreaker = paymentCircuitBreaker;
}
public String fetchInvoice(String invoiceId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://billing.internal/invoices/" + invoiceId))
.timeout(Duration.ofSeconds(10)) // Always set HTTP read timeout!
.GET()
.build();
return circuitBreaker.executeChecked(() -> {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 500) {
throw new IOException("Remote server returned HTTP " + response.statusCode());
}
return response.body();
});
}
}A common configuration mistake is setting the HTTP socket timeout longer than the circuit breaker timeout.
-
HTTP Client Timeout: Measures how long a single request waits before failing (
connectTimeoutandrequestTimeout). -
Circuit Breaker Timeout: Measures how long the breaker stays
OPENbefore probing.
Always ensure the HTTP client enforces a strict read timeout (e.g., 5 to 10 seconds). Without HTTP timeouts, a hung downstream socket will hold client threads indefinitely, preventing the circuit breaker from recording failures.
| Anti-Pattern | Operational Consequence | Recommended Fix |
|---|---|---|
Swallowing CircuitBreakerOpenException |
Callers cannot distinguish between healthy empty data and a downstream outage. | Either rethrow the exception or return an explicit degraded fallback model. |
| Omitting HTTP Read Timeouts | Remote server deadlocks hold client threads forever; failures are never counted. | Configure .timeout(Duration.ofSeconds(...)) on all HTTP requests. |
| Recording 4xx Client Errors | Bad user inputs (e.g. invalid form data) trip the circuit breaker for all users. | Use .recordException(...) to filter out client 4xx validation errors. |
| Instantiating a new Breaker per Request | State is destroyed on every request; circuit never trips to OPEN. | Always instantiate CircuitBreaker as a long-lived application singleton. |
Fuse • Zero-Dependency Circuit Breaker for Java 17+ • Licensed under Apache-2.0