CQRS для веб-додатків: як розділення команд і запитів прискорює систему

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

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

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1364
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    960
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1191
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    933
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    950

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

Послуги бекенд-розробки: production-grade надійність

На production-сервері о 3:14 ночі черга Laravel Jobs перестала оброблятися — 40 000 необроблених завдань у Redis. Причина: worker упав через memory leak у статичній змінній Eloquent observer, supervisor не перезапустив через misconfigured stopwaitsecs. Ми розбирали такий інцидент на проекті з 500 RPS: діагностика 4 години, фікс — 20 хвилин. Щоб ви не втрачали гроші, пропонуємо послуги бекенд-розробки з акцентом на production-grade надійність — 10+ років досвіду, 50+ проектів, 5 років на ринку. Оцінимо ваш проект за 2 дні.

Які проблеми вирішуємо

N+1 запити: головний вбивця швидкості

N+1 — найпоширеніша причина повільних сторінок у Laravel-додатках. Стандартна історія: сторінка працювала нормально на dev з 10 записами, на production з 10 000 — 8-секундне завантаження.

Laravel Debugbar у dev-оточенні показує кількість запитів. Більше 20 — сигнал для audit.

Model::preventLazyLoading(! app()->isProduction());

Telescope для профілювання: логує всі запити, jobs, mail, notifications з деталізацією. Після впровадження eager loading час завантаження сторінки падає з 8 с до 0.3 с — у 27 разів.

Memory leak у статичних змінних

У Laravel Octane або Swoole додаток тримається в пам’яті між запитами. Статичні змінні не скидаються — призводять до неконтрольованого росту пам’яті. Використовуємо defer-функції та контейнерні біндинги для коректного скидання стану.

Неправильний connection pool

Rails, Laravel, Django відкривають нове з'єднання PostgreSQL на кожен PHP/Python процес. 100 воркерів — 100 з'єднань. PostgreSQL деградує від 200+ активних з'єднань через overhead на управління.

PgBouncer у transaction pooling: 1000 воркерів → 20–50 реальних з'єднань. Це знижує latency на 40% та зменшує витрати на хостинг на 30% — при середній вартості хостингу $2,000/міс економить $600/міс. GIN-індекс для JSONB до 100 разів швидший за B-tree при пошуку.

Як Octane справляється з високим навантаженням?

Laravel Octane (RoadRunner або Swoole) прибирає overhead bootstrap на кожен HTTP-запит. Приріст: 3–8x на синтетичних бенчмарках, 2–4x на реальних додатках. Важливо: не зберігати стан у статичних змінних — застосовуємо це на проектах >1000 RPS.

Як PostgreSQL допомагає уникнути повільних запитів?

Використовуємо composite indexes для WHERE + ORDER BY, partial indexes для фільтрів з високою селективністю, GIN-індекси для JSONB та full-text search. to_tsvector + GIN замість LIKE '%query%' — запобігає seq scan навіть на мільйонах записів. Аналізуємо плани через EXPLAIN ANALYZE та pg_stat_statements.

Як обрати стек для вашого проекту?

Стек Коли використовувати
Laravel + Octane CRUD, бізнес-логіка, REST/GraphQL API, адмінки
Node.js (Fastify) Realtime WebSocket, streaming, serverless, висока I/O concurrency
Go Високонавантажені мікросервіси (>10k RPS), gRPC, DevOps-інструменти
Django + DRF ML-пайплайни, інтеграція з AI, складна обробка даних
Ruby on Rails Швидкий MVP з багатим екосистемою гемів

Node.js виправданий для realtime: Laravel публікує події в Redis Pub/Sub, Node.js підписується та транслює клієнтам. Go — для goroutines (10k з'єднань на сервер — норма), але розробка повільніша, ніж Laravel.

Чому Redis критичний для продуктивності?

Redis виконує кілька ролей:

Роль Деталі
Кеш Кешування результатів важких запитів, фрагментів HTML
Черги Backend для Laravel Queue / Celery
Session store Distributed sessions в multi-instance оточенні
Pub/Sub Realtime події між сервісами
Rate limiting Sliding window counters для API throttling
Leaderboards Sorted Sets для рейтингів

Redis Cluster для горизонтального масштабування, Sentinel для автоматичного failover. Замовте консультацію щодо оптимізації Redis для вашого проекту.

Що входить в роботу під ключ

  • Архітектурне проектування (документація API, схема БД, діаграма сервісів)
  • Реалізація за узгодженим ТЗ з code review
  • Налаштування CI/CD (GitHub Actions, Docker), моніторингу (Sentry, Grafana), алертингу
  • Навантажувальне тестування (k6, wrk) зі звітом
  • Передача вихідних кодів, доступів, інструкція з деплою
  • Навчання команди замовника (2–3 сесії)
  • Гарантійна підтримка 1 місяць після здачі

Орієнтири по термінах

Задача Термін
REST API для мобільного/SPA (середня складність) 6–12 тижнів
Backend зі складною бізнес-логікою + інтеграції 12–20 тижнів
Високонавантажений сервіс на Go 8–16 тижнів
Міграція legacy PHP на Laravel 16–32 тижні

Вартість розраховується індивідуально після аналізу вимог до навантаження, інтеграцій та бізнес-логіки. Зв'яжіться з нами для безкоштовного аудиту вашого поточного backend — отримайте план оптимізації за 2 дні. Замовте консультацію та дізнайтеся, як знизити витрати на інфраструктуру на 30% без втрати продуктивності.