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