-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This guide covers bootstrapping, protected invocation signatures, and production-grade fallback handling with Fuse.
Fuse is available on Maven Central under the coordinates:
Add the dependency to your pom.xml:
<dependency>
<groupId>io.github.frodygr</groupId>
<artifactId>fuse</artifactId>
<version>1.0.0</version>
</dependency>implementation("io.github.frodygr:fuse:1.0.0")Fuse requires Java 17 or higher and brings zero transitive runtime dependencies.
The CircuitBreaker interface provides three execution methods tailored to standard Java functional contracts:
Use execute(Supplier<T>) for standard operations that only throw runtime exceptions:
CircuitBreaker cb = new DefaultCircuitBreaker("inventory-service", config);
// Invocations returning a response object
InventoryStatus status = cb.execute(() -> inventoryClient.checkStock(sku));Use executeChecked(Callable<T>) when interacting with legacy libraries or network clients (such as standard java.net.http.HttpClient or JDBC) that declare checked exceptions:
try {
HttpResponse<String> response = cb.executeChecked(() -> {
HttpRequest request = HttpRequest.newBuilder(uri).build();
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
});
} catch (CircuitBreakerOpenException e) {
// Fast-path rejection when circuit is open
} catch (IOException | InterruptedException e) {
// Handled downstream I/O failures
} catch (Exception e) {
// Generic catch block
}Use execute(Runnable) for side-effects, cache flushes, or background notification triggers:
cb.execute(() -> auditPublisher.publishEvent(event));When a circuit transitions to OPEN, calling execute(...) immediately throws a CircuitBreakerOpenException. Designing deliberate fallback paths is essential to keeping upstream systems resilient.
Serve cached or degraded data when the live downstream fails:
public ProductDetails getProductDetails(String productId) {
try {
ProductDetails liveData = circuitBreaker.execute(() -> productClient.fetch(productId));
localCache.put(productId, liveData);
return liveData;
} catch (CircuitBreakerOpenException | ServiceUnavailableException ex) {
log.warn("Product service unavailable. Serving cached snapshot for {}", productId);
return localCache.getOrDefault(productId, ProductDetails.empty(productId));
}
}Gracefully disable non-critical functionality (e.g., product recommendations, real-time reviews) without breaking the core checkout flow:
public OrderSummary finalizeCheckout(OrderRequest request) {
OrderSummary summary = orderService.process(request);
try {
List<Recommendation> recs = recBreaker.execute(() -> recommendationClient.getRecommendations(request.userId()));
summary.setRecommendations(recs);
} catch (CircuitBreakerOpenException ex) {
// Recommendations are non-essential; omit them gracefully
summary.setRecommendations(Collections.emptyList());
}
return summary;
}In RESTful APIs, translate CircuitBreakerOpenException into an RFC-compliant HTTP 503 with a Retry-After header:
@ExceptionHandler(CircuitBreakerOpenException.class)
public ResponseEntity<ErrorResponse> handleCircuitBreakerOpen(CircuitBreakerOpenException ex) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.header("Retry-After", "30")
.body(new ErrorResponse("DOWNSTREAM_UNAVAILABLE", "Upstream service temporarily isolated. Retry in 30s."));
}In emergency scenarios (e.g., after an operational failover or database restart), operators can manually clear failure counters and restore normal traffic immediately:
// Forcefully resets state to CLOSED and clears all error counters
circuitBreaker.reset();Fuse • Zero-Dependency Circuit Breaker for Java 17+ • Licensed under Apache-2.0