Circuit breaker middleware for consumer message handlers.
Implements the classic three-state machine:
CLOSED ──(failures >= threshold)──► OPEN
▲ │
│ (reset_timeout elapses)
│ ▼
└──────(probe succeeds)────── HALF_OPENStates
CLOSED — Normal operation. All messages are processed. Failures are counted per fuse. When the failure count reaches
:thresholdwithin:window_ms, the breaker trips to OPEN.OPEN — Circuit is tripped. Messages are rejected immediately with
{:error, :circuit_open}— no downstream calls are made. After:reset_timeout_ms, the breaker transitions to HALF_OPEN.HALF_OPEN — Probe state. The next single message is allowed through as a test. If it succeeds, the breaker resets to CLOSED. If it fails, the breaker returns to OPEN and the timeout restarts.
Usage
Add to a consumer's middleware list:
defmodule MyApp.PaymentsConsumer do
use PhoenixMicro.Consumer
topic "payments.created"
middleware [
{PhoenixMicro.Middleware.CircuitBreaker,
fuse: :payments_db,
threshold: 5,
window_ms: 10_000,
reset_timeout_ms: 30_000}
]
def handle(message, _ctx), do: MyApp.Repo.insert(...)
endFuse names
The :fuse option names the circuit — multiple consumers can share
a fuse (e.g. fuse: :payments_db) so that a downstream failure
opens all of them simultaneously. Defaults to the topic name.
Storage
State is kept in an ETS table (:phoenix_micro_circuit_breakers) owned
by PhoenixMicro.Middleware.CircuitBreaker.Store. This process must be
started in your supervision tree, which PhoenixMicro.Application does
automatically.
Telemetry events
[:phoenix_micro, :circuit_breaker, :tripped]— breaker opened[:phoenix_micro, :circuit_breaker, :reset]— breaker closed[:phoenix_micro, :circuit_breaker, :rejected]— message rejected (OPEN)[:phoenix_micro, :circuit_breaker, :probe]— probe sent (HALF_OPEN)