Command Query Responsibility Segregation
Command Query Responsibility Segregation (CQRS) is a software architectural pattern that separates the handling of write operations (commands, which modify data) from read operations (queries, which retrieve data) by using distinct models for each, thereby allowing for optimized design, scalability, and performance in complex systems.[1] This pattern builds on the earlier Command-Query Separation (CQS) principle introduced by Bertrand Meyer in the context of the Eiffel programming language, which posits that methods should either perform an action without returning data or return data without side effects, but not both.[2] The term CQRS was coined and popularized by Greg Young around 2010, with Martin Fowler providing one of the first detailed descriptions in 2011.[1][2]
In practice, CQRS divides a system's operations into a command side—which focuses on transactional updates using normalized data models, often in a relational database—and a query side—which employs denormalized views or separate stores for efficient reads, potentially using technologies like materialized views or NoSQL databases.[3] This separation enables independent scaling of read and write workloads, simplifies query logic by avoiding complex joins, and enhances security by restricting access models (e.g., read-only permissions for query endpoints).[3] CQRS is particularly valuable in domain-driven design (DDD) contexts, where it aligns with bounded contexts to manage intricate business logic, and it often integrates with event sourcing, where commands trigger events that rebuild the read model asynchronously.[1][3]
While CQRS offers benefits like improved performance in high-throughput applications and clearer separation of concerns for development teams, it introduces challenges such as increased system complexity, the need for synchronization mechanisms (potentially leading to eventual consistency), and higher operational overhead, making it unsuitable for simple CRUD applications.[3] It is most effective in scenarios involving collaborative multi-user environments, task-oriented user interfaces, or systems requiring distinct optimization for reads versus writes, such as e-commerce platforms or financial services.[3]
Overview
Definition
Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates the responsibilities for handling commands, which modify data, from queries, which retrieve data, by using distinct models for each.[4] This segregation allows for independent scaling, optimization, and evolution of the read and write paths in a system, addressing challenges in complex applications where a unified model may lead to inefficiencies.[1]
In traditional CRUD (Create, Read, Update, Delete) systems, a single data model and access layer typically serve both read and write operations, often resulting in a monolithic structure that compromises performance or maintainability under varying loads.[4] CQRS explicitly divides these concerns, enabling the write model to focus on validation and state changes while the read model prioritizes efficient data retrieval, often tailored to specific query needs.[1]
Under CQRS, commands are treated as imperative messages that trigger state mutations but return no data beyond acknowledgments of receipt or success, ensuring they do not serve dual purposes.[4] Conversely, queries are strictly read-only operations that fetch and return data without altering the system's state, promoting a clear boundary between mutative and observational behaviors.[1] This pattern is often paired with Event Sourcing for persisting the write side as a sequence of events rather than direct state updates.[4]
Core Principles
The Command Query Responsibility Segregation (CQRS) pattern adheres to the principle of single responsibility by dividing the application's data model into distinct components: a command model dedicated exclusively to handling state mutations and a query model focused solely on retrieving state, thereby reducing complexity and improving maintainability in each domain. This segregation ensures that the command model avoids the overhead of read optimizations, while the query model is unburdened by write validation logic, allowing each to evolve independently without compromising the overall system's integrity.[1][4]
A key asymmetry exists in CQRS operations, where commands are designed to validate inputs, enforce business rules, and apply changes without returning any business data beyond acknowledgments of success or failure, whereas queries retrieve and return data without inducing any side effects or modifications to the system state. This distinction, rooted in the foundational Command-Query Separation principle, prevents the entanglement of read and write concerns, enabling more targeted optimizations for each operation type.[1][4][5]
CQRS permits the use of different technologies and storage mechanisms for the command and query models, such as normalized relational databases for the command side to ensure transactional consistency and denormalized structures or even separate data stores for the query side to enhance read performance. This flexibility allows developers to select tools best suited to the specific demands of writes (e.g., ACID compliance) versus reads (e.g., fast querying via views or caches), without forcing a uniform approach across the application.[1][4]
The pattern explicitly tolerates eventual consistency between the command and query models, accepting temporary divergences in data views during synchronization—often via asynchronous event propagation—rather than requiring immediate ACID transactions across both, which prioritizes scalability and resilience in distributed systems over strict real-time synchronization. This approach is particularly valuable in high-throughput environments where read operations vastly outnumber writes, allowing the query model to catch up without blocking command processing.[1][6][4]
History
Origins
Command Query Responsibility Segregation (CQRS) was coined by software architect Greg Young around 2010 during his discussions on domain-driven design (DDD) and scalable system architectures.[1] Young introduced the pattern to address the difficulties of applying DDD principles in large-scale applications, where a single unified domain model often resulted in performance bottlenecks due to conflicting requirements for read and write operations.[4] This approach built on earlier concepts like Bertrand Meyer's Command-Query Separation (CQS) but extended it to architectural levels by segregating responsibilities across separate models.[4]
The pattern's first public mentions occurred in Young's presentations on event sourcing and messaging systems in August 2010, where he emphasized the benefits of decoupling write-heavy domain logic from read-heavy reporting needs to improve scalability and maintainability.[7] These talks highlighted how traditional CRUD-based models in DDD could lead to impedance mismatches and overly complex unified representations in distributed environments.[4]
Young's early work also included explorations of "simple CQRS" without full event sourcing, positioning it as a lightweight alternative to comprehensive DDD implementations for teams seeking incremental improvements in system performance.[8] This variant focused on basic command and query separation using straightforward messaging, allowing developers to adopt the core idea without overhauling persistence strategies.[4]
Evolution
The term Command Query Responsibility Segregation (CQRS) was popularized in 2011 through Martin Fowler's influential blog post, which connected the pattern to scalable web architectures and emerging microservices paradigms.[1]
In the mid-2010s, CQRS saw widespread integration with event sourcing as a standard practice, exemplified by frameworks such as the Axon Framework, which reached version 1.0 in 2011 and evolved to support event-driven CQRS implementations, and EventStoreDB, released in 2012 as a dedicated event store for such systems.[9] This period also marked growing adoption in cloud-native applications, with Microsoft Azure providing early guidance on CQRS for scalable services starting around 2011 and expanding through architectural patterns in the 2010s, alongside AWS prescriptive guidance for event-sourced CQRS in distributed systems.[10][11]
A key milestone in CQRS's development came in 2013 with Vaughn Vernon's book Implementing Domain-Driven Design, which offered practical implementation strategies for CQRS within domain-driven design contexts, influencing its application in complex enterprise systems.[12]
Entering the 2020s, CQRS evolved to support reactive systems and serverless architectures, emphasizing polyglot persistence to optimize read and write models across diverse data stores.[3] By 2025, CQRS had become commonplace in e-commerce and fintech sectors for managing high-throughput read and write operations, as demonstrated in real-world fintech projects leveraging CQRS for instant payments and scalable event processing.[13] Recent trends include its integration with GraphQL APIs to enable flexible, client-defined querying while maintaining command-query separation.[14]
Key Concepts
Commands and Queries
In Command Query Responsibility Segregation (CQRS), operations are divided into two distinct categories: commands and queries, each serving a specific role in managing system state and data retrieval.[4] Commands represent imperative messages that instruct the system to perform state changes, encapsulating the necessary data and intent for an action without expecting data in return.[15] For instance, a CreateOrderCommand might include details like order items and customer ID to initiate order creation, incorporating validation logic within the domain to ensure the request's viability before processing.[4] These commands typically return only an acknowledgment of success or failure, such as a boolean or exception, to confirm execution without querying the resulting state.[16]
Queries, in contrast, are declarative requests designed solely to fetch data from the system without modifying any state, adhering to the principle of referential transparency where repeated calls yield consistent results under the same conditions.[1] Examples include GetOrderHistoryQuery to retrieve a list of past orders or FindUsersByStatusQuery to obtain users matching a specific criterion, often optimized for performance through denormalized read models and caching mechanisms to enable rapid responses.[15] Unlike commands, queries return structured data, such as data transfer objects (DTOs), tailored to the needs of the client, such as UI rendering or reporting.[4]
This separation distinguishes CQRS from traditional remote procedure call (RPC) or CRUD-style methods, where operations often blend read and write concerns in synchronous, bidirectional interactions.[1] In CQRS, commands can be processed synchronously or asynchronously, with asynchronous processing (e.g., via queuing) often used to decouple the sender from immediate outcomes and enable scalability in high-throughput scenarios.[15] Queries, meanwhile, can be customized to specific views or projections, bypassing complex domain logic for efficiency.[16]
Failure handling in CQRS emphasizes resilience and auditability. For commands, rejections due to validation errors or concurrency conflicts are typically handled by returning acknowledgments of failure, such as exceptions or error responses, to the client. In systems using domain-driven design or event sourcing, domain events may be used for successful changes or auditing, but failure logging can be achieved through other mechanisms like logs.[4] Queries manage inconsistencies, such as temporary staleness in read models, gracefully by relying on eventual consistency and projections that rebuild views from events, ensuring data availability even during updates.[15] This approach ultimately facilitates the separation of write and read models, allowing each to evolve independently.[1]
Separation of Models
In Command Query Responsibility Segregation (CQRS), the architecture divides the system into distinct write and read models to handle operations separately, allowing each to evolve independently based on their specific requirements.[3][1] The write model, driven by commands, processes updates through a normalized, transactional database that enforces data integrity and business rules, such as validation and consistency checks within aggregates.[3][17] This structure ensures that changes, like booking a hotel room, align precisely with domain logic without compromising transactional guarantees.[3]
The read model, in contrast, supports queries with a denormalized structure, often implemented as materialized views or projections in a read-optimized store, such as a NoSQL document database or key-value store, to enable fast retrieval without complex joins.[3][17] This denormalization collapses related data into efficient formats tailored for specific query needs, like generating user dashboards, thereby prioritizing performance over normalization.[1][17]
Independent scaling becomes feasible with this separation, as the write model can scale vertically to maintain consistency under transactional loads, while the read model scales horizontally to accommodate high query volumes across distributed replicas.[3][17] For instance, in applications with heavy read traffic, additional read replicas can be added without affecting the write side's integrity requirements.[1]
Model divergence further enhances flexibility, with the write model centering on aggregates and invariants to uphold business rules, whereas the read model focuses on user-facing projections that optimize for diverse access patterns.[3][17] This allows the use of different query languages or interfaces, such as SQL for the write model and search APIs for the read model, enabling each to adapt to their operational contexts without mutual constraints.[1][17]
Implementation
Architectural Components
In Command Query Responsibility Segregation (CQRS), the architectural components form the foundational layers that enable the separation of write and read operations, building upon the core distinction between command and query models. These components include specialized handlers for processing commands and queries, orchestration services, infrastructure for data persistence and communication, and boundaries to route requests appropriately. This structure allows for independent optimization of the write side for consistency and the read side for performance, often in distributed environments.[1][4]
Command handlers serve as dedicated processors on the write side of a CQRS system, responsible for receiving, validating, and executing incoming commands that modify the domain state. Upon validation—such as checking command parameters like identifiers or business rules—the handler invokes the relevant domain logic within aggregates or entities, ensuring transactional integrity. Successful execution typically results in the dispatch of domain events to notify other parts of the system, facilitating loose coupling and event-driven behavior without directly updating read models.[4][1]
Query handlers, in contrast, operate on the read side and are designed to efficiently resolve read-only requests against the query model, which is optimized for fast data retrieval. These handlers aggregate data from one or more sources, such as denormalized views or materialized representations, to produce lightweight data transfer objects (DTOs) tailored to specific query needs, bypassing complex domain logic to minimize latency. This approach supports scalable querying, especially in high-read scenarios, by allowing the read model to evolve independently from write operations.[4][1]
Application services act as an orchestration layer, bridging the user interface or API with the underlying handlers to translate high-level user intents into appropriate commands or queries. They coordinate the flow by routing validated commands to command handlers for execution and directing queries to query handlers for resolution, while also handling cross-cutting concerns like authentication or input mapping. This facade pattern reduces coupling between presentation layers and domain components, enabling a task-based interaction model.[4][1]
The infrastructure layer provides the supporting mechanisms for persistence, communication, and scalability in CQRS architectures. It encompasses repositories that abstract data access for both command and query models—such as event stores for the write side to append domain events immutably and read-optimized databases for the query side. Additionally, message buses facilitate asynchronous routing of commands and events between components, with implementations like RabbitMQ enabling reliable queuing, routing, and delivery in distributed setups to handle high volumes without blocking.[4][18][1]
At the system boundary, API gateways or controllers enforce the segregation by inspecting incoming requests and directing them to the correct side: write-oriented endpoints (e.g., POST or PUT for commands) to command-handling paths, and read-oriented ones (e.g., GET for queries) to query-handling paths. This routing mechanism, often implemented in web APIs or service proxies, ensures physical or logical separation of the models, allowing independent deployment, scaling, and technology choices for each side.[1][4]
Handling Synchronization
In Command Query Responsibility Segregation (CQRS), synchronization between the write model and read model is primarily managed through event publishing, where the write model emits domain events immediately after a command succeeds in updating the write store. These events are then asynchronously consumed by handlers or projections on the read side, which denormalize and materialize the data into optimized query structures for efficient reads. This mechanism ensures that changes propagate without tightly coupling the models, often leveraging an event store to maintain an immutable log of updates.[4][1]
A simpler synchronization alternative is dual writing, where both the write and read models update a shared database directly, providing immediate consistency but introducing scalability limitations due to contention and the need for a unified schema. This approach is generally avoided in pure CQRS designs, as it undermines the separation of models and can lead to performance bottlenecks in high-throughput scenarios.[1]
For distributed and scalable systems, asynchronous replication via message queues or event streaming platforms like Apache Kafka facilitates eventual consistency between models. Events from the write model are queued and processed by denormalizers that project them into separate read stores, such as document databases or caches, allowing independent scaling of read and write paths while tolerating temporary discrepancies.[3][4]
Conflict resolution during synchronization often relies on strategies like last-write-wins, where the most recent event timestamp prevails, or idempotent updates that ensure duplicate events do not alter the read model state. Optimistic concurrency controls, such as version numbers in the event store, detect and resolve mismatches by rejecting outdated operations. Monitoring tools, including lag detectors in streaming pipelines, help track replication delays and trigger alerts for prolonged inconsistencies.[4][3]
These synchronization techniques embody key trade-offs in CQRS, prioritizing system availability and scalability over immediate strict consistency, which is particularly advantageous in domains where brief staleness in query results—such as in analytics or reporting—is tolerable. Event sourcing can serve as a foundational method for generating these synchronizing events, providing a complete audit trail of state changes.[1][4]
Integration with Event Sourcing
In the context of Command Query Responsibility Segregation (CQRS), Event Sourcing serves as the backbone for the write model by storing the application's state as a sequence of immutable events rather than direct mutations to data records.[19] Each command processed by the write model appends new events to an event store, representing state changes like "OrderPlaced" or "ItemAdded," from which the current state can be derived by replaying the event sequence on an initial empty state.[20] This approach ensures that the write model maintains a complete, ordered history of all changes, supporting CQRS's separation by treating the event log as the single source of truth for writes.[1]
Events generated by the write model are then used to asynchronously build and update the read model through projections. Projection handlers subscribe to the event stream and transform events into optimized, query-friendly views, such as denormalized tables or indexes tailored for specific read operations.[20] This process allows the read model to evolve independently, often using technologies like in-memory caches or separate databases, while ensuring eventual consistency between the two sides of CQRS.[1]
The integration offers several advantages within CQRS architectures, including the ability to perform temporal queries by replaying events up to a specific point in time, comprehensive auditing through the immutable event log, and straightforward rebuilding of read models by reprocessing the entire event history.[19] Unlike traditional CRUD operations that mutate rows directly, commands in this setup only append events, reducing concurrency issues and enhancing scalability for high-write-load systems.[20]
The typical implementation flow begins with a command being validated and executed by the write model, which appends one or more events to the event store.[1] These events are then published, triggering projection handlers that update the read model asynchronously to reflect the changes.[20] This event-driven mechanism reinforces CQRS's asymmetry, as the write model remains focused on event appends without direct access to read-optimized structures, often leveraging message queues for reliable delivery.[19]
Variations exist in how tightly CQRS couples with Event Sourcing; "simple CQRS" implementations may forgo events entirely, using direct database updates for both command and query sides while separating interfaces, which simplifies development but limits scalability compared to the full event-based integration.[1]
Role in Domain-Driven Design
Command Query Responsibility Segregation (CQRS) aligns closely with Domain-Driven Design (DDD) principles by enabling the creation of distinct models for different aspects of the domain, thereby supporting the strategic and tactical patterns outlined in DDD. In DDD, the focus on modeling the core domain through bounded contexts and aggregates benefits from CQRS's separation of command and query responsibilities, which allows developers to tailor write models for enforcing business rules while optimizing read models for efficient data access. This separation promotes modularity and scalability in complex software systems, as articulated in foundational DDD literature extended by CQRS practices.[4][21]
Within bounded contexts, CQRS facilitates the use of separate models per context, where the write model enforces invariants specific to aggregates in one context, and the read model aggregates data across multiple contexts for comprehensive views without compromising isolation. Bounded contexts in DDD delineate areas where a particular model and ubiquitous language apply uniformly; CQRS enhances this by decoupling the transactional write side from the denormalized read side, reducing coupling between contexts and allowing independent evolution of each. For instance, in a logistics system, the write model might handle inventory updates within a warehouse context, while the read model provides optimized queries spanning supply chain contexts. This approach aligns with DDD's emphasis on context boundaries to manage large domains effectively.[22][21][4]
CQRS supports aggregate handling in DDD by directing commands to specific aggregates to maintain consistency boundaries, while queries bypass full aggregate loading to minimize complexity and contention. Aggregates serve as consistency units in DDD, and CQRS ensures that commands target the aggregate root for validation and state changes, avoiding the need to load extraneous data for read operations. This reduces the risk of violating aggregate invariants during queries and simplifies the domain model by focusing aggregates on behavior rather than data retrieval.[21][22]
The pattern further reinforces DDD's ubiquitous language by expressing domain intents through clearly named commands and queries, bridging technical implementations with business terminology. Commands and queries, such as "ProcessOrder" or "GetOrderStatus," use precise, domain-specific verbs that align with the shared language between developers and experts, fostering better communication and reducing misunderstandings. This linguistic precision helps embed the domain model directly into the code, a core tenet of DDD.[4][21]
In terms of anti-corruption layers, CQRS's distinct read and write models enable translation between subdomains or external systems without contaminating the core write model, acting as a protective facade. The read model can serve as an adapter that maps foreign data structures to the internal domain language, preserving the purity of aggregates and bounded contexts while integrating disparate sources. This mirrors DDD's anti-corruption layer pattern, adapted through CQRS's architectural separation.[23][4]
Post-2010, CQRS has evolved as a tactical pattern within DDD, particularly for scalable applications in complex domains such as finance and logistics, where high transaction volumes and varied query needs demand optimized models. Its adoption surged alongside microservices around 2014, integrating with DDD to handle distributed systems more effectively and enabling teams to scale bounded contexts independently. This evolution has positioned CQRS as a key enabler for maintaining DDD principles in production-scale environments.[22][4]
Benefits and Challenges
Advantages
Command Query Responsibility Segregation (CQRS) offers significant advantages in software architecture, particularly for complex systems where read and write operations have divergent requirements. One primary benefit is enhanced scalability, as CQRS enables the independent optimization and horizontal scaling of read and write paths. By separating these concerns, systems can scale query operations—often the bottleneck in high-traffic applications—without impacting command processing, allowing for targeted resource allocation such as additional read replicas or cloud-based auto-scaling for queries alone.[1][24]
Another key advantage is flexibility in modeling data for specific needs. CQRS permits the use of distinct models: the write model can enforce ACID compliance and complex business invariants, while the read model can be optimized for speed and analytics, such as denormalized views tailored to reporting queries. This separation accommodates diverse data storage technologies, like relational databases for writes and NoSQL for reads, fostering adaptability to evolving requirements without compromising either side.[1][25][24]
CQRS also simplifies system evolution by isolating changes to read models from the write side's stability. Development teams can iterate on query views, such as adding new projections or UI-driven reports, without risking disruptions to core business logic or data integrity on the command path. This modularity supports faster feature development and reduces the complexity of updates in large-scale applications.[24]
In terms of performance, the pattern excels by allowing denormalized read models that eliminate costly joins and enable specialized caching and indexing strategies aligned with common query patterns. Writes remain efficient through focused validation, while reads benefit from optimized data shapes, leading to reduced latency and higher throughput in read-heavy scenarios.[1][25][24]
Finally, testability is improved through the isolation of command and query handlers, which allows for focused unit testing of business logic without entanglement between read and write concerns. Developers can mock or stub one side independently, facilitating thorough verification of domain behaviors and reducing test fragility in integration scenarios.[24]
Drawbacks and Considerations
Implementing Command Query Responsibility Segregation (CQRS) introduces significant complexity due to the need for separate command and query models, which often requires duplicating code and logic across the two sides. This separation demands expertise in managing multiple data stores and synchronization mechanisms, increasing development and maintenance overhead.[17][26] Furthermore, the operational costs rise from handling diverse database technologies and potential hardware or cloud utilization expenses associated with them.[26]
A primary risk in CQRS arises from eventual consistency between the write and read models, where updates to the command side may not immediately propagate to the query side, leading to stale data that can confuse users in applications requiring real-time accuracy. This replication lag necessitates careful design of user interfaces to handle inconsistencies gracefully, such as displaying loading states or optimistic updates.[17][25]
The learning curve for CQRS is steep, making it unsuitable for simple CRUD applications where a unified model suffices and the added layers would represent over-engineering. It is particularly overkill for low-scale systems lacking high read-write disparities, as the benefits rarely outweigh the implementation challenges in such scenarios.[1][27]
Debugging CQRS systems poses difficulties, as issues must be traced across decoupled models and event flows, often requiring robust logging, monitoring, and observability tools to identify failures in synchronization or data propagation. The proliferation of databases and components creates more points of failure, complicating error resolution and system reliability.[25][26]
When considering adoption, CQRS is best suited for scenarios with pronounced differences in read and write loads, such as complex domains or high-performance applications; teams may begin with "simple CQRS" approaches—using the same model initially but separating concerns gradually—to prototype and mitigate risks.[1][27]
Examples
Real-World Applications
In the e-commerce domain, CQRS-like separations are implemented to handle order processing through transactional write models, while maintaining separate read-optimized structures for product catalogs that support extensive search and browsing capabilities. This approach allows for robust handling of high-throughput updates in inventory and payments alongside scalable query performance for user-facing searches.[11][28]
Fintech applications, such as banking systems deployed on Azure, leverage CQRS to isolate transaction writes—ensuring atomicity and regulatory compliance—from frequent balance and account queries that demand low-latency responses. This segregation enhances data integrity for financial operations while optimizing read paths for real-time reporting and user dashboards.[3][29]
Social media platforms apply similar CQRS patterns to differentiate content creation and moderation as write operations from the generation of personalized timeline feeds as read operations, facilitating global scalability for billions of daily queries. This design supports rapid content ingestion while distributing read loads across distributed caches and indexes.
Within microservices ecosystems, Netflix employs CQRS alongside event sourcing to manage user profiles and recommendations, processing millions of queries per second by decoupling write streams for profile updates from denormalized read models for content delivery. In specific implementations like download tracking, this pattern ensures eventual consistency across services handling vast streaming data volumes.[30]
CQRS principles are being explored in AI-driven applications, where they can separate write operations for ingesting and updating training datasets from read-intensive inference queries, enabling efficient scaling in agent-based systems that blend retrieval and action workflows.[31]
Illustrative Code Snippet
To illustrate the core structure of Command Query Responsibility Segregation (CQRS), the following pseudocode depicts a simplified user management example using a message bus for routing. In this setup, incoming requests are dispatched based on HTTP methods: POST requests (or equivalent mutations) route to the command bus for write operations, while GET requests (or equivalent reads) route to the query service for retrieval. Commands perform validation and append changes to a write store (e.g., an event-sourced append-only log) without returning data, instead publishing events asynchronously via the message bus to notify projections. Queries, in contrast, fetch data directly from a denormalized read store optimized for fast access. This separation ensures commands focus solely on side effects, adhering to CQRS principles as described by Greg Young.[4]
Command Example: CreateUserCommand
pseudocode
// Command definition: Immutable object representing the intent to create a user
class CreateUserCommand {
userId: UUID
[email](/page/Email): string
name: string
constructor(userId: UUID, [email](/page/Email): string, name: string) {
if (!isValidEmail([email](/page/Email))) {
throw ValidationError("Invalid email format")
}
if (name.length < 2) {
throw ValidationError("Name must be at least 2 characters")
}
this.userId = userId
this.email = [email](/page/Email)
this.name = name
}
}
// Command handler: Processes the command, appends to write store, publishes event
class CreateUserCommandHandler {
writeStore: AppendOnlyStore // e.g., event store
messageBus: MessageBus
handle(command: CreateUserCommand): void { // No return value
// Validate business rules (e.g., uniqueness via write store query if needed)
if (writeStore.existsUser(command.email)) {
throw BusinessRuleViolation("Email already in use")
}
// Append event to write store
userCreatedEvent = new UserCreatedEvent(
userId: command.userId,
email: command.email,
name: command.name,
timestamp: now()
)
writeStore.append(userCreatedEvent)
// Publish event to message bus for asynchronous propagation
messageBus.publish(userCreatedEvent)
}
}
// Routing example (e.g., in an [API](/page/API) gateway or controller)
if (request.method == "[POST](/page/Post)") {
commandBus.dispatch(new CreateUserCommand(request.userId, request.email, request.name))
response.status([202](/page/202)).send("[Accepted](/page/Accepted)") // No data returned
}
// Command definition: Immutable object representing the intent to create a user
class CreateUserCommand {
userId: UUID
[email](/page/Email): string
name: string
constructor(userId: UUID, [email](/page/Email): string, name: string) {
if (!isValidEmail([email](/page/Email))) {
throw ValidationError("Invalid email format")
}
if (name.length < 2) {
throw ValidationError("Name must be at least 2 characters")
}
this.userId = userId
this.email = [email](/page/Email)
this.name = name
}
}
// Command handler: Processes the command, appends to write store, publishes event
class CreateUserCommandHandler {
writeStore: AppendOnlyStore // e.g., event store
messageBus: MessageBus
handle(command: CreateUserCommand): void { // No return value
// Validate business rules (e.g., uniqueness via write store query if needed)
if (writeStore.existsUser(command.email)) {
throw BusinessRuleViolation("Email already in use")
}
// Append event to write store
userCreatedEvent = new UserCreatedEvent(
userId: command.userId,
email: command.email,
name: command.name,
timestamp: now()
)
writeStore.append(userCreatedEvent)
// Publish event to message bus for asynchronous propagation
messageBus.publish(userCreatedEvent)
}
}
// Routing example (e.g., in an [API](/page/API) gateway or controller)
if (request.method == "[POST](/page/Post)") {
commandBus.dispatch(new CreateUserCommand(request.userId, request.email, request.name))
response.status([202](/page/202)).send("[Accepted](/page/Accepted)") // No data returned
}
This command structure emphasizes validation within the command object and handler, with the handler focusing on mutating the write model and triggering events without querying external read stores.[4]
Query Example: GetUserByIdQuery
pseudocode
// Query definition: Immutable object specifying the [data](/page/Data) to retrieve
class GetUserByIdQuery {
userId: UUID
constructor(userId: UUID) {
this.userId = userId
}
}
// Query handler: Fetches from optimized read store (e.g., denormalized [view](/page/View))
class GetUserByIdQueryHandler {
readStore: ReadModelStore // e.g., relational DB or [cache](/page/Cache) with denormalized [data](/page/Data)
handle(query: GetUserByIdQuery): UserView { // Returns [data](/page/Data)
userView = readStore.getById(query.userId)
if (userView == null) {
throw NotFoundError("User not found")
}
return userView // Denormalized DTO with email, name, etc.
}
}
// Routing example
if (request.method == "GET") {
queryResult = queryBus.dispatch(new GetUserByIdQuery(request.userId))
response.[json](/page/JSON)(queryResult)
}
// Query definition: Immutable object specifying the [data](/page/Data) to retrieve
class GetUserByIdQuery {
userId: UUID
constructor(userId: UUID) {
this.userId = userId
}
}
// Query handler: Fetches from optimized read store (e.g., denormalized [view](/page/View))
class GetUserByIdQueryHandler {
readStore: ReadModelStore // e.g., relational DB or [cache](/page/Cache) with denormalized [data](/page/Data)
handle(query: GetUserByIdQuery): UserView { // Returns [data](/page/Data)
userView = readStore.getById(query.userId)
if (userView == null) {
throw NotFoundError("User not found")
}
return userView // Denormalized DTO with email, name, etc.
}
}
// Routing example
if (request.method == "GET") {
queryResult = queryBus.dispatch(new GetUserByIdQuery(request.userId))
response.[json](/page/JSON)(queryResult)
}
Queries remain lightweight, directly accessing a read-optimized store to avoid domain logic overhead, ensuring separation from write operations.[1]
Event Projection Example: Updating the Read Model
pseudocode
// [Event](/page/Event) [definition](/page/Definition): Immutable record of what occurred
[class](/page/Class) UserCreatedEvent {
userId: UUID
email: [string](/page/String)
name: [string](/page/String)
timestamp: DateTime
}
// Projection handler: Listens to events and updates read model asynchronously
class UserReadModelProjector {
readStore: ReadModelStore
messageBus: MessageBus // Subscribes to events
onUserCreated(event: UserCreatedEvent): void {
// Update denormalized read view (idempotent to handle replays)
if (!readStore.hasUser(event.userId)) {
userView = new UserView(
id: event.userId,
email: event.email,
name: event.name,
createdAt: event.timestamp
)
readStore.upsert(userView)
}
}
// Initialization: Subscribe to message bus
constructor() {
messageBus.subscribe("UserCreatedEvent", this.onUserCreated.bind(this))
}
}
// [Event](/page/Event) [definition](/page/Definition): Immutable record of what occurred
[class](/page/Class) UserCreatedEvent {
userId: UUID
email: [string](/page/String)
name: [string](/page/String)
timestamp: DateTime
}
// Projection handler: Listens to events and updates read model asynchronously
class UserReadModelProjector {
readStore: ReadModelStore
messageBus: MessageBus // Subscribes to events
onUserCreated(event: UserCreatedEvent): void {
// Update denormalized read view (idempotent to handle replays)
if (!readStore.hasUser(event.userId)) {
userView = new UserView(
id: event.userId,
email: event.email,
name: event.name,
createdAt: event.timestamp
)
readStore.upsert(userView)
}
}
// Initialization: Subscribe to message bus
constructor() {
messageBus.subscribe("UserCreatedEvent", this.onUserCreated.bind(this))
}
}
The projector listens for published events via the message bus and maintains the read model's consistency, allowing eventual synchronization between write and read sides without direct coupling. This approach supports scalability by decoupling updates from queries.[4]