PurpleSchool — курсы программирования онлайн
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развития
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • Карьерные пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • PurpleSchool — курсы программирования онлайн
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг

    CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг

    Аватар автора CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг

    Антон Ларичев

    Иконка календаря09 апреля 2026
    architecturetypescriptnodejsseniorИконка уровня senior
    Картинка поста CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг

    Введение

    CQRS и Event Sourcing — два архитектурных паттерна, которые часто упоминаются вместе, особенно в контексте микросервисов и сложных доменных моделей. На практике эти подходы могут кардинально улучшить масштабируемость и поддерживаемость системы, но только если применяются осознанно. В этой статье разберем, когда разделение команд и запросов действительно оправдано, а когда вы рискуете получить оверинжиниринг.

    Мы рассмотрим реальные примеры на TypeScript с использованием NestJS, покажем типичные ошибки внедрения и дадим четкие критерии для принятия решения — нужны ли вашему проекту CQRS и Event Sourcing.

    Что такое CQRS: разделение команд и запросов

    CQRS (Command Query Responsibility Segregation) — паттерн, в котором операции записи (команды) и чтения (запросы) разделены на уровне архитектуры. Вместо единой модели данных вы получаете write model для обработки бизнес-логики и read model, оптимизированную под конкретные сценарии чтения.

    // Команда — изменяет состояние
    class CreateOrderCommand {
      constructor(
        public readonly userId: string,
        public readonly items: OrderItem[],
        public readonly shippingAddress: Address,
      ) {}
    }
    
    // Запрос — только читает данные
    class GetOrdersByUserQuery {
      constructor(public readonly userId: string) {}
    }
    
    // Обработчик команды — работает с write model
    @CommandHandler(CreateOrderCommand)
    class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
      constructor(private readonly orderRepository: OrderRepository) {}
    
      async execute(command: CreateOrderCommand): Promise<string> {
        const order = Order.create(command.userId, command.items, command.shippingAddress);
        await this.orderRepository.save(order);
        return order.id;
      }
    }
    
    // Обработчик запроса — работает с read model
    @QueryHandler(GetOrdersByUserQuery)
    class GetOrdersByUserHandler implements IQueryHandler<GetOrdersByUserQuery> {
      constructor(private readonly readDb: OrderReadRepository) {}
    
      async execute(query: GetOrdersByUserQuery): Promise<OrderView[]> {
        return this.readDb.findByUserId(query.userId);
      }
    }
    

    Ключевое преимущество: write model может использовать нормализованную структуру с полной валидацией, а read model — денормализованные проекции, заточенные под конкретные экраны интерфейса.

    Что такое Event Sourcing: хранилище событий вместо текущего состояния

    Event Sourcing — подход, при котором состояние агрегата хранится не как текущий снимок, а как последовательность событий. Каждое изменение фиксируется как неизменяемое событие в event store, а текущее состояние восстанавливается путем воспроизведения всех событий.

    // Определяем события домена
    class OrderCreatedEvent {
      constructor(
        public readonly orderId: string,
        public readonly userId: string,
        public readonly items: OrderItem[],
        public readonly createdAt: Date,
      ) {}
    }
    
    class OrderPaidEvent {
      constructor(
        public readonly orderId: string,
        public readonly paymentId: string,
        public readonly amount: number,
      ) {}
    }
    
    class OrderShippedEvent {
      constructor(
        public readonly orderId: string,
        public readonly trackingNumber: string,
      ) {}
    }
    
    // Агрегат восстанавливает состояние из событий
    class OrderAggregate {
      private status: OrderStatus;
      private items: OrderItem[] = [];
      private totalAmount: number = 0;
    
      // Восстановление состояния из цепочки событий
      apply(event: DomainEvent): void {
        if (event instanceof OrderCreatedEvent) {
          this.status = OrderStatus.CREATED;
          this.items = event.items;
          this.totalAmount = this.calculateTotal(event.items);
        } else if (event instanceof OrderPaidEvent) {
          this.status = OrderStatus.PAID;
        } else if (event instanceof OrderShippedEvent) {
          this.status = OrderStatus.SHIPPED;
        }
      }
    
      // Бизнес-логика генерирует новые события
      pay(paymentId: string, amount: number): OrderPaidEvent {
        if (this.status !== OrderStatus.CREATED) {
          throw new Error('Заказ не может быть оплачен в текущем статусе');
        }
        if (amount < this.totalAmount) {
          throw new Error('Сумма оплаты меньше стоимости заказа');
        }
        return new OrderPaidEvent(this.id, paymentId, amount);
      }
    }
    

    Event Sourcing дает полный аудит-трейл, возможность временных запросов (какое состояние было в прошлый четверг?) и воспроизведение событий для построения новых проекций.

    Как реализовать CQRS в NestJS

    NestJS предоставляет встроенный модуль @nestjs/cqrs, который реализует базовую инфраструктуру для CQRS. Подключение занимает несколько минут:

    import { Module } from '@nestjs/common';
    import { CqrsModule } from '@nestjs/cqrs';
    
    @Module({
      imports: [CqrsModule],
      providers: [
        // Обработчики команд
        CreateOrderHandler,
        CancelOrderHandler,
        // Обработчики запросов
        GetOrdersByUserHandler,
        GetOrderDetailsHandler,
        // Обработчики событий — строят read model
        OrderCreatedProjection,
        OrderPaidProjection,
      ],
    })
    export class OrderModule {}
    

    Проекция подписывается на доменные события и обновляет read model:

    @EventsHandler(OrderCreatedEvent)
    class OrderCreatedProjection implements IEventHandler<OrderCreatedEvent> {
      constructor(private readonly readDb: OrderReadRepository) {}
    
      async handle(event: OrderCreatedEvent): Promise<void> {
        // Обновляем денормализованное представление для чтения
        await this.readDb.upsert({
          orderId: event.orderId,
          userId: event.userId,
          itemCount: event.items.length,
          status: 'created',
          createdAt: event.createdAt,
        });
      }
    }
    

    Для Event Sourcing понадобится дополнительное хранилище событий — EventStoreDB, PostgreSQL с таблицей событий или библиотека @ocoda/event-sourcing.

    Когда CQRS и Event Sourcing оправданы

    Есть четкие признаки того, что эти паттерны принесут пользу вашему проекту:

    Сложный домен с богатой бизнес-логикой. Если ваша система обрабатывает финансовые транзакции, управляет складом или координирует логистику — события являются естественным языком домена. «Заказ создан», «Платеж получен», «Товар отгружен» — это не технические абстракции, а реальные бизнес-факты.

    Необходимость аудита. Если регулятор или бизнес требует полную историю изменений — event sourcing решает эту задачу архитектурно, а не костылями в виде триггеров и лог-таблиц.

    Разные требования к чтению и записи. Когда на одну запись приходится тысяча чтений, или когда для чтения нужны десятки различных представлений одних и тех же данных — CQRS позволяет масштабировать read model независимо.

    Микросервисная архитектура. В распределенной системе события становятся контрактом между сервисами. Event sourcing обеспечивает eventually consistent интеграцию без двухфазных коммитов.

    Когда это оверинжиниринг: признаки избыточной сложности

    Не менее важно понимать, когда CQRS и Event Sourcing создадут больше проблем, чем решат:

    CRUD-приложения с простой логикой. Если ваш сервис — это справочник с операциями создания, чтения, обновления и удаления, стандартный репозиторий с ORM справится лучше. Разделение на команды и запросы здесь не даст выигрыша, но добавит слои абстракции.

    MVP и прототипы. На этапе проверки гипотезы скорость итераций важнее архитектурной чистоты. Event sourcing требует продумывания схемы событий заранее — а в MVP домен еще не стабилен.

    Маленькая команда без опыта. CQRS и Event Sourcing требуют понимания eventual consistency, идемпотентности, версионирования событий и построения проекций. Если команда из двух джунов — начните с монолита и простого CRUD.

    Нет требований к аудиту и аналитике. Если никому не нужна история изменений, а все запросы работают с текущим состоянием — event store становится ненужной прослойкой.

    Частые ошибки при внедрении Event Sourcing

    Применение ко всей системе сразу. Event sourcing не обязан покрывать каждый агрегат. Выделите bounded context, где он приносит максимальную пользу (платежи, заказы), и оставьте CRUD для справочников и настроек.

    Игнорирование снэпшотов. Когда у агрегата накапливается тысяча событий, восстановление состояния замедляется. Снэпшоты (периодическое сохранение текущего состояния) решают эту проблему:

    class SnapshotStore {
      async saveSnapshot(aggregateId: string, version: number, state: any): Promise<void> {
        await this.db.upsert({
          aggregateId,
          version,
          state: JSON.stringify(state),
          createdAt: new Date(),
        });
      }
    
      async loadAggregate(aggregateId: string): Promise<OrderAggregate> {
        // Загружаем последний снэпшот
        const snapshot = await this.getLatestSnapshot(aggregateId);
        // Догружаем только новые события после снэпшота
        const events = await this.eventStore.getEvents(
          aggregateId,
          snapshot?.version ?? 0,
        );
        const aggregate = snapshot
          ? OrderAggregate.fromSnapshot(snapshot.state)
          : new OrderAggregate();
        events.forEach((e) => aggregate.apply(e));
        return aggregate;
      }
    }
    

    Отсутствие версионирования событий. Бизнес-требования меняются, и схема событий тоже. Без стратегии миграции (upcasting) старые события станут несовместимы с новым кодом.

    Заключение

    CQRS и Event Sourcing — мощные паттерны, но не серебряная пуля. Используйте их в сложных доменах с высокими требованиями к аудиту, масштабируемости и аналитике. Начинайте с малого: внедрите CQRS в одном bounded context, убедитесь, что команда понимает eventual consistency, и только потом расширяйте. Если ваш проект — это CRUD-сервис без сложной бизнес-логики, не тратьте время на event sourcing. Лучшая архитектура — та, которая решает реальные задачи, а не демонстрирует знание паттернов.

    Иконка глаза10

    Комментарии

    0

    Постройте личный план изучения Основы Git до уровня Middle — бесплатно!

    Основы Git — часть карты развития Frontend, Backend, DevOps

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Основы JavaScript

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Продвинутый JavaScript

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    TypeScript с нуля

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее

    Похожие статьи

    Картинка поста NestJS: как организовать модули, чтобы проект не стал монолитом
    Иконка аватараАнтон
    Иконка календаря28 марта 2026
    nestjstypescriptarchitecturemiddleИконка уровня middle

    NestJS: как организовать модули, чтобы проект не стал монолитом

    Организация модулей в NestJS определяет масштабируемость проекта. Разберем принципы модульной архитектуры, разделение по доменам и типичные ошибки, превращающие проект в монолит.

    Иконка чипа0
    Иконка глаза225
    Иконка комментариев0
    Картинка поста Мониторинг приложения: Prometheus + Grafana для Node.js за час
    Иконка аватараАнтон
    Иконка календаря08 апреля 2026
    devopsnodejsmiddleИконка уровня middle

    Мониторинг приложения: Prometheus + Grafana для Node.js за час

    Настраиваем мониторинг Node.js приложения с Prometheus и Grafana за час: подключаем prom-client, собираем метрики, создаём дашборд и поднимаем всё через Docker Compose.

    Иконка чипа0
    Иконка глаза66
    Иконка комментариев0
    Картинка поста FastAPI vs NestJS: какой бэкенд-фреймворк выбрать в 2026
    Иконка аватараАнтон
    Иконка календаря31 марта 2026
    pythonnestjstypescriptmiddleИконка уровня middle

    FastAPI vs NestJS: какой бэкенд-фреймворк выбрать в 2026

    Сравнение FastAPI и NestJS — двух популярных бэкенд-фреймворков для разработки API. Разбираем производительность, архитектуру, экосистему и помогаем выбрать подходящий инструмент для вашего проекта в 2026 году.

    Иконка чипа0
    Иконка глаза220
    Иконка комментариев0
    Иконка чипа0