---
title: "CQRS"
type: "architectural-pattern"
slug: "cqrs"
url: "http://localhost:3000/ru/architectural-patterns/cqrs.md"
also_known_as: "Command Query Responsibility Segregation"
description: "Разделите модель, изменяющую состояние (команды), и модель, читающую состояние (запросы), чтобы каждую сторону можно было моделировать, масштабировать и оптимизировать независимо."
languages: ["typescript"]
---
# CQRS

_Also known as: Command Query Responsibility Segregation_

> Разделите модель, изменяющую состояние (команды), и модель, читающую состояние (запросы), чтобы каждую сторону можно было моделировать, масштабировать и оптимизировать независимо.

## Intent

Используйте одну модель для обновления информации и другую модель для её чтения. Команды выражают намерение изменить состояние; запросы возвращают данные в форме, удобной для отображения. Этим двум моделям больше не нужно идти на компромисс ради единого общего представления.

## Problem

В традиционном дизайне одна модель обслуживает и запись, и чтение. Записи требуют богатой валидации и инвариантов; чтения требуют денормализованных, готовых к отображению форм — нередко множества разных форм для разных экранов и отчётов.

Принуждение проводить и то и другое через одни и те же объекты и таблицы ведёт к неуклюжим компромиссам: избыточная выборка данных, сложные ORM-отображения, конкуренция за блокировки и модель, которая плохо справляется с обеими задачами.

## Solution

CQRS разделяет систему на две части. **Сторона записи** обрабатывает _команды_ через обработчики, которые загружают агрегат, обеспечивают соблюдение инвариантов и сохраняют изменение (опционально порождая события). **Сторона чтения** обслуживает _запросы_ из одной или нескольких _моделей чтения_, сформированных специально под потребляющие их представления.

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

## Structure

* **Команда (Command)** — запрос на изменение состояния, названный по намерению (`PlaceOrder`).
* **Обработчик команды (Command handler)** — валидирует и применяет команду к модели записи.
* **Модель записи (Write model)** — агрегаты, обеспечивающие соблюдение инвариантов; могут порождать **события**.
* **Модель чтения / проекция (Read model / projection)** — денормализованные представления, построенные под запросы и обновляемые из событий.
* **Запрос (Query)** & **обработчик запроса (query handler)** — запросы только для чтения, обслуживаемые из модели чтения.

## Applicability

* Применяйте его там, где **чтение и запись имеют сильно различающуюся форму или нагрузку** — множество представлений для чтения, тяжёлая отчётность или высокое соотношение чтения к записи.
* Применяйте его для **интерфейсов, основанных на задачах**, и совместно используемых предметных областей, где он естественно сочетается с предметно-ориентированным проектированием (DDD) и event sourcing.
* **Избегайте его** для простого CRUD; единая модель проще, а разделение добавляет издержки без отдачи.

## How to Implement

1. Моделируйте **команды** как явные намерения, а не как обобщённые обновления.
2. Направляйте каждую команду в **обработчик**, который загружает соответствующий агрегат и обеспечивает соблюдение его инвариантов.
3. Сохраняйте изменение и, если используются события, **публикуйте** произошедшее.
4. Стройте **модели чтения**, адаптированные под ваши представления; если они отделены, обновляйте их, проецируя события.
5. Обслуживайте **запросы** напрямую из моделей чтения — никакой доменной логики на стороне чтения.

## Pros

* Стороны чтения и записи можно моделировать, оптимизировать и масштабировать независимо.
* Каждая модель остаётся небольшой и сосредоточенной на одной задаче.
* Подходит для интерфейсов, основанных на задачах, и хорошо сочетается с event sourcing и DDD.
* Модели чтения можно адаптировать под каждое представление, устраняя неуклюжие соединения (joins).

## Cons

* Больше движущихся частей и больше кода, чем у единой CRUD-модели.
* Отдельные хранилища для чтения вводят итоговую согласованность (eventual consistency), которую должен учитывать UX.
* Легко переусложнить; редко оправдано для простых предметных областей.
* Эксплуатационные издержки на проекции и инфраструктуру обмена сообщениями.
## Relations

**Related patterns**

- [Предметно-ориентированное проектирование](/ru/architectural-patterns/domain-driven-design.md)

## Code Examples

### typescript

```typescript
// Сторона записи: команда и её обработчик.
type PlaceOrder = { orderId: string; sku: string; qty: number }

class PlaceOrderHandler {
  constructor(private orders: OrderRepository) {}
  async handle(cmd: PlaceOrder) {
    const order = new Order(cmd.orderId)
    order.addLine(cmd.sku, cmd.qty)
    await this.orders.save(order) // порождает OrderPlaced
  }
}

// Сторона чтения: запрос, обслуживаемый из денормализованного представления.
type GetOrderSummary = { orderId: string }

class GetOrderSummaryHandler {
  constructor(private views: OrderSummaryView) {}
  handle(q: GetOrderSummary) {
    return this.views.byId(q.orderId) // заранее подготовлено под экран
  }
}
```

