Skip to content

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 sync execute).
  • Command Bus: A mediator that receives commands and dispatches them to their registered handlers. castlecraft-engineer provides castlecraft_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 sync execute).
  • Query Bus: A mediator that receives queries and dispatches them to their registered handlers. castlecraft-engineer provides castlecraft_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 sync handle).
  • Event Bus: A mediator that allows event handlers to subscribe to specific event types and dispatches published events to all interested subscribers. castlecraft_engineer provides castlecraft_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 classes ExternalEventPublisher and EventStreamConsumer (from abstractions/event_publisher.py and abstractions/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, and EventHandler provide clear contracts and type safety.
  • Buses: CommandBus, QueryBus, and EventBus act as mediators, decoupling components and simplifying the flow of control. They typically integrate with a Dependency Injection (DI) container (like punq, used in common/di.py) to resolve handlers.
  • Aggregate Pattern: The Aggregate and AggregateRepository abstractions (see Aggregates) are central to the write side, ensuring domain logic and consistency.
  • Generic Repositories: ModelRepository and AsyncModelRepository (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)