Как разместить обработчики событий Event Sourcing для создания модели чтения?

Существуют различные примеры приложений и платформ, которые реализуют архитектуру CQRS + Event Sourcing и в большинстве из них описывается использование обработчика событий для создания денормализованного представления из событий домена, хранящихся в хранилище событий.

Одним из примеров размещения этой архитектуры является веб-API, который принимает команды на стороне записи и поддерживает запросы денормализованных представлений. Этот веб-интерфейс, вероятно, масштабируется до многих машин в ферме с балансировкой нагрузки.

Мой вопрос: где размещаются обработчики событий модели чтения?

Возможные сценарии:

  1. Размещается в одной службе Windows на отдельном хосте. Если так, разве это не создаст единственную точку отказа? Это, вероятно, усложняет развертывание, но гарантирует один поток выполнения. Недостатком является то, что модель чтения может демонстрировать увеличенную задержку.

  2. Размещается как часть самого веб-API. Если я использую EventStore, например, для хранения событий и обработки подписки на события, будут ли запускаться несколько обработчиков (по одному в каждом процессе веб-фермы) для каждого отдельного события и, таким образом, вызывать конфликт в обработчиках при попытке чтения / записи. в их магазине чтения? Или мы гарантируем, что для данного агрегатного экземпляра все его события будут обрабатываться по одному в порядке версии события?

Я склоняюсь к сценарию 2, так как он упрощает развертывание и также поддерживает менеджеров процессов, которые также должны прослушивать события. Такая же ситуация, поскольку только один обработчик событий должен обрабатывать одно событие.

Может ли EventStore справиться с этим сценарием? Как другие обрабатывают обработку событий в конечном итоге в согласованных архитектурах?

РЕДАКТИРОВАТЬ:

Чтобы уточнить, я говорю о процессе извлечения данных о событиях в денормализованные таблицы, а не о чтении этих таблиц для "Q" в CQRS.

Я предполагаю, что мне нужны варианты того, как мы "должны" реализовать и развернуть обработку событий для моделей чтения / sagas / etc, которые могут поддерживать избыточность и масштабирование, при условии, конечно, что обработка событий обрабатывается идемпотентным способом.

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

Событие автобус

Шина / очередь событий используется для публикации сообщений после сохранения события, как правило, реализацией репозитория. Заинтересованные стороны (подписчики), такие как модели чтения или менеджеры саг / процессов, используют шину / очередь "каким-то образом", чтобы обработать ее идемпотентным способом.

Если очередь является pub / sub, это подразумевает, что каждая последующая зависимость (модель чтения, sagas и т. Д.) Может поддерживать только один процесс каждый для подписки на очередь. Более чем один процесс будет означать, что каждый обрабатывает одно и то же событие и затем конкурирует за внесение изменений вниз по течению. Идемпотентная обработка должна заботиться о проблемах согласованности / параллелизма.

Если очередь конкурирует с потребителем, у нас по крайней мере есть возможность разместить подписчиков на каждом узле веб-фермы для обеспечения избыточности. Хотя для этого требуется очередь для каждой нисходящей зависимости; один для саг / менеджеров процессов, один для каждой модели чтения и т. д., и поэтому репозиторий должен будет публиковаться в каждом для возможной согласованности.

Подписка / корма

Подписка / канал, в котором заинтересованные стороны (подписчик) читают поток событий по требованию и получают события из известной контрольной точки для обработки в модель чтения.

Это отлично подходит для воссоздания прочитанных моделей при необходимости. Однако, в соответствии с обычным шаблоном pub / sub, может показаться, что для каждой нисходящей зависимости должен использоваться только один процесс подписчика. Если мы зарегистрируем несколько подписчиков для одного и того же потока событий, например, по одному в каждом узле веб-фермы, все они попытаются обработать и обновить одну и ту же соответствующую модель чтения.

2 ответа

Решение

В нашем проекте мы используем подписные проекции. Причины этого:

  • Передача на стороне записи должна быть транзакционной, и если вы используете две части инфраструктуры (хранилище событий и шина сообщений), вы должны начать использовать DTC, иначе вы рискуете сохранить ваши события в хранилище, но не опубликовать на шине, или наоборот, в зависимости от вашей реализации. DTC и двухфазные коммиты - неприятные вещи, и вы не хотите идти по этому пути
  • События в любом случае обычно публикуются в шине сообщений (мы делаем это также через подписку) для управляемой событиями связи между различными ограниченными контекстами. Если вы используете подписчиков сообщений для обновления своей модели чтения, когда вы решите перестроить модель чтения, другие ваши подписчики также получат эти сообщения, и это приведет систему в недопустимое состояние. Я думаю, что вы уже подумали об этом, когда говорили, что у вас должен быть только один подписчик для каждого опубликованного типа сообщения.
  • Потребители шины сообщений не имеют никакой гарантии в отношении порядка сообщений, и это может привести к тому, что ваша модель чтения станет беспорядочной.
  • Потребители сообщений обычно обрабатывают повторы, отправляя сообщение обратно в очередь и, как правило, к концу очереди для повторной попытки. Это означает, что ваши события могут сильно выйти из строя. Кроме того, обычно после некоторого числа повторных попыток потребитель сообщения отказывается от подозрительного сообщения и помещает его в некоторый DLQ. Если это будет ваш прогноз, это будет означать, что одно обновление будет игнорироваться, в то время как другие будут обрабатываться. Это означает, что ваша модель чтения будет в несогласованном (недействительном) состоянии.

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

Используя подписки, вы всегда проецируете события, которые уже были зафиксированы. Если что-то пойдет не так с проекциями, то сторона записи определенно является источником правды и остается таковой, вам просто нужно исправить проекцию и запустить ее снова.

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

Специально для EventStore у них теперь есть конкурирующие потребители, которые являются серверными подписками, где многие клиенты могут подписаться на группу подписок, но только один клиент получает сообщение.

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

Другие вопросы по тегам