Event Driven Architecture, или EDA — довольно популярный архитектурный подход, в буквальном переводе «архитектура на основе событий», где мы строим приложение вокруг событий, которые генерируются в системе. В самом распространённом случае, у нас есть много пользователей, которые генерируют много событий, и эти события маршрутизируются в сервисы‑потребители.
Реализовать такое приложение в облаке можно несколькими способами. Так что сегодня покажу типовые варианты архитектуры для одного и того же сценария. А чтобы сравнивать было приятнее, возьмём для сопоставления архитектур один и тот же кейс — автоматизацию ухода в отпуск.
Для тех, кому удобнее смотреть, — запись моего доклада на эту тему с Yandex Scale:
О задаче и подходах к ней
Когда нам нужно в отпуск, есть предсказуемые шаги, которые нужно пройти:
Создаём заявление на отпуск.
Проверяем условия: доступно ли нужное количество дней отпуска.
Отправляем заявление на утверждение руководителю.
Если все условия соблюдены, создаём запрос в бухгалтерию.
На время отпуска перестраиваем процессы, завязанные на сотрудника: корректируем график дежурств, настраиваем автоответы, устанавливаем статус отсутствия на внутренних ресурсах и так далее.
На каждом из шагов мы хотим создавать уведомления о результате, и в целом мониторить ход процесса.
Одни из самых видных паттернов для обработки событий в рамках EDA — это хореография и оркестрация. Для автоматизации нашего сценария разберёмся, что мы будем подразумевать под этим.
Хореография — есть обособленные сервисы или даже домены сервисов, которые сами знают, как им работать: в какой момент создать событие и послать его куда‑нибудь, а также как реагировать, когда нужно наоборот принять событие. Бизнес‑логика децентрализирована.
Оркестрация — есть централизованный оркестратор, который управляет всеми сервисами. Сервисы умеют отвечать на запросы, но то, как они взаимодействуют, решает оркестратор. Бизнес‑логика (на верхнем уровне) централизована.
Если хотите глубже познакомиться с концепцией, паттернами и практиками EDA, рекомендую также заглянуть в EDA Visuals.
Выбирать инструменты для EDA мы будем, исходя из этой оптики.
Начнём с оркестрации
Для автоматизации задачи в логике оркестрации в облаке есть несколько вариантов реализации.
Очевидное решение: написать код оркестрации самостоятельно. Создадим оркестратор сами и развернём на виртуальной машине, в кластере ВМ или k8s‑кластере.
Плюсы подхода:
Традиционный подход, не требующий специальных новых знаний: DevOps‑инженер, работающий с облачными технологиями, или облачный архитектор знают, как разворачивать приложения на виртуальных машинах или в k8s.
Максимальная гибкость и свобода при реализации за счет того, что вы сами пишете код оркестрации, а также возможность переиспользовать уже написанное ранее решение.
Но у этого варианта есть недостатки:
Развёртывание и поддержка отказоустойчивого, высокодоступного и масштабируемого решения даже в облаке потребует значительных усилий команды Operations или эксплуатации.
Самостоятельная разработка займёт больше времени и потребует более квалифицированных специалистов, потому что необходимо будет реализовать обработку ошибок, ретраи, сохранение состояний в БД и т. д. Объём кода будет большим и также потребует дальнейшего развития и поддержки.
Нужно реализовывать интеграции с другими сервисами облака.
Если нет нагрузки, за простой всё равно придётся платить, так как в этом варианте не предусмотрена модель PAYG.
Львиную долю этих трудностей можно избежать, если переложить часть работ по развёртыванию и эксплуатации на облачного провайдера в рамках managed‑решений.
Собрать решение из управляемых сервисов. Для оркестрации потоков операций по обработке данных мы можем выбрать Managed Service for Apache Airflow. В нём можно управлять автоматизированными процессами, или воркфлоу в виде направленного ациклического графа (Directed Acyclic Graph, DAG), который реализован с помощью скрипта на Python. Мы уже рассказывали, как с его помощью можно автоматизировать регулярные аналитические задачи.
Использовать serverless. В случае бессерверных интеграций мы можем вызывать по http функции или контейнеры. Напомню, что важно знать для построения такой архитектуры:
Вызвать функцию или контейнер можно через HTTP/HTTPS напрямую, через триггер, через сервис для создания API‑шлюзов.
Нет постоянно запущенного сервиса или процесса, выполняющего код функции или контейнера, вызов происходит в результате наступления событий, описанных выше.
Стейт не сохраняется: если трафика в контейнер нет, если события не наступают, то платформа «тушит» контейнер и его состояние исчезает из памяти. Для сохранения стейта мы делаем монтирование файловых систем в функцию или контейнер.
Главный плюс в том, что бессерверные инструменты работают в режиме PAYG, и это позволяет управлять ресурсами более гибко. Минус только в том, что для настройки бессерверных решений порой нужно время на изучение документации (об этом мы уже тоже рассказывали вот здесь).
Мы думали над тем, как сделать serverless‑технологии ещё доступнее и пришли к созданию такого инструмента для оркестрации, как Yandex Workflows. Покажу, как это работает, на примере нашего кейса.
Диаграмма шагов для автоматизации ухода в отпуск выглядела бы так:
Для описания процесса используется декларативная спецификация в формате yaml, но прямо сейчас мы работаем над визуальным конструктором. Чтобы автоматизировать бизнес‑процесс, надо выразить его в терминах Workflows, для этого есть «кубики» или степы. Они по умолчанию выполняются друг за другом (в спецификации следующий степ указывается в поле next
). Степы бывают двух видов:
Функциональные — выполняют непосредственно действия и являются интеграциями с другими сервисами. Пример: http/gRPC‑вызов, вызов serverless‑функции или контейнера, отправка сообщений через Postbox. Помимо этого за счёт интеграций можно положить сообщение в очередь YMQ или в топик YDS, а также положить объект в Object Storage.
Управляющие (control flow) — степы, контролирующие исполнение Workflow и очерёдность / параллельность исполнения функциональных степов. Пример: условный переход по степу, параллельное исполнение степов, принудительное завершение Workflow.
Посмотрим, как устроен рабочий процесс.
{
"inner":{
"value": [1, 2, 3]
},
"token": "V29ya2Z…"
}
Как выглядит вызов функции или контейнера с помощью Http Call:
url: "https://example.com"
method: POST
body: '{"data": \(.inner.value)}'
headers:
Authorization: Bearer \(.token)
И вывод:
{
"user": {
"id": "25071997",
"nickname": "werelaxe"
}
}
Как уже было сказано, каждый степ описывается с помощью декларативной спецификации, которая статична на протяжении всего исполнения Workflow (видим это в вызове).
Но понятно, что некоторые параметры для выполнения могут стать известны только в рантайме (например, url и body для http‑вызова). Для «доопределения» конечных значений используется шаблонизация с помощью jq‑выражений, которые используют шаблоны из спецификации и информацию из рантайма для их рендеринга.
Состояние рантайма мы называем стейт, или json state, видим его в примере кода.
Тот самый json state — это JSON map, который является глобальным для всех элементов. Это позволяет поддержать персистентность и взаимодействие между шагами процесса. Как это выглядит посмотрим опять на примере HTTP‑вызова:
У каждого функционального блока есть фильтры input и output. Input нужен для фильтрации всего json‑стейта (потенциально сильно большего, чем нужно), чтобы взять только нужное для конкретного http‑вызова. После исполнения степа получаем новый json, который также трансформируем с помощью jq‑выражения
Теперь посмотрим на какой‑нибудь управляющий степ. Например, на switch — это условный переход между степами (аналог if
из программирования), который в зависимости от информации из рантайма передаёт исполнение разным степам. Состоит он из списка правил, где каждое правило — это условие и имя степа, на которой нужно перейти, если условие равно true
, и указания default
. Для каждого правила по очереди проверяется условие, после первого найденного истинного условия, управление сразу передаётся на указанный в правиле степ. Если ни одно из правил не сработало, управление передаётся на дефолтный степ, а если он не указан — возникает ошибка.
Также посмотрим, как это выглядит в спецификации.
Подробно рассматривать все степы в статье не будем, их описание можно найти в документации.
Попробуем уйти в отпуск с помощью хореографии
Очевидное решение с использованием Managed Kafka. Поднимем Kafka и создадим там топик под каждый домен или сервис, который должен работать в системе. Для фильтрации, трансформации и обогащения придётся писать отдельный код на стороне отправки или приёма события.
Плюсы снова те же: если мы уже знакомы с Kafka, то поднимем её в облаке быстро, а дальше выбираем любой удобный нам инструментарий.
Но также есть недостатки:
Нужно писать код для фильтрации и обогащения событий.
Для создания отказоустойчивого и масштабируемого решения нужен уже кластер или инстанс‑группа.
Снова не будет возможности работать по модели PAYG, что означает переплату за простой.
Бессерверная интеграция. В этом случае мы можем использовать триггеры — кусочки сервиса, которые могут вызывать функции или контейнеры. В нашем облаке настроить их можно с помощью инструмента EventRouter.
Посмотрим на архитектуру детальнее:
Наша шина с одной стороны соединяется с источниками событий с помощью коннектора. А когда события уже внутри шины, для их маршрутизации используются правила, состоящие из условия и трансформера. Если нужные условия соблюдаются, то события преобразуются с помощью трансформера и отправляются в приёмники — к целевым сервисам. Если доставить событие не получилось, EventRouter будет ретраить отправку до истечения TTL события. Максимальное количество ретраев и TTL задаются в настройках приёмника. Событие, которое не удалось обработать, перемещается в DLQ.
Как это может выглядеть на примере кейса с отпуском:
В чём плюсы такого бессерверного решения:
Часть операций DevOps делается за пользователя внутри сервиса.
Используется модель PAYG.
Фильтрацию, роутинг и доставку событий можно конфигурировать.
Тем, кого заинтересовали эти serverless-решения, рекомендуем также посмотреть:
доклад «Путь от FaaS к платформе для EDA‑приложений» на Yandex Scale
описание сервиса Yandex Serverless Integrations
документацию по Yandex Workflows
документацию по Yandex EventRouter