Поэтому я некоторое время заигрываю с Event Sourcing и CQRS, хотя у меня никогда не было возможности применять шаблоны в реальном проекте.
Я понимаю преимущества разделения ваших проблем с чтением и записью, и я ценю то, как Event Sourcing облегчает проецирование изменений состояния в базы данных «Read Model», которые отличаются от вашего Event Store.
То, что я не очень ясно, это то, почему вы когда-либо регидратировать свои агрегаты из самого магазина событий.
Если проецировать изменения в «читаемые» базы данных так просто, почему бы не всегда проектировать изменения в «записывающей» базе данных, схема которой идеально соответствует вашей доменной модели? Это будет эффективно база данных снимков.
Я предполагаю, что это должно быть довольно распространенным явлением в приложениях ES + CQRS.
Если это так, является ли хранилище событий полезным только при перестройке вашей базы данных «записи» в результате изменений схемы? Или я что-то упустил?
источник
Ответы:
Потому что «события» - это книга рекордов.
Да; Иногда полезно оптимизировать производительность, чтобы использовать кэшированную копию агрегатного состояния вместо того, чтобы каждый раз восстанавливать это состояние с нуля. Помните: первое правило оптимизации производительности - «Не надо». Это добавляет дополнительную сложность к решению, и вы бы предпочли избегать этого, пока у вас не будет убедительной мотивации бизнеса.
Вам не хватает чего-то большего.
Во-первых, если вы рассматриваете решение, основанное на событиях, то это потому, что вы ожидаете, что будет полезно сохранить историю того, что произошло, то есть вы хотите внести неразрушающие изменения .
Вот почему мы вообще пишем в магазин событий.
В частности, это означает, что каждое изменение должно быть записано в хранилище событий.
Конкурирующие авторы могут потенциально либо уничтожать записи друг друга, либо приводить систему в непреднамеренное состояние, если они не знают о правках друг друга. Поэтому обычный подход, когда вам нужна согласованность, состоит в том, чтобы адресовать ваши записи в определенную позицию в журнале (аналог условного PUT в HTTP API). Неудачная запись говорит автору, что его текущее понимание журнала не синхронизировано, и что они должны восстановиться.
Возврат к известной хорошей позиции с последующим воспроизведением любых дополнительных событий, поскольку эта точка является обычной стратегией восстановления. Эта известная хорошая позиция может быть копией того, что находится в локальном кэше, или представлением в вашем хранилище снимков.
На счастливом пути вы можете сохранить снимок агрегата в памяти; вам нужно обратиться к внешнему хранилищу только тогда, когда нет локальной копии.
Кроме того, вам не нужно быть полностью захваченным, если у вас есть доступ к книге рекордов.
Поэтому обычный подход ( при использовании репозитория моментальных снимков) - поддерживать его асинхронно . Таким образом, если вам нужно восстановить, вы можете сделать это без перезагрузки и повторного воспроизведения всей истории агрегата.
Во многих случаях эта сложность не представляет интереса, потому что мелкозернистые агрегаты с ограниченным временем жизни обычно не собирают достаточно событий, чтобы выгоды превышали затраты на поддержание кэша моментальных снимков.
Но когда это подходящий инструмент для решения проблемы, загрузка устаревшего представления агрегата в модель записи с последующим обновлением его дополнительными событиями является вполне разумной вещью.
источник
Поскольку вы не указываете, для чего будет предназначена база данных «запись», я буду предполагать, что вы имеете в виду следующее: при регистрации нового обновления в агрегате вместо перестройки агрегата из хранилища событий вы вытащите его из базы данных «запись», подтвердите изменение и отправьте событие.
Если это именно то, что вы имеете в виду, то эта стратегия создаст условие для несогласованности: если новое обновление произойдет до того, как у последнего будет шанс внести его в базу данных «запись», новое обновление будет проверено на устаревшие данные, таким образом, потенциально выдает «невозможное» (то есть «запрещенное») событие и портит состояние системы.
Например, рассмотрим постоянный пример бронирования мест в театре. Чтобы избежать двойного бронирования, вам нужно убедиться, что забронированное место еще не занято - это то, что вы называете «проверка». Для этого вы сохраняете список уже забронированных мест в базе данных «запись». Затем, когда приходит запрос на бронирование, вы проверяете, есть ли запрошенное место в списке, и, если нет, запускаете «забронированное» событие, в противном случае отвечаете сообщением об ошибке. Затем вы запускаете процесс проецирования, где вы слушаете «забронированные» события и добавляете забронированные места в список в базе данных «запись».
Обычно система будет функционировать так:
Однако что делать, если запросы поступают слишком быстро, а шаг 5 происходит до шага 4?
Теперь у вас есть два события для бронирования одного и того же места. Состояние системы повреждено.
Чтобы этого не происходило, вы никогда не должны проверять обновления по проекции. Чтобы проверить обновление, вы перестраиваете агрегат из хранилища событий, а затем проверяете обновление по нему. После этого вы запускаете событие, но используете временную метку, чтобы убедиться, что с момента последнего чтения из магазина не было создано никаких новых событий. Если это не удается, просто повторите попытку.
Восстановление агрегатов из хранилища событий может привести к снижению производительности. Чтобы смягчить это, вы можете хранить совокупные снимки прямо в потоке событий, помеченные идентификатором события, из которого был создан снимок. Таким образом, вы можете перестроить агрегат, загрузив самый последний моментальный снимок и воспроизведя только те события, которые произошли после него, вместо того, чтобы всегда воспроизводить весь поток событий с начала времени.
источник
Основная причина - производительность. Вы можете сохранить снимок для каждого коммита (commit = события, которые генерируются одной командой, обычно только одним событием), но это дорого. Вдоль снимка необходимо также сохранить коммит, иначе это не будет Event Sourcing. И все это должно быть сделано атомарно, все или ничего. Ваш вопрос действителен, только если используются отдельные базы данных / таблицы / коллекции (в противном случае это был бы именно источник событий), поэтому вы вынуждены использовать транзакции , чтобы гарантировать согласованность. Транзакции не масштабируются. Поток событий только для добавления (хранилище событий) является основой масштабируемости.
Вторая причина - совокупная инкапсуляция. Вы должны защитить это. Это означает, что Агрегат должен иметь возможность изменять свое внутреннее представительство в любое время. Если вы храните его и сильно зависите от него, то вам будет очень трудно работать с версиями, что наверняка произойдет. В ситуации, когда вы используете снимок только в качестве оптимизации, при изменении схемы вы просто игнорируете эти снимки ( просто ? Я действительно так не думаю; удачи в определении того, что схема Aggregate изменяет - включая все вложенные объекты и объекты-значения) в эффективный способ и управление этим).
источник