Circuit breaker
The Circuit Breaker is a System Management pattern from Hohpe and Woolf. It trips after repeated failures against a downstream service, then short-circuits further calls for a cool-down so the dependency can recover.
let main_route = RouteBuilder::from("timer:cb-test?period=1000&repeatCount=15")
.route_id("circuit-breaker-demo")
.process(|mut exchange| async move {
let n = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
exchange.input.body = Body::Text(format!("request-{}", n));
Ok(exchange)
})
.to("direct:failing-service")
.circuit_breaker(
CircuitBreakerConfig::new()
.failure_threshold(3)
.open_duration(Duration::from_secs(3)),
)
.to("log:cb-success?showBody=true&showCorrelationId=true")
.error_handler(
ErrorHandlerConfig::dead_letter_channel(
"log:cb-fallback?showBody=true&showHeaders=true&showCorrelationId=true",
)
.on_exception(|_| true)
.build(),
)
.build()?;
YAML equivalent
- id: circuit-breaker-demo
from: timer:cb-test?period=1000&repeatCount=15
circuit_breaker:
failure_threshold: 3
open_duration_ms: 3000
error_handler:
dead_letter_channel: log:cb-fallback?showBody=true&showHeaders=true&showCorrelationId=true
steps:
- to: direct:failing-service
- to: log:cb-success?showBody=true&showCorrelationId=true
The breaker cycles through three states. In Closed, traffic flows. Each failed call increments a consecutive-failure counter. A successful call resets that counter to zero. When the counter reaches failure_threshold, the breaker trips into Open. In Open, the breaker rejects every call with CamelError::CircuitOpen. The route never touches the downstream service. The breaker holds Open for open_duration, then enters HalfOpen.
HalfOpen admits a single probe call. The breaker rejects concurrent callers in that window so the probe runs in isolation. A probe that succeeds closes the breaker and resets the counter. A probe that fails reopens the breaker for another full cool-down. This single-probe design stops a backlog of traffic from stampeding the service. The dependency gets one probe, not a flood, at the first sign of recovery.
Use the circuit breaker to protect a downstream service. Do not use it to repair a transient blip. Retry sends more traffic at a failing call. The circuit breaker sends none. Pair them when a flaky dependency needs a retry for transient errors but a hard stop during a sustained outage. Per ADR-0019, the breaker compiles into a gate on RouteChannelService rather than a pipeline step. Boundary rejections flow through RouteErrorHandler::handle_boundary. The route's error_handler can then route CircuitOpen to a dead-letter sink, as the example does with log:cb-fallback.
The example source is at examples/circuit-breaker.