Как реализовать паттерны event sourcing и CQRS
Что проверяют
Различаете ли вы два независимых приёма и понимаете ли, чем за них платят.
Как отвечать
Сразу разделите. CQRS это разделение модели записи и модели чтения. Event sourcing это хранение не текущего состояния, а последовательности событий, из которых состояние восстанавливается. Их часто применяют вместе, но они независимы: CQRS работает и поверх обычной таблицы.
Event sourcing на практике. Есть таблица событий: идентификатор агрегата, номер версии, тип события, полезная нагрузка, время. Запись идёт только вставкой, обновлений нет. Агрегат загружается проигрыванием своих событий по порядку, обрабатывает команду и порождает новое событие. Конкурентность решается оптимистичной блокировкой: уникальный индекс на пару из агрегата и версии, вставка с неверной версией падает, команда повторяется. Чтобы не проигрывать тысячу событий каждый раз, периодически сохраняют снимок состояния.
CQRS на практике. Команды идут в агрегат, а запросы читают отдельные модели чтения, заточенные под конкретный экран. Проекции строятся обработчиками событий и обновляются асинхронно, отсюда согласованность в конечном счёте: пользователь может не увидеть свою запись сразу после сохранения. Это надо закладывать в интерфейс.
В экосистеме Java есть Axon и Eventuate, но собственная реализация на таблице событий и Kafka встречается чаще.
Цена, которую надо назвать:
- версионирование событий: событие хранится вечно, а формат меняется. Нужны апгрейдеры старых версий при чтении;
- удаление персональных данных конфликтует с неизменяемым журналом. Обходят шифрованием с удалением ключа;
- перестроение проекций занимает время и должно быть штатной операцией, а не подвигом;
- отладка усложняется, потому что состояние нигде не лежит целиком.
Когда это оправдано: обязателен аудит, ценна история изменений, сложная предметная логика. Для обычного CRUD это лишняя стоимость.
Где спотыкаются
Считают CQRS и event sourcing одним и тем же.
Забывают про оптимистичную блокировку и допускают потерянные обновления.
Не закладывают задержку проекции в интерфейс.
Не продумывают, что делать со старым форматом событий.