Шаблон источников событий
Вместо хранения только текущего состояния данных в реляционной базе данных сохраните полный ряд действий, выполняемых для объекта в хранилище только для добавления. Хранилище действует как система записи, и его можно использовать для материализации объектов домена. Такой подход может повысить производительность, масштабируемость и аудит в сложных системах.
Это важно
Выбор событий — это сложный шаблон, который пронизывает всю архитектуру и создает компромиссы для повышения производительности, масштабируемости и удобства аудита. После того как система станет системой обеспечения событий, все будущие решения по проектированию ограничены тем, что это система источников событий. Существует высокая стоимость миграции в систему источника событий или из нее. Этот шаблон лучше всего подходит для систем, где производительность и масштабируемость являются главными требованиями. Сложность, которая добавляется к системе, не оправдана для большинства систем.
Контекст и проблема
Большинство приложений работают с данными, и типичным подходом является хранение последнего состояния данных в реляционной базе данных, вставка или обновление данных по мере необходимости. Например, в традиционной модели создания, чтения, обновления и удаления (CRUD) типичный процесс обработки данных заключается в чтении данных из хранилища, внесении в него некоторых изменений и обновлении текущего состояния данных с новыми значениями, часто с помощью транзакций, которые блокируют данные.
Подход CRUD прост и быстро для большинства сценариев. Однако в системах с высокой нагрузкой этот подход имеет некоторые проблемы:
Производительность. По мере масштабирования системы производительность приведет к снижению производительности из-за конфликтов с ресурсами и блокировкой проблем.
Масштабируемость: системы CRUD синхронны и блок операций с данными при обновлении. Это может привести к узким местам и более высокой задержке при загрузке системы.
Возможность аудита: системы CRUD хранят только последнее состояние данных. Если нет механизма аудита, который записывает сведения о каждой операции в отдельном журнале, журнал теряется.
Решение
Шаблон источника событий определяет подход к обработке операций с данными на основе последовательности событий, каждое из которых записывается в инкрементируемое хранилище. Код приложения вызывает события, которые императивно описывают действия, принятые в объекте. События обычно отправляются в очередь, в которой отдельный процесс, обработчик событий, прослушивает очередь и сохраняет события в хранилище событий. Каждое событие представляет логическое изменение объекта, например AddedItemToOrder
или OrderCanceled
.
События сохраняются в хранилище событий, которое выступает в качестве системы записи (заслуживающий доверие источник данных) текущего состояния данных. Дополнительные обработчики событий могут прослушивать интересующие их события и принимать соответствующие меры. Потребители могут, например, запустить задачи, применяющие операции в событиях к другим системам, или выполнить любое другое связанное действие, необходимое для завершения операции. Обратите внимание, что код приложения, создающий события, никак не связан с системами, подписанными на эти события.
В любой момент приложения могут читать историю событий. Затем можно использовать события для материализации текущего состояния сущности путем воспроизведения и использования всех событий, связанных с этой сущностью. Этот процесс может возникать по запросу для материализации объекта домена при обработке запроса.
Так как это относительно дорого для чтения и воспроизведения событий, приложения обычно реализуют материализованные представления, проекции хранилища событий только для чтения, оптимизированные для запроса. Например, система может поддерживать материализованное представление всех заказов клиентов, которые используются для заполнения пользовательского интерфейса. Когда приложение добавляет новые заказы, добавляет или удаляет элементы в заказе или добавляет сведения о доставке, события создаются, а обработчик обновляет материализованное представление.
На рисунке показан обзор шаблона, включая некоторые типичные реализации с шаблоном, включая использование очереди, хранилище только для чтения, интеграцию событий с внешними приложениями и системами, а также события повторения для создания проекций текущего состояния конкретных сущностей.
Рабочий процесс
Ниже описан типичный рабочий процесс для этого шаблона:
- Слой презентации вызывает объект, ответственный за чтение из хранилища только для чтения. Возвращаемые данные используются для заполнения пользовательского интерфейса.
- Слой презентации вызывает обработчики команд для выполнения таких действий, как создание корзины или добавление элемента в корзину.
- Обработчик команд вызывает хранилище событий, чтобы получить исторические события для сущности. Например, он может получить все события корзины. Эти события будут воспроизводиться в объекте, чтобы материализовать текущее состояние сущности до любого действия.
- Бизнес-логика выполняется и создаются события. В большинстве реализаций события отправляются в очередь или раздел, чтобы разделить производителей событий и потребителей событий.
- Обработчики событий прослушивают интересующие события и выполняют соответствующее действие для этого обработчика. Ниже приведены некоторые типичные действия обработчика событий:
- Запись событий в хранилище событий
- Обновление хранилища только для чтения, оптимизированного для запросов
- Интеграция с внешними системами
Преимущества шаблонов
Шаблон источника событий предоставляет следующие преимущества.
События являются неизменяемыми, и их можно сохранить с помощью инкрементируемой операции. Вы можете не завершать работу источника, с помощью которого было активировано событие (пользовательский интерфейс, рабочий процесс или любой другой процесс), а задачи, обрабатывающие события, могут выполняться в фоновом режиме. Этот процесс, в сочетании с тем, что во время обработки транзакций не возникает никаких сомнений, может значительно повысить производительность и масштабируемость приложений, особенно для слоя презентации.
События — это простые объекты, описывающие некоторые действия, которые произошли вместе с любыми связанными данными, необходимыми для описания действия, представленного событием. События не имеют прямого отношения к обновлению хранилища данных. Они просто записываются для обработки в соответствующее время. Использование событий может упростить реализацию и управление.
События представляют определенную ценность для специалиста по работе с доменами, а объектно-реляционная несогласованность может усложнить понимание сложных таблиц базы данных. Таблицы представляют собой искусственные конструкции, отображающие текущее состояние системы, а не произошедшие события.
Источник событий может помочь предотвратить возникновение конфликтов, вызванных параллельными обновлениями, так как исключает необходимость непосредственного обновления объектов в хранилище данных. Однако модель домена по-прежнему должна быть разработана для собственной защиты от запросов, которые могут вызвать несогласованное состояние.
Хранилище событий только для добавления предоставляет путь аудита, который можно использовать для отслеживания действий, выполняемых в хранилище данных. Он может повторно создать текущее состояние в виде материализованных представлений или проекций, перепроверив события в любое время, и он может помочь в тестировании и отладке системы. Кроме того, требование использовать компенсирующие события для отмены изменений может предоставить журнал изменений, которые были отменены. Эта возможность не будет такой, если модель хранит текущее состояние. Список событий также можно использовать для анализа производительности приложения и обнаружения тенденций поведения пользователей. Кроме того, его можно использовать для получения других полезных бизнес-сведений.
Обработчики команд вызывают события и задачи выполняют операции в ответ на эти события. Это разделение задач и событий обеспечивает гибкость и расширяемость. Задачи "знают" о типе и данных события, но не об операции, вызвавшей это событие. Кроме того, каждое событие могут обрабатывать несколько задач. Это обеспечивает простую интеграцию с другими службами и системами, которые только прослушивают новые события, вызванные хранилищем событий. Однако события источника событий зачастую являются низкоуровневыми, из-за чего может потребоваться создание определенных событий интеграции.
Источник событий обычно объединяется с шаблоном CQRS путем выполнения задач управления данными в ответ на события и путем материализации представлений из сохраненных событий.
Проблемы и рекомендации
При принятии решения о реализации этого шаблона необходимо учитывать следующие моменты.
В конечном итоге согласованность — система будет в конечном итоге согласована только при создании материализованных представлений или создании проекций данных путем повторения событий. Существует некоторая задержка между добавлением событий в хранилище событий в результате обработки запроса, публикации событий и потребителей событий, обрабатываемых ими. В течение этого периода в хранилище событий могли попасть новые события, описывающие дальнейшие изменения сущностей. Клиенты должны быть в порядке с тем фактом, что данные в конечном итоге согласованы, и система должна быть разработана для учета конечной согласованности в этих сценариях.
Примечание.
Дополнительные сведения о реализации итоговой согласованности см. в руководстве по согласованности данных.
События управления версиями — хранилище событий является постоянным источником информации, поэтому данные о событиях никогда не должны обновляться. Единственным способом обновления сущности или отмены изменения является добавление компенсирующего события в хранилище событий. Если схема (а не данные) сохраненных событий должна измениться, возможно, во время миграции может быть трудно объединить существующие события в хранилище с новой версией. Приложению потребуется поддерживать изменения структур событий. Это можно сделать несколькими способами.
- Убедитесь, что обработчики событий поддерживают все версии событий. Это может быть сложной задачей для поддержания и тестирования. Для этого требуется реализация метки версии для каждой версии схемы событий, чтобы поддерживать как старые, так и новые форматы событий.
- Реализуйте обработчик событий для обработки определенных версий событий. Это может быть проблема обслуживания в этом исправлении ошибок может потребоваться выполнить в нескольких обработчиках. Для этого требуется реализация метки версии для каждой версии схемы событий, чтобы поддерживать как старые, так и новые форматы событий.
- Обновите исторические события до новой схемы при реализации новой схемы. Это нарушает неизменяемость событий.
Порядок событий — многопоточное приложение и несколько экземпляров приложений могут хранить события в хранилище событий. Согласованность событий в хранилище событий крайне важна, так как представляет собой порядок событий, влияющих на определенную сущность (порядок событий, который вызывает изменения в отношении сущности, влияет на ее текущее состояние). Добавление метки времени для каждого события может помочь избежать проблем. Другой распространенной практикой является добавление заметок для каждого события, вызванного запросом с добавочным идентификатором. Если два действия пытаются добавить события для одной сущности одновременно, хранилище событий может отклонить событие, которое соответствует идентификатору имеющейся сущности и идентификатору события.
Запросы событий — нет стандартного подхода или существующих механизмов, таких как sql-запросы, для чтения событий для получения информации. Единственные данные, которые можно извлечь, — это поток событий, использующий идентификатор события в качестве условия. Идентификатор события обычно сопоставляется с отдельными сущностями. Текущее состояние сущности можно определить только путем воспроизведения всех событий, связанных с ней в отношении исходного состояния этой сущности.
Стоимость повторного создания состояния для сущностей — длина каждого потока событий влияет на управление и обновление системы. Если потоки объемные, можно создать моментальные снимки через определенные интервалы, например указанное количество событий. Текущее состояние сущности можно получить из моментального снимка и за счет воспроизведения любого события, произошедшего после этой точки во времени. Дополнительные сведения о создании моментальных снимков данных см. в разделе "Репликация первичного подчиненного моментального снимка".
Конфликты . Несмотря на то, что источник событий сводит к минимуму вероятность конфликтов обновлений данных, приложение должно по-прежнему иметь возможность иметь возможность справиться с несоответствиями, которые приводят к конечному согласованности и нехватке транзакций. Например, событие, указывающее на сокращение запасов, может прибыть в хранилище данных во время размещения заказа на этот элемент. Эта ситуация приводит к требованию к согласованию двух операций путем консультирования клиента или создания обратного заказа.
Потребность в идемпотентности — публикация событий может быть по крайней мере один раз, и поэтому потребители событий должны быть идемпотентными. Они не должны повторно применять обновление, описанное в событии, если событие обрабатывается несколько раз. Несколько экземпляров потребителя могут поддерживать и агрегировать свойство сущности, например общее количество заказов, размещенных. Только один из них должен успешно увеличить агрегат при возникновении события размещения заказа. Хотя этот результат не является ключевым признаком источника событий, это обычное решение о реализации.
Циклическая логика . Учитывайте сценарии, в которых обработка одного события включает создание одного или нескольких новых событий, так как это может привести к бесконечному циклу.
Когда следует использовать этот шаблон
Используйте этот шаблон в следующих сценариях:
Когда в данные необходимо записать намерение, цель или причину. Например, изменения сущности клиента можно записать в виде ряда определенных типов событий, таких как "Перемещено домой", "Закрытая учетная запись" или "Умершая".
Когда крайне важно свести к минимуму или полностью избежать конфликта операций обновления данных.
Если требуется записать события, воспроизвести их для восстановления состояния системы, отката изменений или сохранения журнала и журнала аудита. Например, если задача включает несколько шагов, может потребоваться выполнить действия для восстановления обновлений, а затем воспроизвести некоторые шаги, чтобы вернуть данные в согласованное состояние.
При использовании событий. Это естественная функция работы приложения, и для этого требуется мало дополнительных усилий по разработке или реализации.
Если необходимо отделить процесс ввода или обновить данные от задач, необходимых для применения этих действий. Это изменение может быть для повышения производительности пользовательского интерфейса или распространения событий другим прослушивателям, которые принимают меры при возникновении событий. Например, можно интегрировать систему заработной платы с веб-сайтом отправки расходов. События, создаваемые хранилищем событий в ответ на обновления данных, сделанные на веб-сайте, будут использоваться как веб-сайтом, так и системой заработной платы.
Если вы хотите, чтобы гибкость могла изменять формат материализованных моделей и данных сущностей, если требования изменяются, или (при использовании с CQRS) необходимо адаптировать модель чтения или представления, предоставляющие данные.
При использовании с CQRS и конечной согласованности допускается во время обновления модели чтения, а также влияние на производительность сущностей и данных из потока событий.
Этот шаблон неприменим в следующих случаях:
Приложения, которые не требуют гипермасштабирования или производительности.
Для небольших или простых доменов, систем, которым не достает бизнес-логики или она и вовсе отсутствует, систем, не имеющих отношения к домену, которые обычно хорошо взаимодействуют со стандартными механизмами управления данных CRUD.
Систем, где для представления данных требуются согласованность и обновления в режиме реального времени.
Системы, в которых существует только небольшое количество конфликтующих обновлений базовых данных. Например, это системы, которые преимущественно добавляют данные, а не обновляют их.
Проектирование рабочей нагрузки
Архитектор должен оценить, как можно использовать шаблон источника событий в проектировании рабочей нагрузки для решения целей и принципов, описанных в основных принципах платформы Azure Well-Architected Framework. Например:
Принцип | Как этот шаблон поддерживает цели основных компонентов |
---|---|
Решения по проектированию надежности помогают рабочей нагрузке стать устойчивой к сбоям и обеспечить восстановление до полнофункционального состояния после сбоя. | Из-за записи истории изменений в сложном бизнес-процессе она может облегчить восстановление состояния, если необходимо восстановить хранилища состояний. - Секционирование данных RE:06 - Аварийное восстановление RE:09 |
Эффективность производительности помогает рабочей нагрузке эффективно соответствовать требованиям путем оптимизации масштабирования, данных, кода. | Этот шаблон, как правило, в сочетании с CQRS, соответствующим дизайном домена и стратегическим моментальным снимком, может повысить производительность рабочей нагрузки из-за атомарных операций только для добавления и предотвращения блокировки базы данных для операций записи и чтения. - Производительность данных PE:08 |
Как и любое решение по проектированию, рассмотрите любые компромиссы по целям других столпов, которые могут быть представлены с этим шаблоном.
Пример
Система управления конференциями должна отслеживать количество завершенных резервирований для конференции. Таким образом, он может проверить, есть ли места по-прежнему доступны, когда потенциальный участник пытается сделать резервирование. Имеется по крайней мере два способа хранения общего числа заявок на бронирование для конференции в системе:
Она может хранить сведения об общем количестве зарезервированных мест в качестве отдельной сущности в базе данных, содержащей данные о резервировании. После резервирования мест или отмены резервирования система может соответствующим образом регулировать это количество. Этот подход прост в теории, но может привести к проблемам с масштабируемостью, если большое количество участников попытается зарезервировать места за короткий период времени. Например, в последний день или перед непосредственным завершением периода резервирования.
Система может хранить сведения о резервировании и отменах по мере выполнения событий в хранилище событий. Затем путем воспроизведения этих событий она может рассчитать количество доступных мест. Из-за неизменяемости событий этот подход может оказаться более масштабируемым. Системе необходимо только считывать данные из хранилища событий или добавлять их в это хранилище. Сведения события о резервировании и отменах никогда не изменяются.
На схеме ниже показано, как с помощью источника событий можно реализовать подсистему резервирования мест системы управления конференцией.
Последовательность действий для резервирования двух мест выглядит следующим образом:
Пользовательский интерфейс выдает команду для резервирования мест для двух участников. Команда обрабатывается отдельным обработчиком команд. Часть логики, отделенная от пользовательского интерфейса и отвечающая за обработку запросов, записывается в виде команд.
Сущность, содержащая сведения обо всех резервированиях для конференции, создается путем запроса событий, описывающих резервирование и отмены. Эта сущность вызывается
SeatAvailability
и содержится в модели домена, которая предоставляет методы для запроса и изменения данных в сущности.Некоторые оптимизации, которые следует учитывать, используют моментальные снимки (чтобы не нужно запрашивать и воспроизводить полный список событий для получения текущего состояния сущности), а также поддерживать кэшированную копию сущности в памяти.
Для выполнения резервирования обработчик команд вызывает метод, предоставляемый моделью домена.
Сущность
SeatAvailability
вызывает событие, содержащее количество зарезервированных мест. При следующем применении сущности события все резервирования будут использоваться для вычисления количества оставшихся мест.Система добавляет новое событие в список событий в хранилище событий.
Если пользователь отменяет резервирование, в системе выполняется аналогичный процесс, за исключением того, что обработчик команд выдает команду, которая создает событие отмены места и добавляет это событие в хранилище событий.
Помимо обеспечения большей области масштабируемости, используя хранилище событий, также предоставляет полную историю или аудит резервирования и отмены для конференции. События в хранилище событий представляют собой точные записи. Нет необходимости сохранять статистические данные другим способом, так как система может легко воспроизводить события и восстанавливать состояние в любой момент времени.
Следующие шаги
Несоответствие несогласованности реляционного импедаляционного объекта
Data Consistency Primer (Руководство по обеспечению согласованности данных). При использовании источника событий с отдельным хранилищем чтения или материализованными представлениями данные чтения не будут немедленно согласованы. Вместо этого данные будут согласованы только в конечном итоге. В этой статье перечислены проблемы, связанные с обеспечением согласованности распределенных данных.
Data Partitioning Guidance (Руководство по секционированию данных). Данные часто секционируются при использовании источника событий для повышения масштабируемости, уменьшения количества разных результатов и оптимизации производительности. В этой статье описывается, как разделить данные на дискретные секции и проблемы, которые могут возникнуть.
Блог Мартина Фаулера:
Связанные ресурсы
При реализации этого шаблона следует принять во внимание следующие шаблоны и рекомендации.
Шаблон выделения ответственности команды и запроса (CQRS). В хранилище записи, предоставляющее постоянный источник информации для реализации CQRS, часто используется реализация шаблона источника событий. В этом шаблоне описывается, как отделить операции, считывающие данные в приложении, от операций обновления данных с помощью отдельных интерфейсов.
Materialized View Pattern (Шаблон материализованного представления). Хранилище данных, используемое в системе, основанной на источнике событий, обычно не подходит для эффективного запроса. Вместо этого наиболее распространенным подходом является создание предварительно заполненных представлений данных через регулярные интервалы или при изменении данных.
Шаблон компенсирующих транзакций. Существующие данные в хранилище источников событий не обновляются. Вместо этого добавляются новые записи, которые переходят состояние сущностей в новые значения. Чтобы отменить изменение, используются компенсирующие записи, так как изменить предыдущие изменения невозможно. Этот шаблон описывает, как отменить задачи, выполненные с помощью предыдущей операции.