image de chargement
Back to glossary

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

order-cqrs.ts
// ===== 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

  1. Identify bounded contexts where CQRS brings real value (high read load, complex business write logic)
  2. Clearly define business commands with their validation rules and queries with their performance needs
  3. Implement the write model with event sourcing or traditional persistence depending on business complexity
  4. Create projections to build optimized read models, using materialized views or dedicated databases
  5. Set up synchronization mechanisms (message bus, CDC, polling) with eventual consistency management
  6. Monitor propagation delays and adjust synchronization strategy according to business SLAs
  7. 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.

Themoneyisalreadyonthetable.

In 1 hour, discover exactly how much you're losing and how to recover it.