Оркестрация микросервисов с Activiti BPMN Engine

от автора

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

Второй вариант может быть реализован в виде исполняемого кода, либо с использованием специальных движков для исполнения сценария бизнес-процесса, который может включать в себя вызов внешних сервисов. Стандартом в области описания бизнес-процессов является визуальная нотация BPMN 2.0 и наибольший интерес представляет соединение графической диаграммы и исполняемых сценариев, которое также называется Executable BPMN 2.0 и среды для его исполнения, среди которых можно назвать jBPM, Flowable, Camunda BPM и Activiti (она интересна еще и тем, что на ней реализуется управление процессами в Open Source системе управления документами Alfresco). В этой статье мы рассмотрим основы BPMN и создадим простой процесс для управления системой полива в зависимости от измеренной влажности (все компоненты системы реализованы как микросервисы).

Прежде чем мы перейдем к рассмотрению Activiti и ее настройке, нужно сказать несколько слов о BPMN. Эта нотация появилась как результат упрощения и обобщения для описания реальных бизнес-процессов другой известной нотации UML Activity Diagram (в более новой версии UML AD используются сходные графические элементы, как в BPMN). BPMN определяет соглашения по представлению потока управления и потока данных (разные варианты линий со стрелками), описанию выполняемых задач (они могут быть реализованы как пользовательские User Task, например при описании документооборота, так и выполняться через сценарий Script Task), разделения и склейки потока управления (выбор одного из вариантов, параллельное выполнение), передачи и приема сигналов и сообщений, а также описания стартового события сценария (может быть сообщением или, например, таймером). Важным аспектом описания является возможность определения альтернативных завершений задачи (по таймауту, при возникновении исключения, при прерывании и т.д.), благодаря этому можно описывать не только основной (успешный) сценарий, но и корректно обрабатывать ошибки и неожиданные ситуации.

Все элементы имеют обобщенное графическое отображение (например, задачи изображаются как прямоугольник, шлюзы управления — ромбом, события — окружностью) и уточняющую нотацию (чаще всего пиктограмма внутри объекта, например для идентификации сценарной или пользовательской задачи).

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

<definitions targetNamespace="http://activiti.org/bpmn20"               xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"               xmlns:activiti="http://activiti.org/bpmn"              xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"               xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"               xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI">   <process id="helloworld">     <documentation>Simple "Hello World" process</documentation>     <startEvent id="startevent1" name="Start"></startEvent> <scriptTask id="task1" name="Hello World" scriptFormat="groovy">   <script>     execution.setVariable("message", "Hello, World!")   </script> </scriptTask>         <endEvent id="endevent1" name="End"></endEvent>     <sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="task1"></sequenceFlow>     <sequenceFlow id="flow2" name="" sourceRef="task1" targetRef="endevent1"></sequenceFlow>   </process>   <bpmndi:BPMNDiagram id="BPMNDiagram_helloWorldProcess">     <bpmndi:BPMNPlane bpmnElement="helloWorldProcess" id="BPMNPlane_helloWorldProcess">       <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">         <omgdc:Bounds height="55" width="55" x="273" y="10"></omgdc:Bounds>       </bpmndi:BPMNShape>       <!-- здесь другие элементы -->     </bpmndi:BPMNPlane>   </bpmndi:BPMNDiagram> </definitions>

Здесь определено начальное событие (startevent1), задача для пользователя (task1) и конечное событие (endevent1), а также направление потока выполнения (после startevent1 переходим к task1, после task1 к endevent1). Визуально диаграмма может выглядеть следующим образом:

Простой процесс в Activiti
Простой процесс в Activiti

Для проектирования процесса можно использовать любой инструмент для разработки BPMN-диаграмм, например встроенный визуальный редактор процессов Activiti Explorer. Разработанный процесс может запускаться как внутри Activiti, так и в любом коде через импорт движка Activiti как зависимости:

implementation 'org.activiti:activiti-engine:7.1.0.M6'

и в дальнейшем в коде создается движок и выполняется запуск процесса:

val processEngine = ProcessEngineConfiguration.   createStandaloneInMemProcessEngineConfiguration().   setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE).   setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000").   setAsyncExecutorActivate(false).   buildProcessEngine();

Дальше из processEngine можно получить доступ к сервисам:

  • processEngine.getRuntimeService() — управление выполнением сценария

  • processEngine.getRepositoryService() — отвечает за загрузку и хранение ресурсов (включая описание BPMN-процессов)

  • processEngine.getIdentityService() — отвечает за управление учетными записями

  • processEngine.getHistoryService() — возможность посмотреть историю запущенного workflow

  • processEngine.getTaskService() — возможность программно создавать задачи и смотреть за текущим состоянием

Для запуска процесса из созданного processEngine необходимо обратиться к репозиторию и затем передать на выполнение в RuntimeService:

val repositoryService = processEngine.getRepositoryService(); repositoryService.createDeployment()   .addClasspathResource("hello.bpmn20.xml")   .deploy(); val variables = mutableMapOf<String, Object>(); variables["name"] = "World"; val runtimeService = processEngine.getRuntimeService(); val processInstance = runtimeService.startProcessInstanceByKey("helloworld", variables);

Для передачи значений между элементами (узлами) процесса используется контекст переменных, которые могут быть инициализированы при старте процесса, модифицироваться во время выполнения процесса (через execution.SetVariable) и использоваться при принятии решений в узлах ветвления (ExclusiveGateway), а также быть получены после завершения выполнения процесса. Также существует контекст задачи (объект Task) из которого можно получать сохраненные локальные результаты, например, может быть добавлен Listener по завершению ScriptTask (внутри определения задачи):

<activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">   <activiti:field name="script">     <activiti:string>       execution.setVariable('message', task.getVariable('message'));     </activiti:string>   </activiti:field> </activiti:taskListener>

Теперь посмотрим внимательнее на scriptTask. В атрибуте scriptFormat указывается язык сценария (groovy, JavaScript, Python), при этом для поддержки Groovy и Python нужно добавить соответствующие зависимости (org.codehaus.groovy:groovy-all:3.0.8 или org.python:jython:2.7.2). Для запуска JavaScript используются возможности Nashorn API, поэтому для выполнения сетевых запросов можно использовать классы Java:

function read(inputStream){     var inReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));     var inputLine;     var response = new java.lang.StringBuffer();      while ((inputLine = inReader.readLine()) != null) {            response.append(inputLine);     }     inReader.close();     return response.toString(); }  var con = new java.net.URL("URL").openConnection(); con.requestMethod = "GET"; response = read(con.inputStream);

Теперь можно выполнить соответствующие вызовы для обращения к микросервисам из ServiceTask и сохранить промежуточные результаты в переменные задачи (или процесса) и использовать их в проверке ExclusiveGateway. Предположим, что наши микросервисы опубликованы на адресах http://gateway/metrics (возвращает json с текущим значением влажности) и http://gateway/automation (принимает параметром sprinkler значение 0 или 1 при необходимости включить или выключить поливальную машину). Тогда процесс будет состоять из startEvent, getMetrics, gateway, turnOnAutomation, turnOffAutomation, endEvent. Для getMetrics и управления автоматизацией код создается аналогично упомянутому выше сценарию, единственный новый элемент — exclusiveGateway:

<exclusiveGateway id="gateway" name="Exclusive Gateway" />  <sequenceFlow id="flow2" sourceRef="gateway" targetRef="turnOnAutomatic">   <conditionExpression xsi:type="tFormalExpression">${humidity &lt; 30}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow3" sourceRef="gateway" targetRef="turnOffAutomatic">   <conditionExpression xsi:type="tFormalExpression">${humidity &gt;= 30}</conditionExpression> </sequenceFlow>

Таким образом, использованием BPMN-движка помогает выполнить координацию вызовов микросервисов при возникновении необходимых условий и одновременно с этим сохранить все преимущества визуального проектирования и документирования процесса

Полный исходный текст с описанием BPMN-процесса и код для запуска процесса (и эмуляции API микросервисов) доступен на Github.


Приглашаем всех желающих на открытое занятие «Шардирование в микросервисной архитектуре», на котором рассмотрим виды шардинга, проанализируем стратегии шардирования. Также рассмотрим консистентное шардирование, поиск, вычисления, хранение и как правильно делить данные. Регистрация на занятие.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/671360/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *