Микросервисная архитектура предполагает декомпозицию системы на относительно независимые фрагменты с собственными источниками данных, которые могут переиспользоваться в различных процессах и обмениваться данными. Но в таком решении есть и оборотная сторона, связанная с необходимостью включения логики оркестрации или непосредственно в код сервисов (что затрудняет возможность гибкого изменения процесса), либо использовать внешний оркестратор, который будет обеспечивать запуск микросервисов с входными параметрами, получение и передачу результата, а также управление сценарием при возникновении ошибок или определенных ситуаций при выполнении процесса.
Второй вариант может быть реализован в виде исполняемого кода, либо с использованием специальных движков для исполнения сценария бизнес-процесса, который может включать в себя вызов внешних сервисов. Стандартом в области описания бизнес-процессов является визуальная нотация 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
). Визуально диаграмма может выглядеть следующим образом:
Для проектирования процесса можно использовать любой инструмент для разработки 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 < 30}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow3" sourceRef="gateway" targetRef="turnOffAutomatic"> <conditionExpression xsi:type="tFormalExpression">${humidity >= 30}</conditionExpression> </sequenceFlow>
Таким образом, использованием BPMN-движка помогает выполнить координацию вызовов микросервисов при возникновении необходимых условий и одновременно с этим сохранить все преимущества визуального проектирования и документирования процесса
Полный исходный текст с описанием BPMN-процесса и код для запуска процесса (и эмуляции API микросервисов) доступен на Github.
Приглашаем всех желающих на открытое занятие «Шардирование в микросервисной архитектуре», на котором рассмотрим виды шардинга, проанализируем стратегии шардирования. Также рассмотрим консистентное шардирование, поиск, вычисления, хранение и как правильно делить данные. Регистрация на занятие.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/671360/
Добавить комментарий