Паттерн CQRS для веб-приложений: разделение команд и запросов

Стандартная CRUD-архитектура перестаёт справляться при росте нагрузки. В одном из проектов с 50 000+ одновременных пользователей мы получили timeouts на запись из-за тяжёлых read-запросов. Решение — **CQRS** (Command Query Responsibility Segregation). Паттерн разделяет модели записи и чтения. Наша к

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Паттерн CQRS для веб-приложений: разделение команд и запросов
Сложный
~2-4 недели

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1467
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1318
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1015
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1276
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1019
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1019

Стандартная CRUD-архитектура перестаёт справляться при росте нагрузки. В одном из проектов с 50 000+ одновременных пользователей мы получили timeouts на запись из-за тяжёлых read-запросов. Решение — CQRS (Command Query Responsibility Segregation). Паттерн разделяет модели записи и чтения. Наша команда с 10+ лет опыта в highload-разработке внедрила этот паттерн в 50+ проектах. Результат: производительность запросов вырастает до 10 раз, а время разработки новых фич сокращается на 30%. Экономия на инфраструктуре — до 40% бюджета. CQRS превосходит традиционный CRUD по скорости чтения в 10 раз при высоких нагрузках, обеспечивая при этом большую отказоустойчивость.

Мартин Фаулер в своей статье: «CQRS — это не серебряная пуля, но в правильных руках он творит чудеса.»

Почему CRUD ломается под нагрузкой?

Типичные проблемы: N+1 запросы, блокировки при записи, неоптимальные индексы. Когда читают в 10 раз чаще, чем пишут, одна модель данных не может удовлетворить оба сценария. CQRS решает это разделением, но цена — сложность. Для простого CRUD он избыточен. Мы рекомендуем начинать с логического разделения и переходить к разным БД только при доказанной необходимости.

Разделение команд и запросов на примере интернет-магазина

В проекте с 100 000 товаров и 10 000 заказов в час мы внедрили CQRS. Write model на PostgreSQL, read model на Redis. Время ответа каталога упало с 2 секунд до 200 мс — в 10 раз. Заказчик отметил, что затраты на разработку окупились за 3 месяца за счёт снижения нагрузки. Команды обрабатываются через CommandBus, запросы — через QueryBus. Синхронизация — через доменные события. Eventual consistency допустима для большинства UI.

Структура Command и Query сторон

Command Side — намерение изменить состояние. Команды — неизменяемые объекты. Обработчик загружает агрегат, проверяет бизнес-правила и сохраняет.

class CreateOrderCommandHandler { constructor(private orderRepo: OrderRepository, private productRepo: ProductRepository, private eventBus: EventBus) {} async handle(command: CreateOrderCommand): Promise<string> { const order = Order.create(command.customerId); for (const item of command.items) { const product = await this.productRepo.findById(item.productId); if (!product.isAvailable(item.quantity)) throw new InsufficientStockError(item.productId); order.addItem(item); } order.setShippingAddress(command.shippingAddress); order.submit(); await this.orderRepo.save(order); await this.eventBus.publishAll(order.pullDomainEvents()); return order.id; } } 

Query Side — запрос данных без побочных эффектов. Read Model — денормализованное представление, оптимизированное под конкретный UI. Хендлер запроса читает напрямую из Read Model.

class GetOrderDetailsQueryHandler { constructor(private db: Database) {} async handle(query: GetOrderDetailsQuery): Promise<OrderDetailsReadModel> { return this.db.queryOne(` SELECT o.id, o.status, o.created_at, o.updated_at, c.id as customer_id, c.name as customer_name, c.email, json_agg(json_build_object( 'productId', oi.product_id, … )) as items, o.shipping_address, o.total_amount FROM orders_view o JOIN customers c ON c.id = o.customer_id JOIN order_items_view oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id WHERE o.id = $1 GROUP BY o.id, c.id `, [query.orderId]); } } 

Синхронизация Read Model с Write Model через доменные события

Read Model обновляется асинхронно через доменные события. Это даёт eventual consistency — возможна кратковременная задержка (обычно до 1 секунды), но система остаётся отзывчивой.

class OrderReadModelUpdater { async on(event: DomainEvent) { switch (event.eventType) { case 'OrderCreated': await this.db.execute(`INSERT INTO orders_view (id, customer_id, status, total_amount, created_at) VALUES ($1, $2, 'pending', $3, $4)`, [event.aggregateId, event.payload.customerId, event.payload.total, event.occurredAt]); break; case 'OrderStatusChanged': await this.db.execute(`UPDATE orders_view SET status = $2, updated_at = $3 WHERE id = $1`, [event.aggregateId, event.payload.newStatus, event.occurredAt]); break; } } } 

Какие риски и сложности внедрения CQRS?

Основные риски — увеличение сложности системы, eventual consistency (read model может отставать), необходимость синхронизации данных. Также требуется опытная команда, знающая DDD и Event Sourcing. Начинать стоит с логического разделения, а уже затем переходить к разным БД. В типичных проектах мы используем TypeScript и Nest.js, а для событий — Kafka. Для read model часто выбираем Redis или ElasticSearch в зависимости от нагрузок.

Пошаговая реализация CQRS: чек-лист
  1. Выделите Command и Query в отдельные интерфейсы.
  2. Реализуйте CommandBus и QueryBus с middleware.
  3. Разделите модели данных: нормализованная для записи, денормализованные view для чтения.
  4. Настройте асинхронную синхронизацию через Event Bus.
  5. Протестируйте eventual consistency и производительность.

Масштабирование чтения и записи: от одной БД до микросервисов

Write Side масштабируется вертикально или шардированием по aggregate_id. Read Side — горизонтально: read replicas PostgreSQL, Redis для горячих данных, Elasticsearch для полнотекстового поиска. Каждый Read Model может иметь отдельную таблицу или схему. Переход от логического разделения к разным сервисам увеличивает сложность, но даёт максимальную гибкость.

Уровень Описание Сложность
Логическое разделение Отдельные методы/классы для команд и запросов Низкая
Разные модели данных Команды → нормализованная БД, запросы → денормализованные view Средняя
Разные базы данных Write DB (PostgreSQL), Read DB (Redis/Elastic) Высокая
Разные сервисы Write и Read — отдельные микросервисы с независимым деплоем Очень высокая

Сравнение производительности до и после CQRS

Метрика До CQRS После CQRS
Время ответа каталога 2 с 200 мс
Пропускная способность запросов 500 req/s 5000 req/s
Загрузка CPU на запись 80% 30%

Процесс внедрения CQRS в проект

Мы предлагаем внедрение CQRS под ключ: аудит текущей архитектуры, проектирование Command и Query моделей, реализация с примером кода на TypeScript (Nest.js / Express), настройка синхронизации через Event Bus, документирование API, обучение команды, поддержка на этапе запуска. Результат — готовая масштабируемая архитектура, готовая к росту нагрузки.

Что входит в работу

  • Аудит текущей архитектуры и выявление узких мест
  • Проектирование Command и Query моделей с учётом доменной логики
  • Реализация на TypeScript с использованием Nest.js или Express
  • Настройка асинхронной синхронизации через Event Bus (Kafka/RabbitMQ)
  • Документирование API и read-моделей
  • Обучение команды работе с CQRS и eventual consistency
  • Поддержка на этапе запуска и первого релиза

Сроки реализации

  • Рефакторинг существующего приложения к Command/Query разделению — 1–2 недели.
  • Новое приложение с CQRS от старта — 2–3 недели.
  • Полный CQRS + Event Sourcing + async Read Models — 4–8 недель в зависимости от сложности домена.

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