Чтобы узнать, кто изменил стоимость заказа, обычно достаточно нормально устроенного журнала аудита. Переводить ради этого весь продукт на Event Sourcing необязательно. Этот подход нужен для более сильного требования: состояние выбранной части системы должно восстанавливаться из последовательности сохранённых событий.
Удобный пример — учёт движений товара. Приход, резервирование, снятие резерва и отгрузка имеют разный смысл. Если они записаны как бизнес-факты, из истории можно получить текущий остаток и другие представления. Но обычный складской журнал сам по себе ещё не доказывает, что приложение построено на Event Sourcing: важно, что именно служит источником состояния.
Что становится первичным
В обычной системе главной может быть строка с текущим остатком, а история — дополнительной записью для проверки. В Event Sourcing первичен поток событий; состояние вычисляется их применением. Если основной результат нельзя восстановить без отдельной таблицы, содержащей незаписанные в событиях факты, обещание неполно.
Для быстрого открытия объекта сохраняют snapshot — состояние на определённой позиции потока — и применяют только последующие события. Для экранов и отчётов готовят отдельные представления. Snapshot ускоряет чтение, но не заменяет источник истины или его резервное копирование.
Это также не синоним событийной архитектуры. Публиковать сообщения другим сервисам можно без хранения собственной модели в виде событий. Различие между историей и восстановлением состояния хорошо показано в разборе Event Sourcing.
История не должна пересчитывать прошлое по сегодняшним правилам
Предположим, при продаже применили скидку. Если при восстановлении снова запросить действующий прайс и заново рассчитать скидку, результат может отличаться от первоначального. Нужно сохранять достаточные факты о принятом решении: использованную сумму, версию правила или необходимые исходные значения — в зависимости от модели.
Смена формата события тоже требует поддержки. Старое поле нельзя молча прочитать в новом смысле. Команда должна уметь обработать историческую версию либо явно преобразовать её представление, не меняя сам бизнес-факт. Исправление ошибочного факта обычно оформляют новым корректирующим событием.
Содержимое событий требует отдельной дисциплины: не стоит без необходимости помещать туда персональные данные, которые затем придётся исправлять, ограничивать в доступе или удалять. Эти требования нужно учитывать при проектировании хранения, а не после накопления истории.
Два человека могут одновременно потратить один остаток
Хранение только добавляемых событий не устраняет конкуренцию. Два обработчика могут прочитать одну версию остатка и оба решить, что товар доступен. При добавлении нового события необходимо проверить, что поток с момента чтения не изменился. Если изменился, решение принимают заново на актуальном состоянии.
Такая проверка защищает конкретный поток. Она не создаёт автоматически общую транзакцию для всех товаров, платежей и поставщиков. Ограничения конкуренции, снимков и производных представлений разобраны в Azure Architecture Center.
Восстановить отчёт — не значит повторить отгрузку
При повторном проигрывании истории нужно обновить представление, а не повторно отправить письмо или передать посылку перевозчику. Восстановление отделяют от внешних действий; поступающие новые события учитывают так, чтобы после переключения представления не появились пропуски или дубли.
Перед выбором решения полезен небольшой эксперимент на одной предметной области: удалить производное представление в тестовой среде, восстановить его из истории и сравнить результат. Проверить старые версии событий и запрет повторных внешних действий. Если такой сценарий бизнесу не нужен, а задача сводится к расследованию изменений, стоит начать с аудита и версионирования записей. Они не требуют менять способ работы всего приложения.