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% бюджету (до $5,000 на місяць). CQRS перевершує традиційний CRUD за швидкістю читання в 10 разів при високих навантаженнях, забезпечуючи при цьому більшу відмовостійкість. Крім того, CQRS забезпечує в 5 разів кращу масштабованість, ніж CRUD при однаковому навантаженні. Зменшення часу простою — на 99.9% при правильній реалізації.

Мартін Фаулер у своїй статті: «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, навчання команди, підтримка на етапі запуску. Результат — готова масштабована архітектура, готова до зростання навантаження. Наша команда має сертифікації AWS Solutions Architect та Microsoft Azure, що гарантує якість впровадження. Ми надаємо гарантію на впровадження — 6 місяців безкоштовної підтримки.

Що входить в роботу

  • Аудит поточної архітектури та виявлення вузьких місць
  • Проектування 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 у вашому проекті. Отримайте консультацію інженера з архітектури. Зв'яжіться з нами для безкоштовного аудиту вашої поточної системи.