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

Что такое CQRS?

3.0 Senior🔥 151 комментариев
#Архитектура и паттерны

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

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

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

Что такое CQRS?

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

В традиционном подходе, таком как CRUD (Create, Read, Update, Delete), одна и та же модель данных используется как для чтения, так и для записи, что может приводить к сложностям при росте приложения. CQRS решает эти проблемы, явно разделяя ответственности.

Ключевые принципы CQRS

  1. Разделение моделей:

    • Модель команд — оптимизирована для обработки операций записи, валидации бизнес-логики и обеспечения консистентности данных.
    • Модель запросов — оптимизирована для быстрого чтения данных, часто денормализована и может использовать специализированные хранилища.
  2. Асинхронная обработка:

    • Команды могут обрабатываться асинхронно через очереди (например, RabbitMQ, Kafka), что улучшает отзывчивость системы.
    • После выполнения команды обновляется модель запросов, например, через события (Event Sourcing).
  3. Масштабируемость:

    • Модели чтения и записи могут масштабироваться независимо. Например, можно добавить больше реплик базы данных для запросов, если нагрузка на чтение высока.
  4. Гибкость технологий:

    • Для команд и запросов можно использовать разные СУБД. Например, команды — в PostgreSQL, а запросы — в Elasticsearch для полнотекстового поиска.

Пример реализации на PHP

Рассмотрим упрощенный пример CQRS в PHP-приложении. Допустим, у нас есть сущность Product.

1. Команда для создания продукта

<?php

class CreateProductCommand
{
    public function __construct(
        public string $name,
        public float $price,
        public int $stock
    ) {}
}

class CreateProductHandler
{
    public function __construct(private ProductRepository $repository) {}

    public function handle(CreateProductCommand $command): void
    {
        $product = new Product();
        $product->setName($command->name);
        $product->setPrice($command->price);
        $product->setStock($command->stock);
        
        $this->repository->save($product);
        
        // Может генерировать событие ProductCreated для обновления модели чтения
        event(new ProductCreated($product->getId(), $product->getName()));
    }
}

2. Запрос для получения списка продуктов

<?php

class GetProductsQuery
{
    public function __construct(
        public ?int $limit = 10,
        public ?int $offset = 0
    ) {}
}

class GetProductsHandler
{
    public function __construct(private ProductReadRepository $repository) {}

    public function handle(GetProductsQuery $query): array
    {
        // Используем репозиторий, оптимизированный для чтения
        return $this->repository->findAll($query->limit, $query->offset);
    }
}

// Пример специализированного репозитория для чтения
interface ProductReadRepository
{
    public function findAll(int $limit, int $offset): array;
}

class ElasticsearchProductRepository implements ProductReadRepository
{
    public function findAll(int $limit, int $offset): array
    {
        // Денормализованные данные из Elasticsearch для быстрого поиска
        return []; // Возвращаем данные в формате, удобном для отображения
    }
}

3. Интеграция с Event Sourcing (опционально)

CQRS часто сочетают с Event Sourcing, где состояние приложения определяется последовательностью событий. После выполнения команды генерируется событие, которое обновляет модель чтения.

<?php

class ProductCreatedListener
{
    public function __construct(private ProductReadRepository $readRepository) {}

    public function handle(ProductCreated $event): void
    {
        // Обновляем денормализованное представление в хранилище для запросов
        $this->readRepository->updateProjection($event->productId, $event->productName);
    }
}

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

  • Производительность: Модели чтения можно оптимизировать под конкретные запросы (например, с денормализацией), а команды — под строгую консистентность.
  • Масштабируемость: Разделение позволяет независимо масштабировать компоненты чтения и записи.
  • Гибкость архитектуры: Упрощается внедрение сложной бизнес-логики в командах, так как они изолированы от запросов.
  • Улучшенная поддержка: Код становится более чистым и понятным, поскольку ответственности разделены.

Недостатки и сложности

  • Сложность реализации: Требует дополнительных усилий по синхронизации моделей, особенно при использовании разных хранилищ.
  • Задержки данных: Модели чтения могут отставать от модели записи (eventual consistency), что не подходит для всех сценариев.
  • Оверкилл для простых систем: Для приложений с простой логикой CRUD CQRS может излишне усложнить архитектуру.

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

  • Высокая нагрузка на чтение или запись: Например, системы аналитики или платформы с частыми обновлениями.
  • Сложная бизнес-логика: Когда команды требуют много валидаций и транзакций.
  • Интеграция с другими системами: Если нужно предоставлять данные в разных форматах (API, отчеты).

В PHP-экосистеме CQRS часто применяется в сочетании с фреймворками (Symfony, Laravel) и библиотеками (Broadway, Tactician). Это мощный паттерн для сложных enterprise-приложений, но его внедрение должно быть оправдано требованиями проекта.