Вместо сохранения текущего состояния данных в домене используйте инкрементируемое хранилище для записи полных серий действий с этими данными. Хранилище действует как система записи, и его можно использовать для материализации объектов домена. Это позволяет упростить задачи в сложных доменах, устраняя необходимость синхронизации модели данных и домена для бизнеса при одновременном повышении производительности, масштабируемости и скорости реагирования. Кроме того, обеспечивается совместимость транзакционных данных и сохранение всех журналов аудита и истории, с помощью которых можно использовать компенсирующие действия.
Контекст и проблема
Большинство приложений работают с данными, и типичный подход, используемый в приложении, — поддержка текущего состояния данных за счет обновления по мере работы с ними. Например, в традиционной модели создания, чтения, обновления и удаления (CRUD) типичный процесс обработки данных заключается в чтении данных из хранилища, внесении в него некоторых изменений и обновлении текущего состояния данных с новыми значениями, часто с помощью транзакций, которые блокируют данные.
Для такого подхода CRUD предусмотрены некоторые ограничения:
Системы CRUD выполняют операции обновления непосредственно в хранилище данных. Эти операции могут замедлить производительность и скорость реагирования и ограничить масштабируемость из-за необходимых затрат на обработку.
В домене для совместной работы с большим количеством параллельных подключений пользователей более вероятны возникновения конфликтов обновления данных, так как операции обновления выполняются для отдельного элемента данных.
Если нет другого механизма аудита, который записывает сведения о каждой операции в отдельном журнале, журнал теряется.
Решение
Шаблон источника событий определяет подход к обработке операций с данными на основе последовательности событий, каждое из которых записывается в инкрементируемое хранилище. Код приложения отправляет ряд событий, которые принудительно описывают каждое действие с данными в хранилище событий, где они сохраняются. Каждое событие обозначает некоторый набор изменений в данных (например, AddedItemToOrder
).
События сохраняются в хранилище событий, которое выступает в качестве системы записи (заслуживающий доверие источник данных) текущего состояния данных. Хранилище событий обычно публикует эти события для оповещения потребителей и при необходимости предоставления им возможности обрабатывать эти события. Потребители могут, например, запустить задачи, применяющие операции в событиях к другим системам, или выполнить любое другое связанное действие, необходимое для завершения операции. Обратите внимание, что код приложения, создающий события, никак не связан с системами, подписанными на эти события.
Стандартное использование событий, опубликованных хранилищем событий, включает поддержку материализованных представлений сущностей по мере их изменения в приложении с помощью действий, а также интеграцию с внешними системами. Например, система может поддерживать материализованное представление всех заказов клиента, используемых для заполнения частей пользовательского интерфейса. Приложение добавляет новые заказы, добавляет или удаляет элементы в заказе и добавляет сведения о доставке. События, описывающие эти изменения, можно обрабатывать и использовать для обновления материализованного представления.
В любой момент приложения могут читать историю событий. Затем его можно использовать для материализации текущего состояния сущности путем воспроизведения и использования всех событий, связанных с этой сущностью. Этот процесс может возникать по запросу для материализации объекта домена при обработке запроса. Кроме того, процесс выполняется с помощью запланированной задачи, чтобы состояние сущности можно было хранить в виде материализованного представления для поддержки слоя презентации.
На схеме ниже представлен обзор шаблона, включая некоторые варианты использования потока событий, например создание материализованного представления, интеграцию событий с внешними приложениями и системами, а также воспроизведение событий для создания проекций текущего состояния определенных сущностей.
Шаблон источника событий предоставляет следующие преимущества.
События являются неизменяемыми, и их можно сохранить с помощью инкрементируемой операции. Вы можете не завершать работу источника, с помощью которого было активировано событие (пользовательский интерфейс, рабочий процесс или любой другой процесс), а задачи, обрабатывающие события, могут выполняться в фоновом режиме. Этот процесс, в сочетании с тем, что во время обработки транзакций не возникает никаких сомнений, может значительно повысить производительность и масштабируемость приложений, особенно для уровня презентации или пользовательского интерфейса.
События — это простые объекты, описывающие некоторые действия, которые произошли вместе с любыми связанными данными, необходимыми для описания действия, представленного событием. События не имеют прямого отношения к обновлению хранилища данных. Они просто записываются для обработки в соответствующее время. Использование событий может упростить реализацию и управление.
События представляют определенную ценность для специалиста по работе с доменами, а объектно-реляционная несогласованность может усложнить понимание сложных таблиц базы данных. Таблицы представляют собой искусственные конструкции, отображающие текущее состояние системы, а не произошедшие события.
Источник событий может помочь предотвратить возникновение конфликтов, вызванных параллельными обновлениями, так как исключает необходимость непосредственного обновления объектов в хранилище данных. Однако модель домена по-прежнему должна быть разработана для собственной защиты от запросов, которые могут вызвать несогласованное состояние.
Хранилище событий только для добавления предоставляет путь аудита, который можно использовать для отслеживания действий, выполняемых в хранилище данных. Он может повторно создать текущее состояние в виде материализованных представлений или проекций, перепроверив события в любое время, и он может помочь в тестировании и отладке системы. Кроме того, требование использовать компенсирующие события для отмены изменений может предоставить журнал изменений, которые были отменены. Эта возможность не будет такой, если модель хранит текущее состояние. Список событий также можно использовать для анализа производительности приложения и обнаружения тенденций поведения пользователей. Кроме того, его можно использовать для получения других полезных бизнес-сведений.
Хранилище событий вызывает события, а задачи выполняют операции в ответ на эти события. Это разделение задач и событий обеспечивает гибкость и расширяемость. Задачи "знают" о типе и данных события, но не об операции, вызвавшей это событие. Кроме того, каждое событие могут обрабатывать несколько задач. Это обеспечивает простую интеграцию с другими службами и системами, которые только прослушивают новые события, вызванные хранилищем событий. Однако события источника событий зачастую являются низкоуровневыми, из-за чего может потребоваться создание определенных событий интеграции.
Источник событий часто сочетается с шаблоном 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 (Шаблон материализованного представления). Хранилище данных, используемое в системе, основанной на источнике событий, обычно не подходит для эффективного запроса. Вместо этого наиболее распространенным подходом является создание предварительно заполненных представлений данных через регулярные интервалы или при изменении данных.
Шаблон компенсирующих транзакций. Существующие данные в хранилище источников событий не обновляются. Вместо этого добавляются новые записи, которые переходят состояние сущностей в новые значения. Чтобы отменить изменение, используются компенсирующие записи, так как изменить предыдущие изменения невозможно. Этот шаблон описывает, как отменить задачи, выполненные с помощью предыдущей операции.