Возможно, ваша компания захочет перейти на архитектуру микросервисов и автоматизировать рабочие процессы (в этом посте блога я не вдаюсь в мотивацию, но вы, возможно, захотите прочитать о 5 Workflow Automation Use Cases You Might Not Have Considered или BizDevOps — the true value proposition of workflow engines). Это ставит вас в ряд со многими нашими клиентами. Как правило, у вас возникнут вопросы:
- Область применения и границы — какой рабочий процесс вы хотите автоматизировать и как он ложится на несколько микроуслуг, или разраниченный контекст в вашем ландшафте. Я ограничен объемом этого поста, поэтому я не затрону эту тему сегодня, но вы, возможно, захотите прочитать Avoiding the «BPM monolith» when using bounded contexts или Real-Life BPMN.
- Стек и инструменты — какой движок процессов я могу использовать?
- Архитектура — я запускаю движок процесса централизованно или децентрализованно?
- Управление — кто владельцы модели рабочего процесса и как ее развертывать?
- Операции — как мне сохранить контроль?
В этой записи блога я даю некоторые рекомендации по основным архитектурным решениям, которые вам необходимо принять. Я дам упрощенные советы, чтобы помочь вам начать ориентироваться в этой сложной теме.
Так как вся теория такая теория, то я хочу обсудить определенные аспекты на конкретном бизнес-примере, который легко понять и следовать за ним. Я последовательно рассмотрю следующие аспекты:
- Отслеживание или управление — хореография или оркестровка?
- 3 варианта коммуникации: Асинхронная коммуникация с использованием команд и событий, RPC-связь «точка-точка», распределение работы с помощью движка рабочего процесса
- Центральный или децентрализованный движок рабочего процесса
- Владение моделями рабочего процесса
Бизнес пример
Пример, который я использую в этой статье, представляет собой простое приложение для выполнения заказа, доступное в виде flowing-retail примера приложения с исходным кодом на GitHub. Самое интересное в программе flowing-retail то, что она реализует различные архитектурные альтернативы и предоставляет примеры на разных языках программирования. Все примеры используют движок рабочего процесса, будь то Camunda BPM или Zeebe. Но вы можете перенести полученные знания на другие инструменты — я просто знаю инструменты из своей собственной компании лучше всего, и у меня есть много примеров кода, которые легко доступны.
Предположим, вы хотите реализовать некоторые бизнес-возможности (например, выполнение заказа при нажатии кнопки тире на Amazon) с помощью разделенных сервисов.
Отслеживание или управление? Хореография или оркестровка?
Один из первых вопросов, как правило, касается оркестровки или хореографии, при этом последний чаще всего рассматривается как лучший вариант (на основе статьи «Microservices» Martin Fowler). Обычно это сочетается с Event-driven архитектурой.
В такой хореографической архитектуре вы излучаете так называемые доменные события, и каждый заинтересованный может действовать в соответствии с этими событиями. Это трансляция. Идея заключается в том, что вы можете просто добавлять новые микросервисы, которые прослушивают события, ничего больше не меняя. Рабочий процесс как таковой нигде не является явным, но развивается как цепочка событий, которые передаются по кругу. Опасность заключается в том, что вы теряете из виду более масштабный поток, в нашем примере выполнение заказа. Становится невероятно трудно понять поток, изменить его или также управлять им. И даже ответы на такие вопросы, как «Есть ли какие-либо заказы просрочены?» или «Есть ли что-нибудь застрял, что нуждается в вмешательстве?» является проблемой. Я обсуждаю это в своем докладе Complex event flows in distributed systems (записанные, например, в QCon в Нью-Йорке или DevConf в Кракове).
Вы можете найти рабочий пример чистой хореографии здесь: https://github.com/berndruecker/flowing-retail/tree/master/kafka/java/choreography-alternative
Отслеживание
Легким решением может быть, по крайней мере, отслеживание потока событий. В зависимости от конкретной технической архитектуры (см. ниже), вы, вероятно, можете просто добавить движок рабочего процесса, считывающий все события и проверяющий, могут ли они быть соотнесены с потоком отслеживания. Я обсуждал это в своем докладе Monitoring and Orchestration of Your Microservices Landscape with Kafka and Zeebe (запись с Kafka Summit в Сан-Франциско).
Путешествие к управлению
Это неинвазивно, так как вам не нужно ничего менять в своей архитектуре. Но это позволяет вам начать что-то делать, например в случае задержки заказа:
Обычно это приводит к переходу от простого отслеживания потока к его реальному управлению:
Смешивать хореографию и оркестровку
Хорошая архитектура обычно представляет собой смесь хореографии и оркестровки. Справедливости ради надо сказать, что нелегко уравновесить эти две силы, не имея определенного опыта. Но мы видели много доказательств того, что это правильный путь, поэтому определенно стоит инвестировать время. Иначе ваша хореография, которая на доске была изящным танцем независимых профессионалов, обычно заканчивается скорее беспорядочным слэмом:
В flowing-retail примере это также означает, что у вас должен быть отдельный микросервис для наиболее важной бизнес-возможности: заказа клиента!
Роль движка рабочего процесса — три архитектурных альтернативы
Но как настроить архитектуру с помощью движка рабочего процесса для достижения такого баланса? Давайте упростим дело и предположим, что мы начинаем с нуля, и мы можем выбрать только из трех альтернатив архитектуры (так что никаких гибридных архитектур или наследия не будет).
- Асинхронное взаимодействие по командам и событиям (обычно с использованием шины сообщений или событий)
- Связь по принципу «точка-точка» по запросу/ответу (часто REST)
- Распределение работы по движку рабочего процесса
Мы пока не рассматриваем, следует ли запускать движок рабочего процесса централизованно или децентрализованно, что является отдельным вопросом, за него мы возьмёмся позже.
Асинхронная связь по командам и событиям
Эта архитектура основана на центральной шине для асинхронной связи. К этой шине подключаются различные микросервисы. Логика оркестровки и соответствующие потоки оркестровки принадлежат микросервисам. Рабочие процессы могут посылать на шину новые команды («эй оплата, пожалуйста, найди деньги для меня») или ждут, пока произойдут события («кто бы ни заинтересовался, я получил оплату за O42»).
- Типичные инструменты: Kafka, RabbitMQ (AMQP), JMS.
- Что делает движок рабочего процесса: обработка тайм-аута, управление цепочками действий / потоком, поддержка паттернов интеграции предприятия с отслеживанием состояния, таких как агрегатор или повторный упорядочитель, обработка согласованности и компенсации, также известная как Saga pattern, как обсуждалось в моем выступлении «Lost in transaction» (записано, например, на JavaZone Oslo).
- Пример реализации: https://github.com/berndruecker/flowing-retail/tree/master/kafka/java
- За: Временная развязка микросервисов; Правильное применение событийно-ориентированной архитектуры может уменьшить взаимосвязь; многие сценарии сбоев (например, отсутствующие ответные сообщения) прозрачны для разработчика, поэтому он хорошенько продумывает эти ситуации.
- Против: Требуется шина сообщений или событий в качестве центрального компонента, с которым нелегко работать. Отсутствие операционных инструментов для этих компонентов приводит к тому, что усилия направляются в доморощенные «больницы сообщений». Большинство разработчиков не так хорошо знакомы с асинхронной связью.
Связь точка-точка по запросу / ответу
В этой архитектуре вы просто активно вызываете другие микросервисы, чаще всего синхронно, с блокировкой. Самый известный способ сделать это — REST. Конечные точки обычно извлекаются из реестра. Движок рабочего процесса может организовать вызовы REST, а также помочь с проблемами удаленной связи — тему, которую я подробно обсуждал в моей статье о трех проблемах интеграции микросервисов (также доступной в виде выступления, записанной, например, на QCon London).
- Типичные инструменты: REST, SOAP, gRPC; Также может быть реализовано с блокировкой обмена сообщениями с помощью: https://www.rabbitmq.com/tutorials/tutorial-six-java.html
- Что делает движок рабочего процесса: паттерны устойчивости (например, повторные попытки), обработка тайм-аута, управление цепочками действий / потоком, последовательность и обработка компенсаций, так называемый Saga pattern, обсуждалось в моем выступлении «Lost in transaction» (записан, например, в JavaZone Oslo).
- Пример реализации: https://github.com/berndruecker/flowing-retail/tree/master/rest
- За: Простота установки и понятно большинству разработчиков; доступен хороший инструмент.
- Против: Вызовы выглядят как локальные, поэтому разработчики часто забывают о сложности распределенных систем; требует применения паттернов устойчивости (например, Circuit Breaker).
Распределение работы по механизму рабочего процесса
В этой архитектуре рабочий процесс распределяет работу между микросервисами, что означает, что он сам становится своего рода шиной. Микросервисы могут подписаться на определенную работу рабочего процесса и получать задачи через некую очередь.
- Типичные инструменты: External Tasks (Camunda BPM) или Workers (Zeebe).
- Что делает движок рабочего процесса: канал коммуникации, обработка тайм-аутов, управление цепочками действий / потоком, последовательность и обработка компенсаций, так называемый Saga pattern, о чем я говорил в «Lost in transaction» (записан, например, на JavaZone Oslo).
- Пример реализации: https://github.com/berndruecker/flowing-retail/tree/master/zeebe
- За: Простота установки; доступен хороший инструмент.
- Против: Движок рабочего процесса становится центральной частью архитектуры и должен работать надлежащим образом; связь между микросервисами только через рабочие процессы — или необходимо установить второй способ связи (например, REST или Messaging).
Мысли и рекомендации
Как всегда сложно дать четкую рекомендацию. Обычно я пытаюсь выяснить, что уже устоялось в компании, и основываю свое решение на интуиции относительно того, что может быть успешным в этой среде.
Например, если у клиента нет опыта работы с Kafka или Messaging, будет очень сложно заложить это на ходу. Поэтому, возможно, им лучше использовать архитектуру на основе REST, особенно если, например, они глубоко погрузились в Spring Boot, что делает некоторые проблемы относительно легко решаемыми. Однако, если они стратегически хотят двигаться в сторону большей асинхронности, я лично поддерживаю это, но я все же хочу убедиться, что они действительно могут справиться с этим. Если заказчик уже принимает Domain-driven design (DDD) и события, или даже использует такие фреймворки, как Akka или Axon, то событийный подход, включающий в себя движок рабочего процесса, может быть лучшим вариантом.
Таким образом, существует широкий спектр возможностей, и я думаю, что все варианты могут быть полностью действенными. Спросите себя, что подходит вашей организации, какие инструменты у вас уже есть и какие цели вы преследуете. Не забудьте о своих разработчиках, которым приходится выполнять всю тяжелую работу и которые должны по-настоящему понимать, что они делают. И не забывайте об операционной деятельности, чтобы быть уверенным, что вам действительно будет весело, когда вы выйдете в продакшн.
Центральный или децентрализованный движок рабочего процесса
Если вы хотите использовать движок рабочего процесса для распределения работы, он должен быть централизованным. В других альтернативах у вас есть два с половиной варианта:
- Децентрализованные движки, то есть вы запускаете по одному движку на микросервис.
- Один центральнный движок, который обслуживает несколько микросервисов.
- Центральный движок, который используется как децентрализованный.
Хорошим предварительным чтением по этому поводу будет Architecture options to run a workflow engine.
Децентрализованные движки
В микросервисах по умолчанию дают командам большую автономию и максимально изолируют их друг от друга. В этом смысле, по умолчанию подразумеваются децентрализованные движки, по одному рабочему движку на каждый микросервис, которому он нужен. Каждая команда может даже решить, какой именно движок (продукт) они хотят использовать.
- Пример реализации: https://github.com/berndruecker/flowing-retail/tree/master/kafka/java/order-camunda
- За: Автономия; изоляция.
- Против: Каждая команда должна использовать свой собственный движок (включая накат исправлений); никакого центрального мониторинга «из коробки» (пока).
Обычно в этой архитектуре больше всего обсуждается мониторинг: «Как мы можем следить за тем, что происходит»?
Чаще всего наличие децентрализованных операционных инструментов является преимуществом. Поскольку команды в микросервисных архитектурах часто выполняют DevOps для запускаемых ими служб, они несут ответственность за исправление ошибок в рабочих процессах. Поэтому довольно здорово, что у них есть сфокусированный взгляд, в котором они видят только то, за что отвечают.
Централизованный мониторинг с децентрализованными движками
Но часто вы все же хотите иметь общий обзор, по крайней мере, сквозных потоков, пересекающих границы микросервисов. В настоящее время клиенты часто создают собственный централизованный мониторинг, чаще всего на основе, например, Elastic. Теперь вы можете отправлять самые важные события из децентрализованных движков (например, запуск экземпляра рабочего процесса, достижение контрольной точки, сбой или завершение экземпляра рабочего процесса). Централизованный мониторинг просто показывает обзор на более высоком уровне и подробно связывает с децентрализованными операционными инструментами. Другими словами, децентрализованный движок рабочего процесса обрабатывает всю логику повторных попыток и обработки сбоев, а центральный мониторинг просто дает видимость всего потока.
В нашем собственном стеке мы начинаем допускать определенные инструменты для сбора данных из децентрализованных движков, например, Camunda Optimize или Zeebe Operate.
- Пример реализации: Упрощенный пример содержится в https://github.com/berndruecker/flowing-retail/tree/master/kafka/java/monitor
Один центральный движок
Для упрощения операций можно также запустить центральный движок. Это удаленный ресурс, к которому микросервисы могут подключаться для развертывания и выполнения рабочих процессов. Технически это может быть через REST (Camunda BPM) или gRPC (Zeebe).
- Пример реализации: https://github.com/berndruecker/flowing-retail/tree/master/kafka/java/order-zeebe
- За: Простота эксплуатации; централизованный мониторинг доступен сразу после установки.
- Против: менее строгая изоляция между микросервисами с точки зрения данных времени выполнения, но также и с точки зрения версий продукта; центральный компонент более важен с точки зрения требований доступности.
Если вы хотите использовать движок рабочего процесса в качестве распределителя, вам, конечно же, необходимо запустить его централизованно.
Центральный движок, который используется как децентрализованный
Такой подход обычно нуждается в некотором объяснении. Что вы можете сделать в Camunda, так это запустить движок рабочего процесса в виде библиотеки (например, используя Spring Boot Starter) децентрализованным образом в различных микросервисах. Но затем вы подключаете все эти движки к центральной базе данных, где они встречаются. Это позволяет бесплатно осуществлять централизованный мониторинг.
- За: Централизованный мониторинг доступен прямо «из коробки».
- Против: меньшая изоляция между микросервисами с точки зрения данных времени выполнения, но также и с точки зрения версий продукта, но на самом деле это регулируется такими средствами, как Rolling Upgrade и Deployment Aware Process Engine.
Мысли и рекомендации
Я лично предпочитаю децентрализованный подход в целом. Он просто совпадает с сутью и ценностью микросервисов.
Но я рад запустить центральный движок если есть веские доводы. Это особенно актуально для небольших компаний, где накладные расходы имеют большее значение, чем четкие границы коммуникации. Это также уменьшают проблему окна обслуживания движка рабочего процесса. Итак, практическое правило: чем крупнее компания, тем больше вы должны стремиться к децентрализации. Вдобавок к организационным причинам, нагрузка на движок также поможет определиться — поскольку несколько движков также означает распределение нагрузки.
Наличие гибрида с общей базой данных — изящный трюк, возможный с Camunda, но, наверное, не стоит злоупотреблять. Я бы ограничил это сценарием, где вы все еще можете контролировать все случаи использования движка рабочего процесса, и легко обсуждать их с каждой командой, использующей его.
Конечно, вы также можете смешивать и сочетать. Например, вы можете поделиться одной базой данных между ограниченным количеством микросервисов, которые каким-то образом связаны, но другие команды используют полностью разделенный движок.
Однако, гораздо более важным вопросом, чем вопрос о том, где работает сам движок, является вопрос о владении и управлении моделями процессов.
Право собственности на модели процессов
Независимо от физического развертывания модели рабочего процесса (читай: где работает движок) должно быть предельно ясно, кто отвечает за определенную модель, где она поддерживается и как вносятся изменения.
В микросервисных архитектурах право собственности на модель рабочего процесса должно принадлежать команде, владеющей соответствующей областью.
В примере flowing-retail есть две модели рабочего процесса:
- Выполнение заказа: Это относится к бизнес-возможностям выполнения заказов и имеет свое начало в микросервисе заказов.
- Платеж: принадлежит платежному микросервису.
Очень важно, чтобы вы распределяли право собственности на части бизнес-процесса в соответствии с границами вашей области. Не создавайте монолит BPM — например, где процесс выполнения заказа обрабатывает бизнес-логику и другие детали, связанные с оплатой — просто потому, что вы используете модель рабочего процесса.
«Процесс оркестровки»?
Меня часто спрашивают: Но где же «процесс оркестровки»? Это просто: в моем понимании микросервиса, нет такого понятия, как «процесс оркестровки».
Часто люди имеют в виду сквозные бизнес-процессы, как, например, выполнение заказа в приведенном выше примере. Конечно, сквозной процесс очень заметен и важен, но это логика области, как и все остальное, и переходит в границы микросервиса, в нашем примере — микросервиса заказа. В этом смысле микросервис заказа, содержащий логику сквозной оркестровки, может быть очень важным — но организованным так же, как и другие микросервисы, например, оплата.
Говоря «процесс оркестровки», некоторые люди имеют в виду логику области, которая включает только поток, поэтому соответствующий микросервис может не иметь другой логики (без собственных данных, без собственного программного кода,…). Это нормально, но это должен быть микросервис, поскольку даже эта логика требует четких обязанностей. И часто логика со временем все равно растет.
Резюме
Описанная тема сложна, и здесь нет простых ответов для всех сценариев. Надеюсь, этот пост дал вам некоторую ориентацию. Подведем итоги:
Вам нужно выбрать стиль коммуникации: Асинхронный, RPC-связь или распределение работы с помощью движка рабочего процесса. В зависимости от этого выбора движок рабочего процесса может оказать вам различные услуги:
Право собственности на модели рабочего процесса должно находиться в компетенции соответствующего микросервиса. Рабочий процесс должен быть четко сконцентрирован на этой области.
Вы можете запустить движок рабочего процесса централизованно или децентрализованно.
Отслеживать или управлять — вы должны стремиться к сбалансированному сочетанию хореографии и оркестровки.