Circuit Breaker
Prevent cascade failures by stopping requests to failing services.
How It Works
The circuit breaker has three states:
- Closed - Requests flow normally
- Open - Requests are rejected immediately
- Half-Open - Limited requests allowed to test recovery
┌─────────┐
│ Closed │ ◄── Normal operation
└────┬────┘
│ Failures exceed threshold
▼
┌─────────┐
│ Open │ ◄── Rejecting requests
└────┬────┘
│ Reset timeout expires
▼
┌──────────┐
│Half-Open │ ◄── Testing recovery
└────┬─────┘
│
Success│ Failure
│ │
▼ ▼
Closed OpenFactory Methods
Default Circuit Breaker
scala
import wvlet.uni.control.CircuitBreaker
val cb = CircuitBreaker.default
val result = cb.run {
externalServiceCall()
}Failure Threshold
Open circuit when failures exceed threshold within a window:
scala
// Open when 2 out of 5 recent calls fail
val cb = CircuitBreaker.withFailureThreshold(2, 5)
val result = cb.run {
externalServiceCall()
}Consecutive Failures
Open circuit after consecutive failures:
scala
// Open after 2 consecutive failures
val cb = CircuitBreaker.withConsecutiveFailures(2)
val result = cb.run {
externalServiceCall()
}Always Closed (Disabled)
Disable circuit breaker for testing:
scala
val cb = CircuitBreaker.alwaysClosed
// Never opens, even with many failures
for i <- 0 to 10 do
cb.recordFailure(new Exception())
cb.isConnected shouldBe trueRunning Code with Circuit Breaker
Use run to execute code through the circuit breaker:
scala
val cb = CircuitBreaker.withFailureThreshold(1, 2)
// Successful execution
cb.run {
operation()
}
// If the operation throws, it's recorded as a failure
try
cb.run[Any] {
throw new TimeoutException()
}
catch
case e: TimeoutException => // Handle exceptionHandling Open Circuit
scala
import wvlet.uni.control.{CircuitBreaker, CircuitBreakerOpenException}
val cb = CircuitBreaker.withFailureThreshold(1, 2)
try
cb.run {
externalService.call()
}
catch
case e: CircuitBreakerOpenException =>
// Circuit is open, use fallback
fallbackResponse()Circuit State
Check and control the current state:
scala
val cb = CircuitBreaker.default
// Check state
cb.state match
case CircuitBreaker.CLOSED =>
println("Circuit is healthy")
case CircuitBreaker.OPEN =>
println("Circuit is open, requests blocked")
case CircuitBreaker.HALF_OPEN =>
println("Circuit is testing recovery")
// Check if connected
if cb.isConnected then
println("Circuit breaker allows connections")Manual State Control
scala
val cb = CircuitBreaker.default
cb.state shouldBe CircuitBreaker.CLOSED
cb.isConnected shouldBe true
// Manually open the circuit
cb.open()
cb.state shouldBe CircuitBreaker.OPEN
cb.isConnected shouldBe false
// Set to half-open for probing
cb.halfOpen()
cb.state shouldBe CircuitBreaker.HALF_OPEN
cb.isConnected shouldBe true
// Close the circuit
cb.close()
cb.state shouldBe CircuitBreaker.CLOSED
cb.isConnected shouldBe trueStandalone Usage
Record successes and failures manually:
scala
val cb = CircuitBreaker.default
// Verify connection before making request
cb.verifyConnection()
try
val result = externalService.call()
cb.recordSuccess()
result
catch
case e: Throwable =>
cb.recordFailure(e)
throw eWith Retry
Combine circuit breaker with retry:
scala
import wvlet.uni.control.{CircuitBreaker, Retry}
val cb = CircuitBreaker.withFailureThreshold(5, 10)
val result = Retry.withBackOff(maxRetry = 3).run {
cb.run {
externalService.call()
}
}Use Cases
External Service Protection
scala
class PaymentService(cb: CircuitBreaker):
def processPayment(payment: Payment): Result =
cb.run {
paymentGateway.charge(payment)
}Database Protection
scala
val dbBreaker = CircuitBreaker.withConsecutiveFailures(3)
def queryDatabase(sql: String): Result =
dbBreaker.run {
database.execute(sql)
}API Gateway
scala
val apiBreaker = CircuitBreaker.withFailureThreshold(2, 5)
def callApi(request: Request): Response =
try
apiBreaker.run {
httpClient.send(request)
}
catch
case _: CircuitBreakerOpenException =>
Response.serviceUnavailable()Best Practices
- Tune thresholds - Based on normal failure rates
- Use failure threshold - For services with variable error rates
- Use consecutive failures - For services where any failure is significant
- Monitor state changes - Alert on frequent opens
- Use fallbacks - Provide degraded functionality
- Test failure scenarios - Verify circuit behavior
