Request and event handler fallbacks with optional circuit breaker
π Release notes β v4.10.0
Fallbacks for requests and events
TL;DR β Request and event handler fallbacks with optional circuit breaker; unified
ICircuitBreakerprotocol; new examples and tests.
This release adds fallback support for request handlers and event handlers, with optional circuit breaker integration. You can now define primary and fallback handlers for commands, queries, streaming handlers, and domain events, and optionally protect them with a shared circuit breaker protocol.
π What's new
π¨ Request handler fallbacks
RequestHandlerFallbackβ wrap a primary and fallback request handler so that when the primary fails (or the circuit is open), the fallback is used.- Works with both sync
RequestHandler[Req, Res]and streamingStreamingRequestHandler[Req, Res]. - Optional
failure_exceptionsto trigger fallback only for specific exception types (e.g.ConnectionError,TimeoutError). - Optional circuit breaker so that after N failures the primary is skipped and the fallback runs directly.
Example:
from cqrs import RequestHandlerFallback
from cqrs.adapters.circuit_breaker import AioBreakerAdapter
request_cb = AioBreakerAdapter(fail_max=5, timeout_duration=60)
request_map.bind(
GetOrderCommand,
RequestHandlerFallback(
GetOrderHandler,
GetOrderFallbackHandler,
failure_exceptions=(ConnectionError, TimeoutError),
circuit_breaker=request_cb,
),
)π’ Event handler fallbacks
EventHandlerFallbackβ wrap a primary and fallback event handler so that when the primary fails (or the circuit is open), the fallback is used.- Same options:
failure_exceptionsand optional circuit breaker. - Supported in both event dispatcher and event emitter (handlers registered in
EventMap).
Example:
from cqrs import EventHandlerFallback
event_cb = AioBreakerAdapter(fail_max=5, timeout_duration=60)
event_map.bind(
OrderCreatedEvent,
EventHandlerFallback(
SendEmailHandler,
SendEmailFallbackHandler,
circuit_breaker=event_cb,
),
)π Unified circuit breaker protocol
ICircuitBreakerβ a single protocol used by:- Saga step fallbacks (existing)
- Request handler fallbacks (new)
- Event handler fallbacks (new)
AioBreakerAdapternow implementsICircuitBreaker(and stillISagaStepCircuitBreakerfor backward compatibility).- One adapter instance per βdomainβ (e.g. one for requests, one for events) is enough; each handler/step is namespaced by type.
π‘ Streaming handlers
RequestHandlerFallbackis supported for streaming handlers: when the primary streaming handler fails, the fallback stream is used from the start (no resume from the same stream).
π οΈ Helpers
get_generic_args_for_origin()incqrs.generic_utilsβ used to validate that primary and fallback handlers handle the same request/event and (for requests) the same response type.should_use_fallback()incqrs.circuit_breakerβ central logic to decide whether to run the fallback after a primary failure (circuit open or exception infailure_exceptions, or any exception iffailure_exceptionsis empty).
π Documentation and examples
- Examples added:
examples/request_fallback.pyβ request handler fallback with optional circuit breakerexamples/cor_request_fallback.pyβ CoR (chain of responsibility) with fallbackexamples/event_fallback.pyβ event handler fallbackexamples/streaming_handler_fallback.pyβ streaming request handler fallback
- Unit tests:
test_request_fallback.py,test_event_fallback.py; integration tests for the circuit breaker adapter updated.
π API summary
| Item | Description |
|---|---|
cqrs.RequestHandlerFallback |
Fallback wrapper for request/streaming handlers (primary, fallback, optional failure_exceptions, optional circuit_breaker). |
cqrs.EventHandlerFallback |
Fallback wrapper for event handlers (same options). |
cqrs.ICircuitBreaker |
Protocol: call(identifier, func, *args, **kwargs) and is_circuit_breaker_error(exc). |
cqrs.circuit_breaker.should_use_fallback |
Helper to decide if fallback should run after primary error. |
cqrs.generic_utils.get_generic_args_for_origin |
Extract generic type args from handler classes (for validation). |
β Compatibility
- Backward compatible: Existing saga fallbacks and
AioBreakerAdapterusage continue to work.AioBreakerAdapternow implementsICircuitBreakerin addition toISagaStepCircuitBreaker. - Python: 3.10+
- Optional:
aiobreakerfor circuit breaker support (pip install python-cqrs[aiobreaker]).