Skip to content

Request and event handler fallbacks with optional circuit breaker

Choose a tag to compare

@vadikko2 vadikko2 released this 21 Feb 20:37
· 10 commits to master since this release
95aed36

πŸš€ Release notes – v4.10.0

Fallbacks for requests and events

TL;DR β€” Request and event handler fallbacks with optional circuit breaker; unified ICircuitBreaker protocol; 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 streaming StreamingRequestHandler[Req, Res].
  • Optional failure_exceptions to 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_exceptions and 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)
  • AioBreakerAdapter now implements ICircuitBreaker (and still ISagaStepCircuitBreaker for 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

  • RequestHandlerFallback is 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() in cqrs.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() in cqrs.circuit_breaker – central logic to decide whether to run the fallback after a primary failure (circuit open or exception in failure_exceptions, or any exception if failure_exceptions is empty).

πŸ“š Documentation and examples

  • Examples added:
    • examples/request_fallback.py – request handler fallback with optional circuit breaker
    • examples/cor_request_fallback.py – CoR (chain of responsibility) with fallback
    • examples/event_fallback.py – event handler fallback
    • examples/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 AioBreakerAdapter usage continue to work. AioBreakerAdapter now implements ICircuitBreaker in addition to ISagaStepCircuitBreaker.
  • Python: 3.10+
  • Optional: aiobreaker for circuit breaker support (pip install python-cqrs[aiobreaker]).