Command Query Responsibility Segregation (CQRS)¶
Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates the model for reading data (queries) from the model for writing data (commands). This separation can lead to simpler models, better performance, scalability, and security.
castlecraft-engineer
provides a set of abstractions that facilitate the implementation of CQRS in your applications.
1. The Core Idea: Separating Reads and Writes¶
At its heart, CQRS suggests that operations that change state (Commands) should be distinct from operations that retrieve state (Queries).
- Commands: Represent an intent to change the state of the system. They are typically imperative (e.g.,
CreateOrderCommand
,UpdateUserDetailsCommand
). Commands should not return data but can indicate success or failure (often via exceptions or by completing without error). - Queries: Represent a request for data. They should not alter the state of the system. Queries return data transfer objects (DTOs) or simple data structures tailored for the specific read operation.
This separation allows for different optimization strategies for read and write paths. For instance, the read model might be denormalized for fast queries, while the write model (often your domain model with aggregates) focuses on consistency and business rules.
2. Commands, Command Handlers, and the Command Bus¶
The write side of CQRS in castlecraft-engineer
is managed through these components:
-
Commands: Objects that encapsulate all necessary information to perform an action. They inherit from
castlecraft_engineer.abstractions.command.Command
.- Purpose: To clearly define an operation that will mutate state.
- Example:
CreateProductCommand(name: str, price: float)
.
-
Command Handlers: Classes responsible for executing a specific command. They contain the business logic to process the command, interact with domain models (Aggregates and Repositories), and persist changes. They inherit from
castlecraft_engineer.abstractions.command_handler.CommandHandler
.- Purpose: To isolate the logic for handling a command.
- Key Method:
async execute(self, command: TCommand) -> TResult
(or syncexecute
).
-
Command Bus: A mediator that receives commands and dispatches them to their registered handlers.
castlecraft-engineer
providescastlecraft_engineer.abstractions.command_bus.CommandBus
.- Purpose: To decouple the sender of a command from its processor.
- Functionality: Handlers are registered with the bus, often via DI. When a command is executed via the bus, the bus resolves and invokes the appropriate handler.
For more details, see Commands.
3. Queries, Query Handlers, and the Query Bus¶
The read side of CQRS in castlecraft-engineer
is managed similarly:
-
Queries: Objects that define the parameters for a data retrieval operation. They inherit from
castlecraft_engineer.abstractions.query.Query
.- Purpose: To clearly define a request for data without side effects.
- Example:
GetProductByIdQuery(product_id: UUID)
.
-
Query Handlers: Classes responsible for executing a query and returning the requested data. They typically interact with read-optimized data sources or directly with repositories that fetch data models. They inherit from
castlecraft_engineer.abstractions.query_handler.QueryHandler
.- Purpose: To isolate the logic for fetching and shaping data for a specific query.
- Key Method:
async execute(self, query: TQuery) -> TResult
(or syncexecute
).
-
Query Bus: A mediator that receives queries and dispatches them to their registered handlers.
castlecraft-engineer
providescastlecraft_engineer.abstractions.query_bus.QueryBus
.- Purpose: To decouple the requester of data from the data retrieval logic.
- Functionality: Handlers are registered, and the bus resolves and invokes the correct handler for a given query.
For more details, see Queries.
4. Events and Event Handlers¶
Events play a crucial role in CQRS architectures, especially for handling side effects, inter-aggregate communication, or updating read models.
-
Events: Objects that represent something significant that has happened in the domain, typically as a result of a command being processed. They inherit from
castlecraft_engineer.abstractions.event.Event
.- Purpose: To capture facts about state changes.
- Example:
ProductCreatedEvent(product_id: UUID, name: str)
.
-
Event Handlers: Classes that react to specific events. They can perform various actions, such as sending notifications, updating other aggregates, or updating denormalized read models. They inherit from
castlecraft_engineer.abstractions.event_handler.EventHandler
.- Purpose: To perform side effects or trigger subsequent actions in response to domain events.
- Key Method:
async handle(self, event: TEvent)
(or synchandle
).
-
Event Bus: A mediator that allows event handlers to subscribe to specific event types and dispatches published events to all interested subscribers.
castlecraft_engineer
providescastlecraft_engineer.abstractions.event_bus.EventBus
for in-process eventing.- Purpose: To decouple event publishers from event subscribers.
-
External Event Publisher/Consumer: For systems requiring integration with external message brokers (e.g., Kafka, RabbitMQ),
castlecraft-engineer
provides abstract base classesExternalEventPublisher
andEventStreamConsumer
(fromabstractions/event_publisher.py
andabstractions/event_consumer.py
respectively).
Events are often raised by Aggregates after their state has been successfully changed by a Command Handler. These events can then be published via the Event Bus.
For more details, see Events.
5. How castlecraft-engineer
Abstractions Support CQRS¶
The castlecraft-engineer
library provides the foundational building blocks for implementing CQRS:
- Base Classes:
Command
,CommandHandler
,Query
,QueryHandler
,Event
, andEventHandler
provide clear contracts and type safety. - Buses:
CommandBus
,QueryBus
, andEventBus
act as mediators, decoupling components and simplifying the flow of control. They typically integrate with a Dependency Injection (DI) container (likepunq
, used incommon/di.py
) to resolve handlers. - Aggregate Pattern: The
Aggregate
andAggregateRepository
abstractions (see Aggregates) are central to the write side, ensuring domain logic and consistency. - Generic Repositories:
ModelRepository
andAsyncModelRepository
(see Repositories) can be used on the read side for direct data access or for simpler entities on the write side.
By using these abstractions, developers can build applications with a clear separation of concerns, making the system more maintainable, testable, and scalable.
6. Further Reading¶
To dive deeper into the specific components that make up a CQRS architecture within castlecraft-engineer
, refer to the following detailed pages:
- Commands
- Queries
- Events
- Dependency Injection (for how buses and handlers are wired up)
- Aggregates (for the write-side domain model)
- Repositories (for data access patterns)