Event Driven Systems

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

И суть здесь в том, чтобы захватить эти события и затем обработать их, чтобы вызвать изменения в приложении в дополнение к хранению их в качестве журнала аудита.

The workflow

Рабочий процесс:

Image for post

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

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

Журнал событий можно разделить на журналы muliple и использовать для них отдельный процессор.

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

Event Data

Данные событий: События протекают в приложения, перенося некоторые данные.Поскольку каждое событие представляет собой что-то, что произошло в прошлом, оно имеет смысл быть неизменным. Это чрезвычайно важно, особенно при использовании журналов событий для отслеживания изменений в нашем приложении.

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

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

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

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

Benefits?

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

Мы можем легко узнать, что произошло в прошлом в любой момент времени.

Теперь, поскольку эти компоненты, эти службы работают вместе, это приводит нас к вопросу: как они могут взаимодействовать путем отправки и прослушивания событий?

Collaboration

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

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

Возвращаясь к нашему примеру.

Image for post

  1. В службу заказа отправляется запрос на размещение заказа.
  2. Заказ будет запрашивать службу поддержки клиентов, запрашивая информацию о клиенте, такую как кредитная карта, адрес и т.д.
  3. Затем он отправляет платежной службе команду для взимания платы с клиента.

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

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

Image for post

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

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

Эти события сохраняются в журнале событий.

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

Commands vs Events

Команды против событий: Команды императивны, в настоящем, могут быть отклонены, не записываются, срабатывают по внешнему запросу (пользователю).

События декларативны, произошли в прошлом, неизменны (не могут быть изменены), и могут быть поняты всеми заинтересованными сторонами (описательный текст).

Если компонент A заботится о том, чтобы компонент B что-то делал, то это команда. Если, однако, компонент A вызывает изменение и не заботится о том, кто заинтересован в этом изменении, это событие.

Queries vs Events

Запросы против событий:Запросы-это когда компонент явно запрашивает у другого фрагмент данных.

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

Таким образом, службе заказов не нужно запрашивать службу клиентов, поскольку она сохранила запись о последнем событии и, таким образом, знает данные клиента. Таким образом, мы сократили звонки в службу поддержки клиентов.

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

Каждый компонент должен структурировать данные, какие данные хранить и какие игнорировать.

Если необходим централизованный источник данных, мы должны (1) выдавать события для обновления данных. После этого (2) передайте обновления компонентам для выполнения действий.

Другие могут просто хранить журнал событий, прикрепленный к самому объекту домена, и в этом нет ничего плохого. Например, объект order будет иметь свойство “history”, массив всех изменений.

Image for post

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

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

Тем не менее, мы увеличили доступность, но согласованность является проблемой. Служба заказа может не иметь последней информации в службе обслуживания клиентов (устаревшие данные), когда служба обслуживания клиентов не работает. И поэтому он может предпринимать действия, основанные на устаревших данных. Но, опять же, с запросами, это просто не будет работать — что может быть предпочтительным в некоторых сценариях.

Другой недостаток заключается в том, что, хотя у нас есть разделение, становится трудно объяснить, что именно происходит, когда событие происходит. Кто слушает? Что происходит дальше? Мы должны внимательно смотреть на журналы, и это становится сложнее, когда приложение содержит много служб. С запросами легко определить и увидеть, что вызов от одного компонента переходит к другим компонентам.

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

Сотрудничество событий открывает доступ к источнику событий.

Event sourcing

Поиск событий: Источник событий — это не только последовательность событий, которые можно запросить и посмотреть, но и который позволяет нам …

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

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

Snapshots

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

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

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

External Systems

Внешняя система: Все усложняется при работе с внешними системами. Платежная служба, возможно, полагается на внешние системы для запроса обменного курса и списания средств с кредитной карты клиента.

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

Во время перестроения и повторов шлюз не должен пересылать запросы внешним системам (отключенный режим). Он должен различать фактический запрос и воспроизведение. И он должен сделать это, не имея приложения, чтобы беспокоиться об этом.

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

Code / Schema changes

Изменения кода / схемы: Изменения кода могут быть сгруппированы под новыми функциями, исправлениями ошибок, логикой изменения.

— New features

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

— Bug fixes

Устранение ошибок: Это просто вопрос исправления ошибок и переработки всех баггов. Однако, если используется внешняя система, решить это может быть сложно.

Например, если платежная служба использует внешнее обслуживание для списания средств с кредитной карты клиента. Теперь, скажем, они взимали с клиента больше, чем обычные сборы из-за ошибки в коде. Решение состоит в том, чтобы выпустить новое событие, чтобы вернуть дополнительные снятые деньги.

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

Некоторые из этих исправлений должны быть сделаны вручную.

— Code Logic

Логика Кода: Изменение логики или бизнес-правил требует обработки старых событий со старыми правилами и новых событий с новыми правилами. Один из способов состоит в том, чтобы иметь условное утверждение — Если до определенной даты сделать А еще сделать B.Это решение может стать очень грязным. Таким образом, можно инкапсулировать правила в объект и сопоставить их датам.

Скажем, при оформлении заказа мы взимаем с клиента дополнительную сумму в размере 1,5% налогов. Начиная с февраля 2018 года, мы взимаем с клиентов 1,8%. Итак, вот шаги:

Image for post

  1. Новый заказ был размещен клиентом, и поэтому событие было инициировано.
  2. Объект события вызывает Ruleоснованный на самом событии и дате. Правило здесь представляет различные налоговые проценты.
  3. RuleОбъект вычисляет сумму с процентом налога и вызывает службу заказа, передавая правильные аргументы. Этот расчет снова выполняется относительно даты события.

Объекты правила называются объектами стратегии . Идея заключается в возможности добавлять или обновлять правила без изменения класса Order.

Что происходит при изменении схемы данных?. Это либо не мутировать поля схемы (имя, возраст, пол и т.д.), и расширить его путем добавления новых необязательных полей. Таким образом, старые события не нарушат код.

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

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

Общим решением является использование stickyIds (неизменяемый). Это означает, что каждый раз, когда мы создаем клиента, мы назначаем идентификатор, и этот идентификатор будет связан с электронной почтой клиента (уникальным). Таким образом, при воспроизведении, каждый созданный клиент получит тот же идентификатор, указанный по электронной почте.

Concurrency

Совпадение: Эта проблема является довольно распространенной проблемой и встречается везде, но стоит упомянуть и здесь.

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

Хорошей консистенции можно достичь путем:

  1. Пессимистическая блокировка: предотвращает дальнейшие команды до сохранения текущей. Но, это медленно,и нам нужно обрабатывать тайм-ауты и т.д.
  2. Очередь синхронизации, которая обрабатывает одну команду за раз.
  3. Оптимистическая блокировка путем проверки последней версии обновляемых данных непосредственно перед сохранением. Другими словами, прочтите запись еще раз и проверьте, были ли внесены какие-либо изменения с момента последнего прочтения.

Benefits & Drawbacks?

Преимущества и недостатки:

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

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

  • Чтобы повысить доступность, мы также можем иметь две системы, работающие одновременно и синхронизирующиеся, если одна не удастся, вторая возьмет верх.

Хотя, …

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

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

Parallel Systems

Параллельная система: Мы говорили о возможности создания отдельной тестовой среды и ветвей в приведенных выше преимуществах.

Это дает нам возможность просматривать систему, используя различные пути. Возможно, мы хотим воспроизвести все события, но и удалить, добавить или изменить некоторые. Или, может быть, мы хотим исправить состояние системы, но не хотим связываться с текущим, поэтому мы виляем, исправляем и объединяем обратно.

Это та же идея, что и с ветвлением в Git.

Итак, как мы можем это реализовать?.

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

Проблема в том, что если мы хотим заменить существующую систему, нам все равно нужно интегрировать раздвоенную систему в существующую работающую.

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

CQRS

Поиск событий идет рука об руку с шаблоном разделения ответственности командных запросов (CQRS).

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

Как источник событий вписывается в мир CQRS?

Image for post

Ссылка

Компонент WRITE обрабатывает события и сохраняет эти события, которые используются для создания различных представлений: различных способов представления данных (отчеты, анализ и т.д.). Эти представления можно запросить позже через READ component.

Чтение является неизменяемым, денормализованным и основано на том, как часто пользователь запрашивает данные. Запись нормализована, проверена (перед сохранением), хороша для обновлений и не оптимизирована для запросов.

Очевидным недостатком является то, что эти различные представления могут быть несовместимы в течение некоторого времени, пока все они не будут обновлены с последними изменениями. Это называется конечная согласованность; данные становятся согласованными, станут согласованными в какой-то момент в будущем. Мы уже говорили об этом.

Одно из решений состоит в том, чтобы сравнить последнюю версию представления и журнал событий, если есть gab, обновить представление и вернуть результат.