Шаг 1: Оценка необходимости и определение границ. CQRS — не серебряная пуля. Начните с анализа проблем: ваше приложение страдает от конфликтов блокировок при частых чтениях и записях? Требуются абсолютно разные модели данных для API администратора и мобильного клиента? Бизнес-логика команд (изменения состояния) стала чрезвычайно сложной? Если ответ «да» на один из вопросов, CQRS может помочь. Определите *ограниченный контекст* (Bounded Context из DDD), где вы будете его применять. Не стоит внедрять CQRS во всём приложении сразу — начните с самого проблемного модуля, например, «Управление заказами» или «Аналитика».
Шаг 2: Разделение моделей. Это ядро паттерна. Создайте две независимые модели.
- **Командная модель (Write Model)**: ориентирована на поведение и инварианты предметной области. Здесь живут сущности, агрегаты, фабрики, репозитории. Её задача — валидировать и выполнять команды (например, `PlaceOrderCommand`, `UpdateUserProfileCommand`), изменяющие состояние системы. Эта модель часто следует принципам DDD.
- **Модель запросов (Read Model)**: оптимизирована для отображения данных. Это могут быть простые DTO, проекции из базы данных, денормализованные представления. Здесь нет бизнес-логики, только логика выборки. Часто для неё создаются отдельные, оптимизированные таблицы в БД или используется отдельное хранилище (например, Elasticsearch для полнотекстового поиска).
- **Команда**: это объект, содержащий все данные, необходимые для выполнения действия. Название команды — глагол в повелительном наклонении (`CancelOrderCommand`). Команда выполняется ровно один раз и изменяет состояние. Она не возвращает данных, кроме подтверждения или ID созданной сущности.
- **Запрос**: объект, содержащий критерии для выборки данных. Он не имеет побочных эффектов. Результатом выполнения запроса (`GetUserDashboardQuery`) является DTO или коллекция DTO.
- Получить соответствующий агрегат из репозитория (или создать новый).
- Вызвать на агрегате метод, соответствующий команде (например, `order.cancel(reason)`).
- Сохранить агрегат обратно в репозиторий.
- (Ключевой момент) После успешного сохранения опубликовать *доменное событие* (Domain Event), например, `OrderCancelledEvent`. Это событие будет триггером для обновления модели чтения.
Шаг 6: Синхронизация моделей (Самая сложная часть). Нужно обеспечить согласованность между моделями записи и чтения. Самый распространённый и надёжный способ — использование *событийной модели*.
- После сохранения агрегата командная модель публикует событие в шину (например, `OrderPlacedEvent` с ID заказа, товарами, суммой).
- Отдельный процесс (проектор, Projector) подписывается на эти события.
- Проектор обновляет модель чтения на основе полученного события: создаёт, изменяет или удаляет записи в оптимизированных таблицах для запросов.
Шаг 7: Выбор технологий и инфраструктуры. Для командной стороны часто используют ORM (Hibernate) для работы с агрегатами. Для модели чтения — более легковесные инструменты: JdbcTemplate, MyBatis, клиенты для Redis или Elasticsearch. Для шины событий подойдут Kafka, RabbitMQ или даже транзакционный outbox в той же БД для начала. Используйте фреймворки, такие как Axon или Spring Modulith, чтобы уменьшить объем boilerplate-кода.
Шаг 8: Тестирование. Тестируйте стороны независимо. Командную сторону — через модульные тесты агрегатов и интеграционные тесты обработчиков команд с заглушкой на публикацию событий. Сторону запросов — через тесты, проверяющие корректность возвращаемых данных. Критически важно тестировать проекторы: подайте на них событие и проверьте, корректно ли обновилась база данных для чтения.
Внедрение CQRS — это эволюционный путь. Начните с простой формы (разделенные модели на одной БД), а затем, при необходимости, переходите к сложной (разные БД, событийная шина). Фокус всегда должен оставаться на решении конкретных бизнес-проблем, а не на слепом следовании архитектурной моде.
Комментарии (5)