TGViewer
Библиотека шарписта | C#, F#, .NET, ASP.NET Библиотека шарписта | C#, F#, .NET, ASP.NET @csharpproglib · 21.7K subscribers
Post #7030 2.86K
🛠 CQRS: что это и зачем оно вам

Мы часто упоминаем CQRS в контексте архитектуры. Пора разобраться, что за ним стоит и когда его стоит применять.

Что такое CQRS

CQRS расшифровывается как Command Query Responsibility Segregation. Суть простая: операции чтения и записи разделяются на два независимых потока.

Query — читает данные, ничего не меняет.
Command — меняет состояние, ничего не возвращает (кроме подтверждения).

В классическом подходе один репозиторий или сервис отвечает и за чтение, и за запись. Это удобно, пока система небольшая. Когда нагрузка растёт или модели чтения и записи начинают расходиться, появляются проблемы.

Какую боль решает CQRS

Представьте интернет-магазин. Команда на оформление заказа PlaceOrderCommand обновляет остатки, создаёт запись заказа и запускает цепочку событий. А запрос на страницу каталога GetProductsQuery просто возвращает список товаров с ценами.

Если один и тот же объект обслуживает оба сценария, вы получаете:

• модель данных, которая пытается угодить всем сразу

• сложные запросы с JOIN там, где нужна простая выборка

• трудности с масштабированием — читать нужно в 10 раз чаще, чем писать

CQRS позволяет разделить эти ответственности явно.

Как это выглядит на практике

Допустим, у нас есть приложение на C# с заказами. Для удобства используем MediatR — библиотеку, которая берёт на себя маршрутизацию команд и запросов.

Сначала определяем интерфейсы:
public interface ICommand : IRequest { }
public interface IQuery<TResult> : IRequest<TResult> { }


Команда на создание заказа:
public record PlaceOrderCommand(string UserId, string ProductId, int Quantity) : ICommand;

public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand>
{
private readonly IOrderRepository _repo;

public PlaceOrderCommandHandler(IOrderRepository repo) => _repo = repo;

public async Task Handle(PlaceOrderCommand command, CancellationToken ct)
{
var order = Order.Create(command.UserId, command.ProductId, command.Quantity);
await _repo.SaveAsync(order, ct);
}
}


Запрос на получение заказов пользователя:
public record GetUserOrdersQuery(string UserId) : IQuery<IReadOnlyList<OrderDto>>;

public class GetUserOrdersQueryHandler : IRequestHandler<GetUserOrdersQuery, IReadOnlyList<OrderDto>>
{
private readonly IOrderReadRepository _readRepo;

public GetUserOrdersQueryHandler(IOrderReadRepository readRepo) => _readRepo = readRepo;

public async Task<IReadOnlyList<OrderDto>> Handle(GetUserOrdersQuery query, CancellationToken ct)
=> await _readRepo.GetByUserIdAsync(query.UserId, ct);
}


Вызов из контроллера выглядит одинаково для команд и запросов:
// Команда
await _mediator.Send(new PlaceOrderCommand(userId, productId, quantity));

// Запрос
var orders = await _mediator.Send(new GetUserOrdersQuery(userId));


Обратите внимание: PlaceOrderCommandHandler работает с доменной моделью Order, а GetUserOrdersQueryHandler возвращает OrderDto — упрощённую структуру специально для отображения. Это ключевой момент.

IOrderRepository и IOrderReadRepository — два разных интерфейса. Первый смотрит в основную базу и знает о доменных правилах. Второй может работать с read-replica или отдельной проекцией данных.

Для чтения можно подключить read-replica или отдельную схему, оптимизированную под конкретные запросы. Запись при этом идёт в основное хранилище, а синхронизация происходит через события.

Когда применять

CQRS оправдан, если:

• нагрузка на чтение и запись сильно различается

• модели чтения и записи расходятся, например, вам нужны агрегированные данные на фронте, но нормализованное хранилище на бэке

• вы строите систему с событийной архитектурой или Event Sourcing

CQRS не нужен, если:

• у вас простой CRUD без сложной логики

• команда небольшая и накладные расходы на поддержку двух моделей не оправданы

Если вы чувствуете, что один репозиторий тащит на себе слишком много, это сигнал задуматься о разделении.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
  • ❤ 6
  • 👍 2
  • 😁 2
More from @csharpproglib
  1. Sep 22, 2026💡 Replace, Regex или StringBuilder? Для замены текста в C# есть несколько инструментов. И…
  2. Sep 21, 2026⚙️ Настоящие атомарные операции Если Volatile решает проблему видимости, то Interlocked ре…
  3. Sep 20, 2026💪 Разминка перед трудовыми буднями Что произойдёт? ❤️ — список станет [1, 3] 🔥 — Invalid…
  4. Sep 19, 2026🧩 Middleware в ASP.NET Core: 3 ловушки Middleware — звено HTTP pipeline: app.Use(async (c…
  5. Sep 18, 2026📍 Навигация: Вакансии • Задачи • Собесы 🐸Библиотека шарписта #garbage_collector
  6. Sep 17, 2026🦆 Duck typing в C# — почти как в TypeScript В TypeScript достаточно, чтобы у объекта был…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →