Что такое CQRS?

«Что такое CQRS?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

CQRS (Command Query Responsibility Segregation) — это архитектурный паттерн, который разделяет модель данных и операции на две отдельные части: Команды (Commands) для изменения состояния и Запросы (Queries) для чтения данных.

Ключевые концепции:

  • Команда (Command): Представляет намерение изменить состояние системы (например, PlaceOrderCommand, UpdateUserEmailCommand). Команда выполняется один раз, не возвращает данных (или возвращает только идентификатор/статус) и обладает побочными эффектами.
  • Запрос (Query): Представляет запрос на получение данных без изменения состояния системы (например, GetUserOrdersQuery). Запросы идемпотентны и не имеют побочных эффектов.

Практическая реализация (псевдокод на C#):

// Command и его обработчик (Write Model)
public class CreateProductCommand
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class CreateProductCommandHandler
{
    private readonly WriteDbContext _dbContext;
    public async Task Handle(CreateProductCommand command)
    {
        var product = new Product { Name = command.Name, Price = command.Price };
        _dbContext.Products.Add(product);
        await _dbContext.SaveChangesAsync();
    }
}

// Query и его обработчик (Read Model)
public class GetProductListQuery { }

public class ProductDto { public string Name; public decimal Price; }

public class GetProductListQueryHandler
{
    private readonly ReadDbContext _dbContext; // Может быть отдельная, денормализованная БД
    public async Task<List<ProductDto>> Handle(GetProductListQuery query)
    {
        return await _dbContext.ProductViews
            .Select(p => new ProductDto { Name = p.Name, Price = p.Price })
            .ToListAsync();
    }
}

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

  • Масштабируемость: Модели чтения и записи можно масштабировать независимо.
  • Гибкость: Можно использовать разные типы хранилищ, оптимизированные под конкретные задачи (SQL для команд, NoSQL или материализованные представления для запросов).
  • Более чистая модель: Разделение ответственности упрощает код и делает его более понятным.

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