|
| 1 | +# Configuring a Bus |
| 2 | + |
| 3 | +Each bus can be customized with listeners and middlewares. To do so, create a factory function decorated with `@injectable` that returns the configured bus. |
| 4 | +```python |
| 5 | +from cq import CommandBus, MiddlewareResult, new_command_bus |
| 6 | +from injection import injectable |
| 7 | + |
| 8 | +async def listener(message: MessageType): |
| 9 | + ... |
| 10 | + |
| 11 | +async def middleware(message: MessageType) -> MiddlewareResult[ReturnType]: |
| 12 | + # do something before the handler is executed |
| 13 | + return_value = yield |
| 14 | + # do something after the handler is executed |
| 15 | + |
| 16 | +@injectable |
| 17 | +def command_bus_factory() -> CommandBus: |
| 18 | + bus = new_command_bus() |
| 19 | + bus.add_listeners(listener) |
| 20 | + bus.add_middlewares(middleware) |
| 21 | + return bus |
| 22 | +``` |
| 23 | + |
| 24 | +The same pattern applies to `QueryBus` and `EventBus` using `new_query_bus()` and `new_event_bus()`. |
| 25 | + |
| 26 | +## Listeners |
| 27 | + |
| 28 | +Listeners are executed before the handler(s). They receive the message and can perform side effects such as logging or validation. |
| 29 | +```python |
| 30 | +async def log_listener(message: MessageType): |
| 31 | + print(f"Received: {message}") |
| 32 | +``` |
| 33 | + |
| 34 | +## Middlewares |
| 35 | + |
| 36 | +Middlewares wrap around handler execution, allowing you to run logic before and after a handler processes a message. |
| 37 | +```python |
| 38 | +async def timing_middleware(message: Any) -> MiddlewareResult[Any]: |
| 39 | + start = time.time() |
| 40 | + yield |
| 41 | + print(f"Execution time: {time.time() - start}s") |
| 42 | +``` |
| 43 | + |
| 44 | +For commands and queries, middlewares run once around the single handler. For events, middlewares run around each handler individually. |
| 45 | + |
| 46 | +!!! note |
| 47 | + The generator was chosen to keep both the input message and the return value read-only. |
| 48 | + |
| 49 | +## Class-based listeners and middlewares |
| 50 | + |
| 51 | +For more flexibility, listeners and middlewares can be defined as classes with a `__call__` method. This allows you to inject dependencies and configure their behavior. |
| 52 | +```python |
| 53 | +from cq import MiddlewareResult |
| 54 | +from dataclasses import dataclass |
| 55 | + |
| 56 | +@dataclass |
| 57 | +class LogListener: |
| 58 | + logger: Logger |
| 59 | + |
| 60 | + async def __call__(self, message: Any): |
| 61 | + self.logger.info(f"Received: {message}") |
| 62 | + |
| 63 | +@dataclass |
| 64 | +class TimingMiddleware: |
| 65 | + metrics: MetricsService |
| 66 | + |
| 67 | + async def __call__(self, message: Any) -> MiddlewareResult[Any]: |
| 68 | + start = time.time() |
| 69 | + yield |
| 70 | + self.metrics.record(time.time() - start) |
| 71 | +``` |
0 commit comments