Jonatan Matajonmatum.com
conceptosnotasexperimentosensayos
© 2026 Jonatan Mata. All rights reserved.v2.1.1
Conceptos

CQRS

Patrón que separa las operaciones de lectura y escritura en modelos distintos, optimizando cada uno independientemente para rendimiento y escalabilidad.

evergreen#cqrs#architecture#patterns#event-sourcing#read-write#scaling

¿Qué es?

CQRS (Command Query Responsibility Segregation) es un patrón arquitectónico que separa las operaciones de escritura (commands) de las de lectura (queries) en modelos completamente distintos. A diferencia de los sistemas CRUD tradicionales donde el mismo modelo maneja ambas operaciones, CQRS permite que cada lado use su propia base de datos, esquema, tecnología y estrategia de escalado.

El patrón se basa en el principio CQS (Command Query Separation) de Bertrand Meyer, pero lo extiende a nivel arquitectónico. Los commands modifican el estado del sistema sin retornar datos, mientras que las queries retornan datos sin modificar el estado. Esta separación permite optimizaciones específicas para cada tipo de operación.

CQRS no requiere necesariamente event sourcing, aunque ambos patrones se complementan naturalmente. Puede implementarse con bases de datos relacionales tradicionales, usando vistas materializadas o procesos de sincronización para mantener los modelos de lectura actualizados.

Implementación práctica

Ejemplo con Node.js y PostgreSQL

// Command side - Write model
interface CreateOrderCommand {
  customerId: string;
  items: OrderItem[];
  shippingAddress: Address;
}
 
class OrderCommandHandler {
  constructor(
    private orderRepository: OrderRepository,
    private eventBus: EventBus
  ) {}
 
  async handle(command: CreateOrderCommand): Promise<void> {
    const order = new Order(
      command.customerId,
      command.items,
      command.shippingAddress
    );
    
    await this.orderRepository.save(order);
    
    // Publish event for read model projection
    await this.eventBus.publish(new OrderCreatedEvent(order));
  }
}
 
// Query side - Read model
interface OrderSummaryQuery {
  customerId: string;
  status?: OrderStatus;
  dateRange?: DateRange;
}
 
class OrderQueryHandler {
  constructor(private readDatabase: ReadDatabase) {}
 
  async handle(query: OrderSummaryQuery): Promise<OrderSummary[]> {
    return this.readDatabase.query(`
      SELECT 
        order_id,
        customer_name,
        total_amount,
        status,
        created_at
      FROM order_summaries 
      WHERE customer_id = $1
        AND ($2::text IS NULL OR status = $2)
        AND created_at BETWEEN $3 AND $4
      ORDER BY created_at DESC
    `, [query.customerId, query.status, query.dateRange?.start, query.dateRange?.end]);
  }
}
 
// Event handler for projection
class OrderProjectionHandler {
  async on(event: OrderCreatedEvent): Promise<void> {
    await this.readDatabase.execute(`
      INSERT INTO order_summaries (
        order_id, customer_id, customer_name, 
        total_amount, status, created_at
      ) VALUES ($1, $2, $3, $4, $5, $6)
    `, [
      event.orderId,
      event.customerId,
      event.customerName,
      event.totalAmount,
      'PENDING',
      event.timestamp
    ]);
  }
}

Patrones de proyección

Proyección inmediata vs eventual

Loading diagram...

Proyección inmediata: Actualiza el modelo de lectura en la misma transacción. Garantiza consistencia pero reduce rendimiento y acoplamiento.

Proyección eventual: Usa eventos asincrónicos para actualizar el modelo de lectura. Mayor rendimiento y desacoplamiento, pero requiere manejar consistencia eventual.

Manejo de consistencia eventual

class OrderQueryService {
  async getOrderWithFallback(orderId: string): Promise<OrderView> {
    // Try read model first
    const orderView = await this.readModel.findById(orderId);
    
    if (orderView) {
      return orderView;
    }
    
    // Fallback to write model if not yet projected
    const order = await this.writeModel.findById(orderId);
    if (!order) {
      throw new OrderNotFoundError(orderId);
    }
    
    // Build view on-the-fly
    return this.buildOrderView(order);
  }
  
  private buildOrderView(order: Order): OrderView {
    return {
      id: order.id,
      customerName: order.customer.name,
      status: order.status,
      totalAmount: order.calculateTotal(),
      createdAt: order.createdAt
    };
  }
}

CQRS sin Event Sourcing

CQRS puede implementarse sin event sourcing usando sincronización de datos tradicional:

class OrderService {
  async createOrder(command: CreateOrderCommand): Promise<void> {
    const transaction = await this.database.beginTransaction();
    
    try {
      // Update write model
      const order = await this.orderRepository.create(command, transaction);
      
      // Update read model in same transaction
      await this.orderSummaryRepository.create({
        orderId: order.id,
        customerId: order.customerId,
        totalAmount: order.totalAmount,
        status: order.status
      }, transaction);
      
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }
}

Marco de decisión

CriterioCQRS RecomendadoCRUD Tradicional
Ratio lectura/escritura> 10:1< 5:1
Complejidad de queriesAlta, múltiples vistasSimple, CRUD básico
Requisitos de escaladoIndependiente por operaciónEscalado uniforme
ConsistenciaEventual aceptableInmediata requerida
Experiencia del equipoSistemas distribuidosAplicaciones monolíticas
Dominio de negocioComplejo, múltiples contextosSimple, un contexto

Anti-patrones comunes

Sobre-ingeniería: Aplicar CQRS a sistemas CRUD simples añade complejidad innecesaria. No todos los bounded contexts necesitan CQRS.

Proyecciones síncronas: Actualizar modelos de lectura sincrónicamente elimina los beneficios de escalabilidad y puede crear cuellos de botella.

Modelos de lectura normalizados: Mantener la normalización en modelos de lectura desperdicia la oportunidad de optimizar para queries específicas.

Falta de versionado: No versionar eventos o esquemas de proyección complica la evolución del sistema y las migraciones.

¿Por qué importa?

CQRS permite optimizar lecturas y escrituras independientemente, lo cual es crítico en sistemas donde los patrones de acceso difieren significativamente. En arquitecturas de microservicios, habilita que cada servicio optimice su modelo de datos para su dominio específico. La separación facilita el escalado horizontal: los modelos de lectura pueden replicarse geográficamente mientras que los de escritura se mantienen centralizados. Combinado con event-driven architecture, CQRS permite construir sistemas resilientes que pueden reconstruir el estado a partir de eventos, facilitando debugging, auditoría y análisis histórico.

Referencias

  • CQRS — Martin Fowler. Definición canónica del patrón y cuándo aplicarlo.
  • CQRS Pattern - Azure Architecture Center | Microsoft Learn — Microsoft, 2024. Guía de implementación con ejemplos prácticos.
  • A Beginner's Guide to CQRS — Event Store, 2024. CQRS con event sourcing y patrones de proyección.
  • CQRS pattern - AWS Prescriptive Guidance — AWS, 2024. Implementación en arquitecturas cloud-native.
  • Pattern: Command Query Responsibility Segregation (CQRS) — Chris Richardson. CQRS en el contexto de microservicios.
  • Event sourcing, CQRS, stream processing and Apache Kafka: What's the connection? | Confluent — Confluent, 2023. Integración con sistemas de streaming.

Contenido relacionado

  • Arquitectura Orientada a Eventos

    Patrón arquitectónico donde los componentes se comunican mediante eventos asíncronos, permitiendo sistemas desacoplados, escalables y reactivos.

  • Diseño Dirigido por el Dominio

    Enfoque de diseño de software que centra el desarrollo en el dominio del negocio, usando un lenguaje ubicuo compartido entre desarrolladores y expertos de dominio.

  • Event Sourcing

    Patrón donde el estado de la aplicación se deriva de una secuencia inmutable de eventos, proporcionando auditoría completa y la capacidad de reconstruir el estado en cualquier punto del tiempo.

  • Patrón Saga

    Patrón para gestionar transacciones distribuidas en microservicios mediante una secuencia de transacciones locales con acciones de compensación para manejar fallos.

Conceptos