CQRS (Command Query Responsibility Segregation)
Architectural pattern separating read and write operations to optimize performance, scalability, and maintainability of complex systems.
Updated on January 9, 2026
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates data modification operations (commands) from data reading operations (queries). This separation enables independent optimization of each system aspect, using data models and scalability strategies tailored to each operation type. Originally popularized by Greg Young, CQRS addresses the limitations of traditional CRUD architectures in high-load distributed systems.
Fundamentals of CQRS Pattern
- Strict separation between write operations (Commands) that modify system state and read operations (Queries) that return data
- Use of distinct data models for writing (write model) optimized for consistency and validation, and for reading (read model) optimized for performance
- Model synchronization via event propagation mechanisms, enabling eventual consistency
- Complete decoupling between business write responsibilities and query needs, facilitating independent evolution of each part
Strategic Benefits
- Optimal scalability: ability to independently scale read infrastructure (often 90% of requests) and write infrastructure according to actual needs
- Improved performance: read models can be denormalized, pre-calculated, and cached for optimal response times
- Technology flexibility: freedom to use different databases (SQL for writes, NoSQL for reads for example) according to specific needs
- Enhanced security: clear separation of responsibilities enabling granular access policies between read and write operations
- Increased maintainability: isolation of complex business write logic from query logic, reducing technical debt
Practical Implementation Example
// ===== COMMANDS (Write Side) =====
interface CreateOrderCommand {
customerId: string;
items: OrderItem[];
shippingAddress: Address;
}
class OrderCommandHandler {
constructor(
private eventStore: EventStore,
private validator: OrderValidator
) {}
async handle(command: CreateOrderCommand): Promise<string> {
// Strict business validation
await this.validator.validate(command);
// Aggregate creation
const order = Order.create(command);
// Event persistence
const events = order.getUncommittedEvents();
await this.eventStore.save('order', order.id, events);
return order.id;
}
}
// ===== QUERIES (Read Side) =====
interface OrderSummaryQuery {
customerId: string;
page: number;
pageSize: number;
}
interface OrderSummaryDto {
orderId: string;
orderDate: Date;
totalAmount: number;
status: string;
itemCount: number;
}
class OrderQueryHandler {
constructor(private readDb: ReadDatabase) {}
async handle(query: OrderSummaryQuery): Promise<OrderSummaryDto[]> {
// Direct read from denormalized model
return this.readDb.query(`
SELECT
order_id,
order_date,
total_amount,
status,
item_count
FROM order_summaries
WHERE customer_id = $1
ORDER BY order_date DESC
LIMIT $2 OFFSET $3
`, [query.customerId, query.pageSize, query.page * query.pageSize]);
}
}
// ===== SYNCHRONIZATION =====
class OrderProjection {
async onOrderCreated(event: OrderCreatedEvent): Promise<void> {
// Read model update
await this.readDb.insert('order_summaries', {
order_id: event.orderId,
customer_id: event.customerId,
order_date: event.timestamp,
total_amount: event.totalAmount,
status: 'pending',
item_count: event.items.length
});
}
async onOrderShipped(event: OrderShippedEvent): Promise<void> {
await this.readDb.update('order_summaries',
{ order_id: event.orderId },
{ status: 'shipped' }
);
}
}Progressive Implementation
- Identify bounded contexts where CQRS brings real value (high read load, complex business write logic)
- Clearly define business commands with their validation rules and queries with their performance needs
- Implement the write model with event sourcing or traditional persistence depending on business complexity
- Create projections to build optimized read models, using materialized views or dedicated databases
- Set up synchronization mechanisms (message bus, CDC, polling) with eventual consistency management
- Monitor propagation delays and adjust synchronization strategy according to business SLAs
- Document consistency models so teams understand the guarantees provided
Architecture Advice
CQRS is not a pattern to apply everywhere. Reserve it for system parts where the read/write ratio is unbalanced (>70% reads) or where business write logic is complex. For simple CRUD operations, a classic architecture remains more appropriate and maintainable.
Associated Tools and Frameworks
- Axon Framework (Java): complete framework for CQRS and Event Sourcing with distributed support
- NestJS CQRS (TypeScript/Node.js): integrated module providing commands, queries, and event handlers
- MediatR (.NET): lightweight library for implementing the mediator pattern underlying CQRS
- EventStore: specialized database for event sourcing, often combined with CQRS
- Apache Kafka / RabbitMQ: message brokers for synchronization between write and read models
- PostgreSQL with LISTEN/NOTIFY: cost-effective solution for event propagation
- Redis: cache for high-performance read models with pub/sub support
CQRS radically transforms how we design systems by recognizing that read and write operations have fundamentally different needs. While more complex than traditional CRUD architecture, this pattern delivers measurable gains in performance, scalability, and evolvability for high-load systems or those with complex business logic.
