Расскажите об архитектурном паттерне CQRS. Каковы его преимущества и недостатки?

Ответ

CQRS (Command Query Responsibility Segregation) — это архитектурный паттерн, который предлагает разделить модель данных и логику приложения на две части: команды (Commands) для изменения состояния и запросы (Queries) для чтения состояния.

Как это работает?

  1. Команды (Commands):

    • Выражают намерение изменить состояние системы (например, CreateUser, UpdateOrderStatus).
    • Обычно не возвращают данные, а только подтверждение успеха или ошибку.
    • Обрабатываются CommandHandler'ами, которые содержат бизнес-логику и работают с моделью записи (часто нормализованной).
  2. Запросы (Queries):

    • Только читают данные и никогда не изменяют состояние системы (например, GetUserProfile, ListActiveOrders).
    • Возвращают DTO (Data Transfer Objects), специально подготовленные для отображения.
    • Обрабатываются QueryHandler'ами, которые могут читать данные из оптимизированной для чтения модели (например, денормализованной или из другой БД).

Пример на Go (концептуальный):

package main

// --- Модель для записи (Write Model) ---
type User struct { /* ... поля ... */ }

// Команда для создания пользователя
type CreateUserCommand struct {
    Name  string
    Email string
}

// Обработчик команды
func HandleCreateUser(cmd CreateUserCommand) error {
    // 1. Валидация
    // 2. Создание сущности User
    // 3. Сохранение в основную БД (например, PostgreSQL)
    // ... логика ...
    return nil
}

// --- Модель для чтения (Read Model) ---

// DTO для отображения профиля
type UserProfileDTO struct {
    Name        string
    TotalOrders int
}

// Запрос на получение профиля
type GetUserProfileQuery struct {
    UserID int
}

// Обработчик запроса
func HandleGetUserProfile(q GetUserProfileQuery) (UserProfileDTO, error) {
    // 1. Получение данных из оптимизированного хранилища (например, Elasticsearch или Redis)
    // ... логика ...
    return UserProfileDTO{}, nil
}

Преимущества:

  • Масштабируемость: Можно независимо масштабировать сервисы и базы данных для чтения и записи. Например, добавить больше реплик для чтения, не затрагивая сервис записи.
  • Производительность: Модель чтения можно оптимизировать под конкретные запросы (денормализация), что ускоряет их выполнение. Модель записи оптимизирована под транзакционную целостность.
  • Гибкость: Для чтения и записи можно использовать разные технологии баз данных (например, PostgreSQL для записи и Elasticsearch для чтения и поиска).
  • Разделение ответственности: Код становится чище и проще для понимания, так как логика чтения и записи не смешана.

Недостатки:

  • Усложнение архитектуры: Требуется больше кода, появляется "две модели" вместо одной, что увеличивает сложность системы.
  • Eventual Consistency (Итоговая согласованность): Данные в модели чтения могут обновляться с задержкой после их изменения в модели записи. UI/UX должен быть готов к тому, что только что созданный объект появится в списке не мгновенно. Это самая большая сложность при работе с CQRS.