← Назад к вопросам

Что такое CQRS?

2.7 Senior🔥 121 комментариев
#REST API и микросервисы#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

CQRS (Command Query Responsibility Segregation)

CQRS — это архитектурный паттерн, разделяющий логику чтения (Query) и записи (Command) данных в отдельные компоненты. Это мощный подход для построения высокопроизводительных и масштабируемых систем.

Основная идея

Вместо того чтобы использовать одну модель данных для операций чтения и записи, CQRS предлагает:

Традиционный подход:
┌─────────────────────┐
│   Application       │
└──────────┬──────────┘
           │
      ┌────▼────┐
      │Database  │
      └──────────┘

CQRS подход:
┌─────────────────────┬──────────────────┐
│  Commands (Write)   │  Queries (Read)  │
└──────┬──────────────┴────────┬─────────┘
       │                       │
┌──────▼─────────┐   ┌─────────▼────┐
│Write Database  │   │ Read Database │
└────────────────┘   │(Cache/View)   │
                     └────────────────┘

Компоненты CQRS

1. Commands (Команды) — операции, которые изменяют состояние

// Команда для создания заказа
public class CreateOrderCommand {
    private String customerId;
    private List<OrderItem> items;
    private BigDecimal totalAmount;
    
    // constructor, getters
}

// Command Handler обрабатывает команду
@Service
public class CreateOrderCommandHandler {
    public void handle(CreateOrderCommand cmd) {
        Order order = new Order(
            cmd.getCustomerId(),
            cmd.getItems(),
            cmd.getTotalAmount()
        );
        orderRepository.save(order);
        // Опубликовать событие OrderCreatedEvent
        eventPublisher.publish(new OrderCreatedEvent(order.getId()));
    }
}

2. Queries (Запросы) — операции, которые только читают данные

// Запрос для получения заказов
public class GetCustomerOrdersQuery {
    private String customerId;
    // constructor, getters
}

// Query Handler обрабатывает запрос
@Service
public class GetCustomerOrdersQueryHandler {
    private final OrderReadRepository orderReadRepository;
    
    public List<OrderDTO> handle(GetCustomerOrdersQuery query) {
        // Запрос из оптимизированного read store
        return orderReadRepository.findByCustomerId(query.getCustomerId());
    }
}

Различия между Write и Read моделями

АспектWrite ModelRead Model
ЦельГарантировать консистентностьБыстро предоставить данные
СтруктураНормализованная (3NF)Денормализованная
ОбновленияСинхронные, транзакционныеАсинхронные через события
ОптимизацияДля вставок/обновленийДля чтения
Примеры БДPostgreSQL, MySQLRedis, Elasticsearch, MongoDB

Процесс работы CQRS с Event Sourcing

1. Пользователь выполняет команду: "Создать заказ"
2. Command Handler обрабатывает команду
3. Генерируется событие: "OrderCreatedEvent"
4. Событие сохраняется в Event Store (source of truth)
5. Event Handler подписывается на событие
6. Read Model обновляется асинхронно (денормализованный вид)
7. Пользователь читает из Read Model (быстро!)

Пример на Java с Spring

// Event - неизменяемое событие
public class OrderCreatedEvent {
    private final String orderId;
    private final String customerId;
    private final BigDecimal amount;
    private final LocalDateTime createdAt;
    
    public OrderCreatedEvent(String orderId, String customerId, 
                             BigDecimal amount, LocalDateTime createdAt) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.amount = amount;
        this.createdAt = createdAt;
    }
}

// Event Publisher
@Component
public class EventPublisher {
    private final ApplicationEventPublisher publisher;
    
    public void publish(OrderCreatedEvent event) {
        publisher.publishEvent(event);
    }
}

// Event Handler - обновляет Read Model
@Component
public class OrderEventHandler {
    private final OrderReadRepository readRepository;
    
    @EventListener
    public void on(OrderCreatedEvent event) {
        OrderReadModel readModel = new OrderReadModel(
            event.getOrderId(),
            event.getCustomerId(),
            event.getAmount(),
            event.getCreatedAt()
        );
        readRepository.save(readModel);
    }
}

Преимущества CQRS

  1. Независимая масштабируемость - read сервис отдельно от write сервиса
  2. Производительность - оптимизация под специфику (write ≠ read)
  3. Простота запросов - денормализованные данные
  4. Аудит и история - все события хранятся (Event Sourcing)
  5. Масштабируемость read операций - можно добавить несколько read replicas

Недостатки CQRS

  1. Сложность - больше кода и компонентов
  2. Eventually consistent - временная несогласованность данных
  3. Необходимость синхронизации - между write и read моделями
  4. Оверхед - не нужен для простых приложений

Когда использовать CQRS

✓ Высоконагруженные системы с разными паттернами читать/писать ✓ Комплексные бизнес-правила ✓ Нужна история операций (Event Sourcing) ✓ Read-heavy приложения (аналитика, отчеты) ✓ Микросервисная архитектура

✗ Простые CRUD приложения ✗ Когда нужна strong consistency ✗ Маленькая команда без опыта

Заключение

CQRS — это не серебряная пуля, но мощный паттерн для сложных систем. Я использую его в проектах, где явно видна разница между паттернами чтения и записи, или когда нужна высокая масштабируемость.