Что такое CQRS?

Ответ

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-приложений он может излишне усложнить архитектуру.

Ответ 18+ 🔞

А, CQRS, говоришь? Ну это ж классика, блядь! Сидишь такой, думаешь — ну что за мода, зачем всё усложнять? А потом вляпываешься в проект, где чтение и запись друг другу на голову лезут, и понимаешь: пора, сука, разводить этих дебилов по разным углам ринга.

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

Команда (Command) — это когда ты что-то меняешь. Создал заказ, обновил почту, удалил юзера — всё, состояние системы поплыло. Главное правило команды — она, как хороший киллер, не оставляет свидетелей в виде бизнес-данных. Выполнил задание — и либо "ок", либо "сорян, не вышло". Никаких лишних телодвижений.

Запрос (Query) — это полная его противоположность, тихий шпион. Его задача — вытащить инфу, не потревожив ни пылинки в системе. Посмотрел профиль, получил историю заказов — и слился. Никаких изменений, только данные.

А теперь, блядь, самое вкусное — зачем это всё?

  • Мозги не кипят. Когда модели для чтения и записи живут отдельно, они перестают друг другу мешать. Запросы не путаются под ногами у сложной логики обновления, и наоборот. Код сразу становится чище, хоть и его становится в два раза больше, это да.
  • Оптимизация на максимум. Это же просто мечта! Для записи можно оставить серьёзную реляционную базу, которая гарантирует, что всё сохранится чётко. А для чтения — да хоть MongoDB, Elasticsearch или просто закешированную хуйню, которая отдаёт данные со скоростью света. Разные инструменты для разных задач — красота.
  • Безопасность. Ну тут вообще пиздец как просто. На команды (изменение) можно навесить одни проверки прав — жёсткие. А на запросы (чтение) — другие, помягче. Никакой путаницы.
  • Масштабируемость. Если у тебя приложение читают в 100 раз чаще, чем пишут (как обычно и бывает), то ты просто накидываешь серверов под чтение, а скромный сервис записи пусть себе тихо работает в углу. И всё довольны.

Вот смотри, как это может выглядеть на 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-сайтика". Когда бизнес-логика запихивания данных становится монструозной, а требования к скорости отдачи — запредельными. Когда эти две сущности начинают драться за ресурсы и твой мозг.

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