Статьи

Модель для магазинов событий

В терминологии «Управляемый доменом» хранилище событий является механизмом сохранения для событий домена, которые используются главным образом для обеспечения связи между различными ограниченными контекстами (приложением, отображенным на несколько поддоменов).

Почему магазин событий

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

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

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

Bounded Context A -> Message Queue -> Bounded Context B

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

Bounded Context A -> Topic Queue -> Bounded Context B
                                 -> Bounded Context C

Представляем магазин событий

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

Bounded Context A -> Event Store -> Topic Queue -> Bounded Context A

Классический пример — банк, в котором все транзакции хранятся вместо остатков на счетах, поскольку каждый баланс может быть реконструирован по серии событий, произошедших с ним после открытия. Сам Event Store может быть создан на основе базы данных общего назначения или выбран в качестве готовых компонентов, таких как реализация Greg Young .NET .

Требования к такому магазину:

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

в то время как есть много компромиссов, которые мы можем сделать из-за характера шаблона событий домена:

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

Модель тянуть

Меня удивило, что передача событий в другой ограниченный контекст — не единственный механизм интеграции, особенно для REST-подобных и RESTful-сервисов. Источником этого ценного интеграционного паттерна было «Реализация доменного дизайна» Во Вернона .

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

  • / notifications / 0,19, который является архивным журналом событий.
  • / notifications / current — текущий журнал, содержащий последние произошедшие события; его диапазон всегда меньше 20 событий (например, 640 659)

Я выбираю 20 в качестве примера константы для элементов на странице. Каждое представление, созданное GET по этим URL, содержит несколько заголовков HTTP ссылки:

  • Ссылка: <… / notifications / 600,619>; отн = предыдущая
  • Ссылка: <… / notifications / 620,639>; отн = само
  • Ссылка: <… / notifications / 640,659>; отн = следующий

Как только один из этих ресурсов заполнится (содержит 20 событий), он будет кешироваться на бесконечное время и соответственно установить заголовки кэширования:

Cache-Control: max-age=10000000

При использовании стандартных механизмов HTTP, таких как обратные прокси или кэширование событий на стороне клиента, каждое приложение может очень эффективно опрашивать хранилище событий. Если у вас слишком много запросов отчетов по вашим базам данных, хранилище событий — это способ превратить многие из этих запросов в неизменные результаты, которые можно кэшировать вечно. С другой стороны, ваши потребители должны будут выполнить немного больше работы, например, сохранить последнее увиденное событие или ресурс, чтобы возобновить следующий опрос; и для прохождения даже неинтересных событий из-за более широкой детализации API-интерфейсов Event Store. Там нет такого понятия, как бесплатный обед.

Масштабирование магазина

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

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

  • / notifications / current генерирует дату up_to, равную 1 минуте назад.
  • В своих запросах он учитывает только данные create_at <up_to и подсчитывает сумму.
  • По модулю N = всего% 20 — количество событий, отображаемых в текущем фиде.
  • Для выбора данных курсор сортируется в порядке убывания ObjectId (ObjectId содержит метку времени с точностью до секунд).
  • Создаются ссылки для включения в качестве заголовков HTTP: self — это уведомления / $ LastDocumentIdOfN, 20, а предыдущая — это уведомления / -20, $ nextDocumentId . Чтобы узнать $ nextDocumentId, нужно выбрать N + 1 документов.

Особенность разбиения на страницы MongoDB заключается в том, что вы не можете использовать skip () для запуска из M-го документа коллекции, так как для этого потребуется выбрать все предыдущие документы. Предоставление ссылки на следующую и предыдущую страницу должно работать с индексированными объектными идентификаторами.

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