Что такое CQRS?

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

Ответ

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

Основные концепции

  • Команда (Command): Изменяет состояние системы (например, CreateOrder, UpdateUserEmail). Команда не возвращает бизнес-данные, только подтверждение (успех/ошибка).
  • Запрос (Query): Возвращает данные, не изменяя состояние системы (например, GetUserProfile, GetOrderHistory).

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

  • Разделение ответственности: Упрощает код, так как модели чтения и записи решают разные задачи.
  • Независимая оптимизация: Можно использовать разные базы данных (например, SQL для команд и оптимизированное NoSQL/кэш для запросов).
  • Повышенная безопасность: Легче применять разные правила авторизации для операций изменения и просмотра данных.
  • Масштабируемость: Чтение и запись можно масштабировать независимо друг от друга.

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

// Команда
public record CreateProductCommand(string Name, decimal Price);

public class ProductCommandHandler
{
    private readonly WriteDbContext _dbContext;
    public ProductCommandHandler(WriteDbContext dbContext) => _dbContext = dbContext;

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

// Запрос
public record GetProductQuery(int ProductId);

public class ProductQueryHandler
{
    private readonly ReadDbContext _dbContext;
    public ProductQueryHandler(ReadDbContext dbContext) => _dbContext = dbContext;

    public async Task<ProductView> Handle(GetProductQuery query)
    {
        // Используется упрощенная модель, оптимизированная для чтения
        return await _dbContext.ProductViews
            .FirstOrDefaultAsync(p => p.Id == query.ProductId);
    }
}

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

Паттерн оправдан в системах со сложной бизнес-логикой, высокими нагрузками, где требования к чтению и записи сильно различаются. Для простых CRUD-приложений он может излишне усложнить архитектуру.