Ответ
CQRS (Command Query Responsibility Segregation) — это архитектурный паттерн, который предлагает разделить модель данных и логику приложения на две части: команды (Commands) для изменения состояния и запросы (Queries) для чтения состояния.
Как это работает?
-
Команды (Commands):
- Выражают намерение изменить состояние системы (например,
CreateUser
,UpdateOrderStatus
). - Обычно не возвращают данные, а только подтверждение успеха или ошибку.
- Обрабатываются
CommandHandler
'ами, которые содержат бизнес-логику и работают с моделью записи (часто нормализованной).
- Выражают намерение изменить состояние системы (например,
-
Запросы (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.