рМБФЖПТНБ ДМС ПВХЮЕОЙС УПВЩФЙКОПНХ РТПЗТБННЙТПЧБОЙА
жБТБЖПОПЧ бМЕЛУБОДТ бТЛБДШЕЧЙЮ
1. йОФЕТРТЕФБФПТ УПВЩФЙКОЩИ РТПЗТБНН
пДОБ ЙЪ РТПВМЕН ПВХЮЕОЙС ЙОЖПТНБФЙЛЕ: ОБИПЦДЕОЙЕ УЙУФЕН, ЛПФПТЩЕ РТЕДУФБЧМСАФ ЙНЕООП ЙДЕА ЛБЛПЗП-ФП ЛМБУУБ НЕФПДПЧ, ЛБЛ НПЦОП НЕОЕЕ ЪБЗТСЪОЕООХА ЛПОЛТЕФОЩНЙ ТЕБМЙЪБГЙПООЩНЙ МЙВП ДТХЗЙНЙ ЮБУФОПУФСНЙ. ч ЮБУФОПУФЙ, ПУПВЕООП РМПИП ЪДЕУШ РПМПЦЕОЙЕ У УПВЩФЙКОЩН РТПЗТБННЙТПЧБОЙЕН. рПЬФПНХ ЧПЪОЙЛМБ ЪБДБЮБ ТЕБМЙЪБГЙЙ ЕЗП ДМС ГЕМЕК ПВХЮЕОЙС.
2. уПВЩФЙКОБС РТПЗТБННБ
нОПЦЕУФЧП ФБЛЙИ РТПЗТБНН ПВТБЪХЕФ РТПУФТБОУФЧП УПВЩФЙК. пВЩЮОБС РТПЗТБННБ ЖБЛФЙЮЕУЛЙ СЧМСЕФУС ДПУФБФПЮОП ЦЕУФЛПК РПУМЕДПЧБФЕМШОПУФША ДЕКУФЧЙК. ч ОПТНЕ ПОБ ПРЙУЩЧБЕФ ПДЙО ЙЪ ЧПЪНПЦОЩИ ДЕФЕТНЙОЙТПЧБООЩИ БМЗПТЙФНПЧ ТБВПФЩ.
пФМЙЮЙЕ РЕТЧПЕ УПВЩФЙКОПК РТПЗТБННЩ ЪБЛМАЮБЕФУС Ч ФПН, ЮФП РТПЗТБННБ НПЦЕФ ЙУРПМОСФШУС Ч РТПЙЪЧПМШОПН РПТСДЛЕ (ЕУФЕУФЧЕООП, ПЗТБОЙЮЕООПН ХУМПЧЙСНЙ ФТЙЗЗЕТПЧ).
оБРТЙНЕТ: Ч РТПЗТБННЕ ЕУФШ 2 ЖХОЛГЙЙ б() Й ч(). рТЕДРПМПЦЙН, ЮФП ПВЕ ПОЙ ДПМЦОЩ ЙУРПМОЙФШУС. ч ПВЩЮОПК РТПЗТБННЕ РПТСДПЛ ЙИ ЙУРПМОЕОЙС, ФБЛ ЙМЙ ЙОБЮЕ, ЦЕУФЛП ЖЙЛУЙТПЧБО, Ч УПВЩФЙКОПК ЦЕ РТПЗТБННЕ РТЙ ХУМПЧЙЙ ПДОПЧТЕНЕООПК ЙУФЙООПУФЙ ФТЙЗЗЕТПЧ ДБООЩЕ ЖХОЛГЙЙ НПЗХФ ЙУРПМОЙФШУС Ч РТПЙЪЧПМШОПН РПТСДЛЕ, ЙМЙ ДБЦЕ ОЕ ЙУРПМОЙФШУС ЧПЧУЕ, Ф.Е. ПОЙ НПЗХФ ВЩФШ БВУПМАФОП ОЕЪБЧЙУЙНЩ. ьФП Й ПФМЙЮБЕФ УПВЩФЙКОПЕ РТПЗТБННЙТПЧБОЙЕ ПФ УФТХЛФХТОПЗП: ОЕ ЧУЕЗДБ РПОСФОП, ЮФП ВХДЕФ ЧЩРПМОЕОП ДБМШЫЕ.
пФМЙЮЙЕ ЧФПТПЕ: Ч ПВЩЮОПК РТПЗТБННЕ ЖХОЛГЙЙ ЙУРПМОСАФУС ЦЕУФЛП РПУМЕДПЧБФЕМШОП, Ф.Е. ОЕЧПЪНПЦОБ УЙФХБГЙС, ЮФП УОБЮБМБ ЧЩРПМОЙМБУШ УФТПЮЛБ ЙЪ б(), РПФПН УФТПЮЛБ ЙЪ ч(), Й ФБЛ ДБМЕЕ Ч РТПЙЪЧПМШОПН РПТСДЛЕ. бРРБТБФ УПВЩФЙКОПЗП РТПЗТБННЙТПЧБОЙС РТЕДПУФБЧМСЕФ ФБЛХА ЧПЪНПЦОПУФШ, ЙОБЮЕ ЗПЧПТС, УПВЩФЙС НПЗХФ ЙУРПМОСФШУС РБТБММЕМШОП Й ОЕЪБЧЙУЙНП. ъБ УЮЕФ УЙУФЕНЩ РТЙПТЙФЕФПЧ УПВЩФЙЕ ЪБРХЭЕООПЕ РПЪЦЕ, НПЦЕФ РТЙПУФБОПЧЙФШ ТБВПФХ ДТХЗЙИ УПВЩФЙК, ЪБРХЭЕООЩИ ТБОЕЕ, ДП РПМОПЗП УЧПЕЗП ЪБЧЕТЫЕОЙС.
3. пРЙУБОЙЕ УЙУФЕНЩ ЙОФЕТРТЕФБФПТБ
чЩВТБООБС УИЕНБ ТЕБМЙЪБГЙЙ
уЙУФЕНБ ТЕБМЙЪХЕФУС ЛБЛ ЙОФЕТРТЕФБФПТ, РПУЛПМШЛХ ЬФП РПЪЧПМСЕФ ВПМЕЕ ЗЙВЛП ПФУМЕЦЙЧБФШ РТПГЕУУ ЙУРПМОЕОЙС, Й НЕОШЫЕ ПЗТБОЙЮЙЧБФШ ЧПЪНПЦОЩЕ ПЫЙВЛЙ, ЮФП СЧМСЕФУС ЧБЦОЩН НПНЕОФПН ДМС ПВХЮБАЭЕК УЙУФЕНЩ. фБЛЦЕ ЪОБЮЙФЕМШОП ХРТПЭЕОБ РТПГЕДХТБ ЙЪНЕОЕОЙС УПВЩФЙКОПК РТПЗТБННЩ, РПУЛПМШЛХ ПОБ СЧМСЕФУС ОБВПТПН РТПУФЩИ ФЕЛУФПЧЩИ ДПЛХНЕОФПЧ.
пВТБВПФЛБ ПЫЙВПЛ
рПУЛПМШЛХ ПУОПЧОЩН ОБЪОБЮЕОЙЕН ДБООПК УЙУФЕНЩ ВХДЕФ ПВХЮЕОЙЕ, ФП ТЕЫЕОП ОЕ ВМПЛЙТПЧБФШ ПРБУОЩЕ ДЕКУФЧЙС РПМШЪПЧБФЕМС Й РТПЗТБННЩ, ДБЦЕ ЕУМЙ ПЫЙВЛБ ВЩМБ СЧОП ПВОБТХЦЕОБ, ОП ОЕ СЧМСЕФУС ЖБФБМШОПК. ч РТПГЕУУЕ ЙУРПМОЕОЙС УПВЩФЙКОПК РТПЗТБННЩ ЧЕДЕФУС МПЗ ЧУЕИ ЧЩРПМОСЕНЩИ ДЕКУФЧЙК ПФ УПЪДБОЙС ПВЯЕЛФПЧ (РТЙЧПДЙФУС ПРЙУБОЙЕ УП ЧУЕНЙ РБТБНЕФТБНЙ) Й РПДЛМАЮЕОЙС УПВЩФЙК (ФБЛЦЕ У РТЙЧЕДЕОЙЕН ЧУЕИ РБТБНЕФТПЧ), ДП ЧЩРПМОЕОЙС ЛБЦДПЗП ДЕКУФЧЙС РП ЙУРПМОЕОЙА РТПЗТБННЩ (РТЙЧПДЙФУС УФТПЛБ ЛПНБОДЩ Й ПРЙУБОЙЕ РТПЙЪПЫЕДЫЙИ ЙЪНЕОЕОЙК). фБЛЦЕ ЧПЪНПЦОБ ЪБРЙУШ Ч МПЗ РТЕДХРТЕЦДЕОЙК П РПФЕОГЙБМШОП ПРБУОЩИ ПРЕТБГЙСИ.
лТБФЛПЕ ПРЙУБОЙЕ УЙУФЕНЩ
уЙУФЕНБ РТЕДПУФБЧМСЕФ ЧПЪНПЦОПУФШ ПВТБВПФЛЙ ФБЛЙИ УПВЩФЙКОЩИ РТПЗТБНН, ОБРЙУБООЩИ ОБ УПЪДБООПН ОБНЙ СЪЩЛЕ. ч РТПГЕУУЕ ЙУРПМОЕОЙС РТПЗТБННЩ ЧЕДЕФУС РПМОЩК МПЗ ЧУЕИ ЙЪНЕОЕОЙК РТПЙУИПДСЭЙИ Ч УЙУФЕНЕ.
пВЯЕЛФОБС УЙУФЕНБ ОБ ФЕЛХЭЙК НПНЕОФ ПРЙУЩЧБЕФУС ЧНЕУФЕ УП ЧУЕК УЙУФЕНПК, ОП РТЙ ЬФПН ПОБ Ч ЪОБЮЙФЕМШОПК УФЕРЕОЙ ОЕЪБЧЙУЙНБ ПФ УБНПК РМБФЖПТНЩ, Б ЧЪБЙНПДЕКУФЧЙЕ У ОЕК ПТЗБОЙЪПЧБОП ЮЕТЕЪ УРЕГЙБМШОХА ЙОФЕТЖЕКУОХА ЖХОЛГЙА, ЮФП ДБЕФ ЧЩОПУБ ЕЕ Ч ПФДЕМШОПЕ ОЕЪБЧЙУЙНПЕ РТЙМПЦЕОЙЕ, ДЙОБНЙЮЕУЛЙ РПДЛМАЮБЕНПЕ Л РМБФЖПТНЕ. рТПЗТБННБ ЛПОЖЕТЕОГЙЙ
Каркас для Event-Driven программирования
Немного лирики

Чем дольше я программирую, тем больше мне нравятся слабосвязанные системы, которые состоят из большого числа разрозненных единиц (модулей), ничего не знающих друг о друге, но предполагающих существование других. Такие системы в идеале должны собираться подобно конструктору, без зависимостей и без адаптирования друг к другу. В идеале, в процессе работы такой системы, все необходимые задачи должны выполняться без остановки системы, простым введением нового модуля в мир (скажем, вбросом jar’ника в classpath), и система немедленно начнет взаимодействовать с новым модулем.
В связи с этим, очень привлекательно выглядит парадигма event-driven (или Событийно-ориентированное) программирование.
Его бесспорным плюсом является то, что не нужно править существующий код при добавлении или удалении какого-то модуля, система безостановочно продолжит работать, мы просто обретем или потеряем часть функциональности.
Надо оговориться, минусом является его же плюс — непредсказуемость поведения при изменениях, а также ослабление контроля над системой в целом (кто виноват, почему работает не так?) — в отличие от традиционного программирования, где система реагирует на подобные аномалии немедленно и жестко, с указанием причин (нужный модуль не найден).
Зачем менять традиции
В какой-то мере событийно-ориентированное программирование проявляется везде — от современных многозадачных ОС, до всяческих фреймворков. Вместо того, чтобы каждый участник (модуль) слушал все события, он подписывается только на те, какие ему интересны, тем самым потребляя меньше машинных ресурсов. Вообще-то, даже вызов метода на объекте можно воспринимать как синхронную посылку-прием сообщений с гарантией доставки и получения. Так зачем лишние сложности?
Ответ в том, чтобы все работало при любом исходе. Нас не интересует, кто получил наше сообщение, сколько получателей, и ответит ли он вообще. Мы просто мягко уведомляем. Никому не интересно — и ладно. Интересно — вот и отлично, работаем дальше.
Реалии
Например, та же событийная система в Java Swing. Чтобы слушать какие-то события, для каждого компонента есть методы addXXXListener, removeXXXListener. Для посылки таких сообщений есть метод fireXXXEvent. Все прекрасно. Но, скажем, мы в той же философии пишем свой компонент. И хотим посылать разнообразные события или реагировать на них, с сохранением инкапсуляции. Следовательно, приходится каждый раз реализовывать эти методы для каждого ХХХ события, для каждого компонента…
Решение
Код всегда до отвращения похожий, потому хотелось бы его заменить на несколько строчек. Подумал я, и в итоге не одного дня реализовал хелпер для таких задач. Это будет класс со статическими методами, который можно вызывать из любой точки программы. Итак, что нам нужно?
Во-первых, мы хотим реагировать на любые события. Пусть, для определенности, наши события будут реализовывать стандартный интерфейс java.util.EventObject, а слушатели будут реализовывать интерфейс java.util.EventListener. Так, с одной стороны, мы ничем не ограничены, а с другой — максимально просто связать это с событийной парадигмой AWT\Swing. Тогда, пожалуй, подписка на событий должна выглядеть так:
Так мы выразили свое желание быть в курсе событий какого-то подкласса EventObject, пообещав, что реализуем интерфейс EventListener (он не определяет методов, об этом позже). Но нас точно уведомят, если конкретное событие произойдет.
Далее, для чистого выхода нам нужна способность отписаться от события, подойдет
А также для совсем чистого выхода (хотя здесь спорный момент):
Хорош при выходе. Вроде бы и все. А, нет, не все — еще нужно однострочно же во многих местах уведомлять о каких-то тех или иных событиях, выглядит это так:
Теория
Событийная система представляет собой набор конечных автоматов, вызываемых диспетчером для обработки очереди событий. Звучит несколько заумно, но на деле все довольно просто.
Программная реализация
Кольцевой буфер и как он используется
Не буду приводить код кольцевого буфера, он уже подробно разбирался.
Для работы с кольцевым буфером у нас есть две основные функции:
void ES_PlaceEvent( unsigned char event) – положить событие в буфер
unsigned char ES_GetnEvent( void ) – взять событие из буфера
Функция ES_PlaceEvent вызывается источником события. Чаще всего это происходит в прерываниях. Например так:
//коды событий
.
#define EVENT_TIMER0 0x01
…
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect( void )
<
ES_PlaceEvent(EVENT_TIMER0);
>
Функция ES_GetEvent вызывается из main`а в бесконечном цикле while. Она возвращает код события и если он не равен нулю, то запускается диспетчер.
Диспетчер
void ES_Dispatch( unsigned char event)
<
void (*pFunc)( void ); //переменная – указатель на функцию
pFunc = FuncAr[event-1]; //присваиваем ей значение из массива
pFunc(); //вызываем функцию
>
Массив указателей на функции должен быть объявлен до диспетчера.
//пустая функция, ничего не делает
void EmptyFunc( void )
<
>
//массив указателей на функции-обработчики
__flash void (*FuncAr[])( void ) =
<
EmptyFunc
//потом добавим сюда указатели на обработчики
>;
Пока что массив содержит указатель на пустую функцию EmptyFunc() – это просто для наглядности.
Вот собственно и все содержимое заготовки.
Функция main()
Функция main() будет выглядеть так:
//подключение заголовочных файлов
unsigned char event = 0;
int main( void )
< //инициализация периферии
//разрешение прерываний
while (1) <
event = ES_GetEvent();
if (event)
ES_Dispatch(event)
>
return 0;
>
Что такое событийная архитектура
Событийная архитектура использует события для запуска и обмена данными между разделенными службами и является обычным явлением в современных приложениях, созданных с использованием микросервисов.
1. Что такое событийная архитектура
Шаблон управляемой событиями архитектуры (событийная архитектура, event-driven architecture, EDA) — это популярный шаблон распределенной асинхронной архитектуры, используемый для создания масштабируемых приложений. EDA состоит из разделенных одноцелевых компонентов, которые асинхронно получают и обрабатывают события.
2. Пример
Приведем пример событийной архитектуры — сайт электронной коммерции. Такая архитектура позволяет сайту реагировать на изменения в различных источниках во время пикового спроса без сбоев приложения или избыточного выделения ресурсов.

Пример событийной архитектуры для сайта электронной коммерции. (1) — Продюсеры событий; (2) — Начальные события; (3) — Маршрутизаторы событий; (4) — События обработки; (5) — Потребители событий.
3. Компоненты
Управляемые событиями архитектуры включают пять ключевых компонентов: продюсеры (производитель) событий, начальные события и события обработки, маршрутизаторы событий и потребители событий. Продюсер публикует начальное событие в маршрутизаторе, который фильтрует и передает событие обработки потребителям. Сервисы продюсера и потребителя разделены, что позволяет масштабировать, обновлять и развертывать их независимо.
3.1 Продюсеры мероприятий
В этом примере производители событий представлены eCommerce сайтом, мобильным приложением и торговым терминалом. В принципе, все, что регистрирует факт и представляет факт как сообщение о событии, может быть продюсером.
3.2 События
Первоначальное событие — это исходное событие, сгенерированное продюсером и полученное маршрутизатором, тогда как события обработки — это события, которые генерируются маршрутизатором событий и принимаются компонентами потребителя событий.
События могут содержать либо состояние (приобретенный товар, его цена и адрес доставки), либо события могут быть идентификаторами (уведомление о том, что заказ был отправлен).
Событие обычно состоит из двух частей: 1) заголовок события включает такую информацию, как имя события, отметка времени и тип события; 2) тело события предоставляет подробную информацию об обнаруженном изменении состояния.
3.3 Каналы
И начальные, и события обработки доставляются по каналам событий.
Первоначальные каналы событий могут быть TCP/IP-соединением или файлом (XML, JSON, электронная почта и т.д). Одновременно можно открыть несколько исходных каналов событий. Они читаются асинхронно, что позволяет обрабатывать события почти в реальном времени. События хранятся в очереди, ожидая последующей обработки маршрутизатором событий.
Каналы обработки событий обычно представлены очередями сообщений и брокерами сообщений. Брокеры сообщений наиболее широко используются, так что события могут обрабатываться несколькими потребителями событий (каждый из которых выполняет свою задачу в зависимости от полученного события обработки).
3.4 Маршрутизатор событий
Маршрутизатор событий отвечает за идентификацию начального события, а затем за выбор и выполнение шагов, содержащихся в событии. Для каждого шага в начальном событии маршрутизатор событий асинхронно отправляет событие обработки в канал событий, которое затем принимается и обрабатывается потребителем событий.
Маршрутизатор событий также может запускать ряд утверждений. Например, если событие, которое поступает в механизм обработки событий, является идентификатором продукта, запас которого на складе ограничен, это может вызвать такие реакции, как «Заказать продукт» и «Уведомить персонал».
Важно отметить, что маршрутизатор событий не выполняет бизнес-логику, необходимую для обработки исходного события — он распределяет соответствующие инструкции (= события обработки) среди потребителей событий.
3.5 Потребители событий
В этом примере потребители событий представлены управленческой базой данных, финансовой системой и отделом по работе с клиентами.
Эти компоненты содержат бизнес-логику приложения, необходимую для обработки события обработки. Потребители событий — это автономные, независимые, сильно разделенные компоненты архитектуры, которые выполняют определенную задачу в приложении или системе. Хотя степень детализации потребителей событий может варьироваться от точечной (например, расчет налога с продаж по заказу) до крупной (например, обработка страхового возмещения), важно помнить, что в целом каждый потребитель события должен выполнять одну бизнес-задачу и не полагаться на других потребителей в выполнении своей конкретной задачи.
4. Анализ шаблона
4.1 Масштабируемость: высокая
Каждый потребитель событий может масштабироваться отдельно, что обеспечивает точную масштабируемость.
4.2 Сложность разработки: высокая
Сложно из-за асинхронного характера шаблона, а также из-за создания контракта и необходимости более сложных условий обработки ошибок в коде для не отвечающих обработчиков событий и отказавших брокеров.
4.3 Производительность: высокая
Высокая производительность за счет асинхронных возможностей: возможность выполнять разделенные, параллельные асинхронные операции перевешивают затраты на постановку в очередь и удаление сообщений из очереди.
4.4 Тестируемость: низкая
Хотя индивидуальное юнит-тестирование не слишком сложно, для генерации событий требуется какой-то специализированный клиент или инструмент тестирования. Тестирование также осложняется асинхронным характером этого шаблона.
4.5 Модифицируемость: высокая
Поскольку компоненты-потребители событий являются одноцелевыми и полностью отделены от других компонентов-потребителей событий, изменения, как правило, ограничиваются одним или несколькими потребителями событий и могут быть выполнены быстро, не затрагивая другие компоненты.
5. Событийная архитектура: примеры использования
5.1 Репликация данных между аккаунтами и регионами
Системы координации между командами, работающими и развертывающимися в разных регионах и учетных записях. Используя маршрутизатор событий для передачи данных между системами, вы можете разрабатывать, масштабировать и развертывать службы независимо от других команд.
5.2 Разветвление и параллельная обработка
Если у вас много систем, которые должны работать в ответ на событие, вы можете использовать архитектуру, управляемую событиями, для разветвления события без необходимости писать собственный для отправки каждому потребителю. Маршрутизатор отправит событие в системы, каждая из которых может обрабатывать событие параллельно с разными целями.
5.3 Мониторинг состояния ресурсов и оповещение
Вместо того, чтобы постоянно проверять свои ресурсы, вы можете использовать управляемую событиями архитектуру для отслеживания и получения предупреждений о любых аномалиях, изменениях и обновлениях. Эти ресурсы могут включать сегменты хранилища, таблицы базы данных, бессерверные функции, вычислительные узлы и многое другое.
5.4 Интеграция гетерогенных систем
Если у вас есть системы, работающие в разных стеках, вы можете использовать управляемую событиями архитектуру для обмена информацией между ними без прямой связи. Маршрутизатор событий устанавливает косвенное обращение и взаимодействие между системами, поэтому они могут обмениваться сообщениями и данными, оставаясь при этом независимыми.
Как событийно-ориентированная архитектура решает проблемы современных веб-приложений
Пока у нас продолжается распродажа на самые взыскательные вкусы, мы обратим ваше внимание на еще одну тему нашего творческого поиска: событийно-ориентированную архитектуру (EDA). Под катом вас ожидают красивые блок-схемы и рассказ о том, как данная инновационная парадигма помогает при разработке веб-приложений.
В этой статье будут рассмотрены некоторые проблемы, подстегивающие развитие инноваций в современной веб-разработке. Далее мы погрузимся в тему событийно-ориентированной архитектуры (EDA), призванной решить эти проблемы, по-новому трактуя архитектуру серверной части.
Веб-приложения прошли долгий путь с тех времен, когда контент, оформленный в виде статических HTML-страниц, подавался с сервера. Сегодня веб-приложения стали гораздо сложнее, в их работе используются разнообразные фреймворки, датацентры и технологии. В последние пару лет можно отметить две тенденции, определяющие развитие IT-рынка:
Актуальные проблемы современного веба
Любая веб-технология должна справляться с теми вызовами, которым должны отвечать современные многопользовательские асинхронные приложения, рассчитанные на бесперебойную работу:
Доступность
Теперь мы работаем не с одним приложением, а с многими – десятками или даже сотнями – связанными сервисами, и каждый из них должен решать свои задачи круглосуточно, семь дней в неделю. Как этого добиться? Чаще всего сервис горизонтально масштабируют на множество инстансов, которые могут быть распределены в нескольких датацентрах – так обеспечивается высокая доступность. Все запросы, поступающие на данный конкретный сервис, маршрутизируются и равномерно распределяются по всем инстансам. В некоторых инструментах развертывания предоставляются возможности самовосстановления, поэтому при отказе одного инстанса создается другой, заступающий на его место.
Масштабируемость
Масштабируемость во многом сродни доступности. Суть доступности – обеспечить, что как минимум один экземпляр сервиса активен и работает, готов обслуживать входящие запросы. Масштабируемость, в свою очередь, связана, прежде всего, с производительностью. Если какое-либо приложение перегружено, то создаются новые экземпляры этого приложения, чтобы подстроиться к возросшему количеству запросов. Но вертикальное масштабирование приложений – нетривиальная задача, в особенности если речь идет о приложениях с сохранением состояния.
Единый источник истины
До появления микросервисов такая задача решалась довольно просто. Все данные располагались в одном местоположении, как правило, это была та или иная реляционная база данных. Но, когда множество сервисов совместно используют базу данных, могут создаваться такие проблемы, как возникающие между разными командами зависимости, касающиеся изменений схемы или проблем с производительностью. Обычно эта проблема решалась выделением своей базы данных на каждый сервис. Распределенный источник истины очень хорошо помогает соблюдать чистую архитектуру, но в такой ситуации приходится иметь дело с распределенными транзакциями и сложностью, сопряженной с поддержкой множественных баз данных.
Синхронность
При реализации типичного сценария вида «запрос-отклик» клиент дожидается, пока ответит сервер; он блокирует все действия, пока не получит ответ, либо пока не истечет заданная задержка. Если взять такое поведение и внедрить его в микросервисную архитектуру при помощи цепочек вызовов, пронизывающих всю систему, то можно легко оказаться в так называемом «микросервисном аду». Все начинается с вызова всего одного сервиса, назовем его «сервис А». Но затем сервис A должен вызвать сервис B, и начинается самое интересное. Проблема с данным поведением такова: если сам сервис связан с заблокированными ресурсами (например, висит поток), то задержки растут экспоненциально. Если у нас разрешена задержка в 500 мс на сервис, а в цепочке пять вызовов сервисов, то первому сервису понадобится задержка в 2500 мс (2,5 секунды), а последнему – 500 мс.
Вызовы современного веба
Знакомство с событийно-ориентированной архитектурой
Событийно-ориентированная архитектура (EDA) – это парадигма программной архитектуры, способствующая порождению, обнаружению, потреблению событий и реакции на них.
В классических трехуровневых приложениях ядром системы является база данных. В EDA фокус смещается на события и на то, как они просачиваются через систему. Такая смена акцентов позволяет полностью изменить подход к проектированию приложений и решению вышеупомянутых проблем.
Прежде, чем рассмотреть, как именно это делается в EDA, рассмотрим, что же такое «событие». Событие – это действие, инициирующее либо некоторое уведомление, либо изменение в состоянии приложения. Свет включился (уведомление), термостат отключил обогревательную систему (уведомление), у пользователя изменился адрес (изменение состояния), у кого-то из ваших друзей изменился номер телефона (изменение состояния). Все это — события, но еще не факт, что мы должны добавлять их в событийно-ориентированное решение. Предполагается, что в архитектуру добавляются лишь события, важные с точки зрения бизнеса. Событие «пользователь оформляет заказ» важно с точки зрения бизнеса, а «пользователь съедает заказанную пиццу или обед» — нет.
Если подумать над некоторыми событиями, то по поводу некоторых сразу понятно, что они важны для бизнеса, а по поводу некоторых – нет. Особенно по поводу тех, что происходят в ответ на другие события. Для выявления событий, идущих через систему, применяется техника под названием «событийный штурм». Созываются участники разработки приложения (от программистов до разработчиков бизнес-логики и экспертов в предметной области) и общими силами картируют все бизнес-процессы, представляя их в виде конкретных событий. Когда такая карта будет готова, результат работы формулируется в виде требований, которые должны выполняться при разработке приложений.
Пример приложения для бронирования, описанного методом событийного штурма
Выявив интересующие нас события и определившись, как их идентифицировать, давайте рассмотрим, как при помощи данной парадигмы можно решить типичные проблемы, упомянутые выше.
Поток событий является однонаправленным: от производителя к потребителю. Сравните данную ситуацию с вызовом REST. Производитель событий в принципе не ожидает отклика от потребителя, тогда как в случае REST-вызова отклик будет всегда. Нет отклика – значит не необходимости блокировать выполнение кода, пока не произойдет что-то еще. В таком случае события становятся асинхронными по природе своей, что полностью исключает риск увязнуть в задержках.
События происходят в результате действия, поэтому целевой системы здесь нет; нельзя сказать, что сервис A инициирует события в сервисе B; но можно сказать, что сервис B интересуют события, порождаемые сервисом A. Правда, в этой системе могут быть и другие «заинтересованные стороны», например, сервисы C или D.
Как же нам убедиться, что событие, инициированное в некоторой системе, достигнет всех «заинтересованных» сервисов? Как правило, подобные системы решаются при помощи брокеров сообщений. Брокер – это просто приложение, действующее в качестве посредника между генератором события (приложением, создавшим это событие) и потребителем события. Таким образом, приложения удается аккуратно открепить друг от друга, позаботившись о проблеме доступности, речь о которой шла выше в этом посте. Если именно в данный момент приложение недоступно, то, вернувшись в онлайн, оно начнет потреблять события и обрабатывать их, наверстав все те события, которые успели произойти за период, пока оно оставалось недоступным.
Что насчет хранилища данных? Можно ли хранить события в базе данных, либо вместо базы данных требуется что-то иное? Определенно, события можно хранить в базах данных, но в таком случае утрачивается их «событийная» сущность. Как только событие произошло, скорректировать его мы уже не можем, поэтому события по сути своей неизменяемы. Базы данных, в свою очередь… изменяемы, после занесения данных в базу их вполне можно изменить.
Лучше хранить события в логах событий. Логи событий – не что иное, как централизованное хранилище данных, где каждое событие записано в виде последовательности неизменяемых записей, так называемого «лога». Лог можно сравнить с журналом, где каждое новое событие добавляется в конец списка. Всегда можно воссоздать наиболее актуальное состояние, воспроизведя все события лога от начала до настоящего момента.
Итак, мы затронули все вопросы, кроме масштабируемости. Сервисы, создаваемые в событийно-ориентированном ключе, всегда рассчитаны на развертывание во множестве инстансов. Поскольку состояние как таковое хранится в логе событий, сам сервис будет без сохранения состояния, что обеспечивает хирургически точное масштабирование любого интересующего нас сервиса.
Единственным исключением из этого принципа являются сервисы, предназначенные для создания материализованных представлений. В сущности, материализованное представление – это состояние, описывающее лог событий в определенный момент времени. Такой подход используется, чтобы было проще запрашивать данные. Возвращаясь к проблеме масштабирования, скажем, что материализованное представление – это просто совокупное представление событий, напоминающее по форме таблицу; но где мы храним эти таблицы? Чаще всего приходится видеть такие агрегации в памяти, и при этом наш сервис автоматически превращается в сохраняющий состояние. Быстрое и легкое решение – снабдить локальной базой данных каждый сервис, создающий материализованные представления. Таким образом, состояние хранится в базе данных, и сервис работает без сохранения состояния.
Хотя, событийно-ориентированная архитектура существует уже более 15 лет, она лишь недавно снискала серьезную популярность, и это неслучайно. Большинство компаний проходят этап «цифровой трансформации», сопровождаемый дикими требованиями. Из-за сложности этих требований инженерам приходится осваивать новые подходы к проектированию ПО, предполагающие, в частности, ослабление связанности сервисов друг с другом и снижение издержек на обслуживание сервисов. EDA — одно из возможных решений этих проблем, но не единственное. Также не рассчитывайте, что все проблемы решатся, стоит только перейти на EDA. Для реализации некоторых фич по-прежнему могут потребоваться надежные дедовские REST API или хранение информации в базе данных. Выберите наиболее подходящий для вас вариант и спроектируйте его как следует!



