«А что если», Event Sourcing

в 18:50, , рубрики: .net, event sourcing, архитектура приложений, Программирование, метки: ,

Наверное, про Event Sourcing слышал каждый, кто хоть раз пересекался с темой CQRS и DDD. Это подход хранения данных, при котором вместо конечного результата храниться череда записей о событиях происшедших с некоторой сущностью. На сайте Мартина Фаулера есть подробное описание, а мы же остановимся на фундаменте, основных «печенюшках», а также проблемах в его применении.

Фундамент

Ключевые механизмы, которые как раз и позволяют строить разный полезный функционал следующие:

  • Каждому событию дается имя, которое определяет его значение, т.е. присутствует семантика. Согласитесь есть огромная разница между «Событие 1» и «Корабль Отплыл».
  • Нет ограничений на кол-во событий для сущности. Соответственно новые события могут отражать, как и новые виды совершенных действий, так и расширять уже существующие, скажем, добавили новое свойство в его 2-ой версии.
  • Произошедшие события неизменны («immutable»).

«Печенюшки»

Естественно, раз подход применяется, значит, в нем есть то самое, «ОНО», ради чего игра стоит свеч.

История событий

Начнем с того, что Event Sourcing – это мечта для тех, кто хочет помнить все «как и почему», «что и когда», произошло в системе. Скажем, это аудит записи, только еще лучше за счет хранения информации «почему». Мне запоминался пример, который Greg Young использовал на одной из своих презентаций:

Банк хранит сведения о том, где проживают их клиенты и в один день происходит изменение одного из почтовых адресов. При хранении информации в таблице, мы максимум сможем увидеть, что запись обновилась. Если был подключен аудит, то в целом мы можем посмотреть на историю всех предыдущих значений. Но сможем ли мы узнать, почему она изменилась: может это была изначальная ошибка?, а может клиент переехал в другое место? С помощью Event Sourcing мы как раз и смогли бы ответить на этот вопрос, т.к. у нас было бы 2 события: КлиентПереехал, ИсправленаОшибкаАдреса.

Проекции

Естественно, раз вся полезная информация о сущности сохранена в потоке событий, то напрямую ей воспользоваться нельзя. Для UI нам необходимы плоские модели (проекции), которые мы как раз и создаем, используя обработчики событий, которые называются денормализаторы (denormalizers). Отличительным свойством можно назвать то, что такой обработчик можно менять и добавлять, как и когда угодно. В любой момент проекцию можно «выкинуть», проиграть все события от начала и новая готова. Небольшой пример кода:

public void Consume (ShipArrived message) {
readModel.Dock.Ships.Add(message.Ship);
} 
public void Consume (ShipDeparted message) {
readModel.Dock.Ships.Remove(message.Ship);
}

Конечно, кроме проекций для интерфейса, денормализация событий – это мощное средство построения разнообразных отчетов и проведения анализа event sourced сущности.

UI ориентированный на процесс

Замена основного хранилища данных (source of Truth) с нормализованных данных в таблицах, и CRUD операций над ними, на записи о событии, подталкивает на изменения к подходу в дизайне. Не выгодно иметь события в стиле CRUD, т.к. тогда теряется весь смысл в их семантике и привязке к происходящему. Значит необходимо строить такой интерфейс, где пользователь выполняет небольшие атомарные действия над сущностью. Т.е. дизайн интерфейса должен быть workflow driven, а не data input-based.

В целом это совпадает с тенденцией улучшения работы пользователя с приложением и замены многостраничных инструкций, на подсказки при совершении действий, т.е. обучению самим приложением.

Технические моменты

Пару технически интересных полезностей при таком подходе:

  • При интеграции с внешними системами можно передавать отфильтрованный поток событий. Это может упростить задачу в некоторых случаях.
  • Event Store можно сделать очень быстрым и расширяемым, т.к. события неизменны и могут только добавляться.
  • Проекции можно гео-реплицировать.

Проблемы

Как можно догадаться, они значительны.

Ад для рефакторинга

Итак, о чудо, наши события immutable, а их поток append-only. Хм, чтобы это значило? А значит это, что любое единожды происшедшее события необходимо поддерживать навечно. Их нельзя, не отменить, не удалить. Можно создавать новые версии событий, события которые компенсируют ошибки, — а это все новые и новые классы, которые необходимо добавить в имеющиеся обработчики и пронести по всей цепочке использования сущности. Это все ровно, что отнять право на ошибку у программиста, и никакого тебе “rollback” плана. Разве что вернуть базу событий к предыдущему снэпшоту, потеряв все действия пользователей, если конечно вы на такое будете согласны :-)

Нет, конечно, каждый из нас может, психанув, взять кувалду, перфоратор и пойти модифицировать произошедшие события в хранилище, но это скорее будет напоминать исправление данных в SQL базе, модифицируя файлы данных и индексов.

Просуммировав, это значит, что модель событий необходимо тщательно продумывать, отрисовывать и джуниора/мидла к ней не подпустишь. А если события уходят в другие компоненты… вообщем необходимо разграничить сразу внешние события и внутренние, а также запереть их определение в отдельный репозиторий. Ссылаться только через NuGet.

Нет готовых инструментов

Начиная разработку используя Event Sourcing, будьте готовы к тому, что все инструменты для работы с событиями придется написать самим. Конечно, есть EventStore от Jonathan Oliver, но это лишь малая часть. Вам понадобится графический и программный интерфейс их управлением: просмотра и поиска, построения и обновления проекций, создания снэпшотов для оптимизации чтения и др.

Взаимно компенсирующие события

Скажем, если пользователь отписался от новостей, а затем подписался… и так раз 50 (на протяжении года). В итоге есть только текущее состояние, что он подписан или отписан и все ок. Однако, если понадобится построить новый отчет или проекцию, где эти данные используются, то обработка таких событий бесполезно отнимет время, но хорошо загрузит машину.

Поддержка

Поддерживать решение на Event Sourcing дорого и сложно. В первую очередь из-за значительного времени на стадии дизайна, а также большого числа разных событий, которые необходимых создавать под каждый конкретный вид операций для соблюдения семантики.

Выводы

Как сказал один мой знакомый: «Мы не решаем проблемы, а переносим их в более комфортную для себя плоскость». Также можно сказать и про Event Sourcing. Каждый должен определить, нужен ли он для решения конкретной задачи, или нет.

Я же могу сказать следующее: используйте Event Sourcing для хорошо изученных сущностей и точечно, не пытайтесь построить на нем всю систему. Желательно, когда это не первая, а может и не вторая ее реализация.

Автор: Luminati

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js