Ленивый event sourcing или как жить сегодняшним днем
Перевод статьи опубликованной на Eventsourcing Publications. Статья описывает некоторые из идей примененных в проекте Eventsourcing.
Если вы читали статью Фаулера или подобные источники на тему event sourcing, у вас в мозгу могла остаться вот приблизительно такая картинка:
Общая идея такого подхода заключается в том, что пользователь (или любая другая внешняя система) генерирует команды, мы их обрабатываем, складывая полученные события в event store и обновляя «состояние мира» в базе данных, данные из которой запрашивает пользователь.
Этот подход выглядит просто и красиво. У нас есть достаточно данных чтобы «переигрывать» события, у нас есть откуда запрашивать данные о состоянии мира и мы можем использовать проверенные временем базы данных. С другой стороны, я обратил внимание что я хотел немного другого от концепции event sourcing. Мне хотелось избежать предугадывания будущего и эта модель как-то не очень подходила, потому что мне приходилось записывать обновленное состояние в мою базу данных «для чтения».
Сама идея того что мы собираем намерения к действиям и все сопутствующие данные приводит к мысли о том что можно всегда посмотреть в прошлое и использовать информацию, от которой не было особого толку во время сбора этой самой информации (но теперь есть новые наблюдения, новый функционал, и т.п.) Что же это означает? Должен ли я обновлять схему моей базы данных чтоб поддержать новые структуры? Должен ли я переиграть все события чтобы перезаполнить базу данных?
Если честно, хотелось бы избежать таких сложностей.
За свою карьеру я разработал достаточно проектов чтоб понять несколько простых истин. Одна из этих истин — самая постоянная штука — это изменения. Фактически, нет нормального способа как узнать как будет выглядеть твое приложение и модель данных через несколько месяцев. Может разве что после того как переписано было все три раза с нуля, да и то это под вопросом.
Что если я могу отложить угадывание будущего до того момента, как я буду собственно в этом самом будущем? Так как я записываю каждое событие, я всегда могу перестроить «состояние мира», проигрывая все старые события еще раз, но, как я отметил раньше, это весьма дорогое удовольствие.
Но что если я могу просто посылать запросы на поиск событий которые подпадают под необходимые критерии, прямо во время запроса от пользователя? Например, представим что у нас есть пользователи и у них есть email адреса. Предположим, что мы создали события UserCreated и EmailChanged. Что если вместо того чтобы переигрывать все события для всех пользователей (или даже для одного!), мы просто будем искать UserCreated (id:) и последний EmailChanged (userId:)?
Хм. Мы можем запрашивать необходимую информацию о пользователи только по необходимости, не переигрывая события!
// Pseudocode-ish User's email retrieval
public class User {
public String email() {
return query(equal(EmailChanged.ID, id),
descending(EmailChanged.TIMESTAMP)).first().
email();
}
Если алгоритм для выяснения email’а пользователя когда либо изменится, все что нам надо сделать это поменять метод User#email (). Более того, пока я не напишу сам метод User#email (), мне не нужно знать что это именно то, как я буду структурировать данные для презентации. Я просто должен записать все данные в моих событиях, по возможности не упуская ничего.
Что еще более интересно, этот подход ведет к следующему умозаключению: на самом деле, нету универсальной версии «состояния мира». Так же как мы, люди, воспринимаем мир и реальность вокруг нас по-разному, в зависимости от контекста, программы могут воспринимать реальность через разные призмы и я не вижу причин почему они должны быть ограничены в этом.
Есть ли проблемы у этого подхода? Конечно. Например, если какой-то из ваших запросов на поиск занимает много времени. Но, когда такие узкие места обнаружены, они могут быть оптимизированы через какой-то вариант препроцессинга или кеширования. Хорошо бы конечно избегать дорогостоящих запросов, но мы живем в реальном мире.
Значит ли это что что мы вообще не должны иметь обработчиков событий, которые исполняются во время записи событий? Не думаю, это хороший способ реагировать на изменения, особенно в доменах которые реализованы сторонними компонентами. Просто я не вижу смысла делать это для каждого события.
Вот, кстати, еще одно интересное последствие использования этого подхода. С растущей популярностью GraphQL как API, все больше и больше приложений оптимизируют объем передаваемых данных и количество запросов, запрашивая и получая только тот минимум который им необходим. Вместе с «ленивым» сбором данных из событий мы никогда не строим «состояния мира» на те части данных, которые не запрашиваются.
Вышеописанный подход используется в проекте Eventsourcing, в том числе в его расширении под названием Domain Protocol. У проекта также есть адаптор к GraphQL. Проект полностью открыт и лицензирован под Mozilla Public License 2.0. Недавно была создана некоммерческая организация Eventsourcing, Inc. для дальнейшего развития и поддержки проекта.