Skip to content

Production Patterns

Frody edited this page Sep 3, 2026 · 1 revision

Production Patterns & Best Practices

This guide details recommended patterns, client integrations, and architectural pitfalls when running Fuse in production.


1. Scope Breakers by Dependency (Granular Isolation)

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
Loading

Anti-Pattern: The "Global" Circuit Breaker

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.


2. Spring Boot Singleton Bean Configuration

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);
    }
}

3. Standard Java 11+ HttpClient Integration

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();
        });
    }
}

4. Relationship Between HTTP Timeouts and Circuit Breakers

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 (connectTimeout and requestTimeout).
  • Circuit Breaker Timeout: Measures how long the breaker stays OPEN before 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.


5. Summary of Common Anti-Patterns

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.