Event Driven Architecture, или EDA — довольно популярный архитектурный подход, в буквальном переводе «архитектура на основе событий», где мы строим приложение вокруг событий, которые генерируются в системе. В самом распространённом случае, у нас есть много пользователей, которые генерируют много событий, и эти события маршрутизируются в сервисы‑потребители.

Реализовать такое приложение в облаке можно несколькими способами. Так что сегодня покажу типовые варианты архитектуры для одного и того же сценария. А чтобы сравнивать было приятнее, возьмём для сопоставления архитектур один и тот же кейс — автоматизацию ухода в отпуск.

Для тех, кому удобнее смотреть, — запись моего доклада на эту тему с Yandex Scale:

О задаче и подходах к ней

Когда нам нужно в отпуск, есть предсказуемые шаги, которые нужно пройти:

  1. Создаём заявление на отпуск.

  2. Проверяем условия: доступно ли нужное количество дней отпуска.

  3. Отправляем заявление на утверждение руководителю.

  4. Если все условия соблюдены, создаём запрос в бухгалтерию.

  5. На время отпуска перестраиваем процессы, завязанные на сотрудника: корректируем график дежурств, настраиваем автоответы, устанавливаем статус отсутствия на внутренних ресурсах и так далее.

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

Одни из самых видных паттернов для обработки событий в рамках 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-решения, рекомендуем также посмотреть: 

Комментарии (0)