Coverage for src/lexigram/web/routing/cqrs.py: 53%
19 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""CQRSController - Controller base class with integrated CQRS command/query bus dispatch.
3Provides a controller that can dispatch commands and queries through the CQRS buses
4registered in the DI container. Controllers extending this class get typed
5``dispatch_command`` and ``dispatch_query`` helper methods.
6"""
8from __future__ import annotations
10from typing import Any
12from lexigram.contracts.events import CommandBusProtocol, QueryBusProtocol
13from lexigram.logging import get_logger
14from lexigram.web.routing.controllers import Controller
16logger = get_logger(__name__)
19class CQRSController(Controller):
20 """Controller with built-in CQRS command and query bus dispatch.
22 Subclass this instead of ``Controller`` when your endpoints need to
23 dispatch commands or queries through the CQRS buses.
25 The buses are injected via the constructor and are expected to be
26 resolved from the DI container.
28 Example::
30 class OrderController(CQRSController):
31 @post("/orders")
32 async def create_order(self, request: Request) -> JSONResponse:
33 data = await request.json()
34 result = await self.dispatch_command(CreateOrderCommand(**data))
35 return JSONResponse({"id": result.id}, status_code=201)
37 @get("/orders/{order_id}")
38 async def get_order(self, request: Request) -> JSONResponse:
39 order_id = request.path_params["order_id"]
40 result = await self.dispatch_query(GetOrderQuery(order_id=order_id))
41 return JSONResponse(result.to_dict())
42 """
44 def __init__(
45 self,
46 command_bus: CommandBusProtocol | None = None,
47 query_bus: QueryBusProtocol | None = None,
48 ) -> None:
49 """Initialize CQRSController with optional command and query buses.
51 Args:
52 command_bus: CommandBusProtocol implementation for dispatching commands.
53 If None, calling ``dispatch_command`` will raise RuntimeError.
54 query_bus: QueryBusProtocol implementation for executing queries.
55 If None, calling ``dispatch_query`` will raise RuntimeError.
56 """
57 super().__init__()
58 self._command_bus = command_bus
59 self._query_bus = query_bus
61 async def dispatch_command(self, command: Any) -> Any:
62 """Dispatch a command through the command bus.
64 Args:
65 command: The command object to dispatch.
67 Returns:
68 The result returned by the command handler.
70 Raises:
71 RuntimeError: If no CommandBusProtocol was provided to this controller.
72 """
73 if self._command_bus is None:
74 raise RuntimeError(
75 f"{type(self).__name__} has no CommandBusProtocol configured. "
76 "Register a CommandBusProtocol in the DI container.",
77 )
78 return await self._command_bus.dispatch(command)
80 async def dispatch_query(self, query: Any) -> Any:
81 """Dispatch a query through the query bus.
83 Args:
84 query: The query object to execute.
86 Returns:
87 The query result.
89 Raises:
90 RuntimeError: If no QueryBusProtocol was provided to this controller.
91 """
92 if self._query_bus is None:
93 raise RuntimeError(
94 f"{type(self).__name__} has no QueryBusProtocol configured. "
95 "Register a QueryBusProtocol in the DI container.",
96 )
97 return await self._query_bus.execute(query)