Java API#
Существуют два вида Java API Workflow Machine:
внутренний API (обработчики, EventContext);
внешний API (старт процессов, вызов событий и т.д.).
Далее в разделе будут подробно рассмотрены оба вида с примерами их использования.
Внутренний API#
Внутренний Java API используется разработчиками, которые создают свой workflow-процесс. Внутренний Java API — это API, используемый непосредственно в обработчиках событий. Для работы с внутренним API Workflow нужно внедрить обработчик EventContext.
Пример:
public class Handler {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
}
Идентификатор EventContext совпадает с идентификатором spring-executor, который определяется в Spring-контексте.
Внутренний API позволяет:
получить информацию о текущем состоянии процесса (flow, state, cmd);
осуществить put/get операции над атрибутами flow и request scope;
получить входящий объект запроса и установить ответ (input и output);
сформировать Transition для перехода;
работать с историей переходов.
Остановимся подробнее на каждом из пунктов.
Получение информации о текущем состоянии процесса
public Transition getCurrentStateExample() {
//получение текущего flow
final String flowId = context.getCurrentFlow();
//получение текущего состояния
final String stateId = context.getCurrentState();
//получение текущего шага истории
final HistoryStep currentStep = context.getCurrentStep();
//получение команды, исполняемой WorkflowMachine в данный момент
//возможные значения: START, EVENT, ROLLBACK, EXIT
final String command = context.getCurrentCommand();
return Transition.go("somewhere");
}
Работа с flow и request scope
Контекст исполнения процесса workflow подразделяется на:
контекст уровня текущего flow (flowScope);
контекст уровня текущего запроса (requestScope).
FlowScope
FlowScope — это put/get-хранилище уровня текущего flow. Данные FlowScope доступны, пока происходит исполнение текущего flow, и не доступны для subflow. Например, происходит исполнение flow1 с FlowScope1. В процессе происходит переход на flow2. FlowScope1 становится недоступен, вместо него появляется FlowScope2. После завершения flow2 исполнение возвращается во flow1, и FlowScope1 опять становится доступен.
Во время выполнения команды rollback происходит восстановление FlowScope, актуального для шага, на который производится откат. В случае отката на внешний flow, восстановление FlowScope происходит лишь в случае указания для этого flow-атрибута saveable=true.
Пример работы с FlowScope:
public Transition flowScopeExample() {
context.setFlowAttr("key", "value");
assert context.getFlowAttr("key").equals("value");
context.removeFlowAttr("key");
return Transition.go("somewhere");
}
RequestScope
RequestScope — это put/get-хранилище уровня текущего запроса. Данные RequestScope доступны, пока происходит обработка текущего запроса. Запросами является исполнение команды START или EVENT.
Единственным исключением продления жизни RequestScope на более чем один запрос является ситуация с внешним переходом на другой flow. Если Workflow Machine возвращает ответ типа EXTERNAL_ENTER или EXTERNAL_RETURN, это значит, что компонент не нашел нужный flow в meta-storage и требуется переход на другую машину. Для продолжения исполнения на машине с нужным flow должно быть вызвано событие on-enter (или on-return). Пока это не произойдет, RequestScope будет "заморожен".
Частый вопрос: Как передать какие-либо данные в subflow? FlowScope будет уже недоступен в новом flow.
Ответ: Нужно использовать RequestScope.
Пример работы с RequestScope:
public Transition requestScopeExample() {
context.setRequestAttr("key", "value");
assert context.getRequestAttr("key").equals("value");
context.removeRequestAttr("key");
return Transition.go("somewhere");
}
Ниже приведены несколько вариантов работы с внутренним API.
Input/Output.
Работа с входными и выходными данными в Workflow происходит через использование EventContext.
Пример:
public class WorkflowHandlerBean {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
public Transition inputOutputExample() throws JsonProcessingException {
//input
final String inputAsString = context.getInput();
final HashMap inputAsJson = context.parseInputToJson(HashMap.class);
//output
context.setOutput(inputAsJson);
return Transition.go("somewhere");
}
}
Важно: input/output хранятся в RequestScope под специальными системными ключами. Это значит, что все ограничения RequestScope распространяются и на Input/Output.
Работа с Transition.
Каждый метод-обработчик событий должен возвращать объект типа Transition (переход куда-либо). Каждый Transition идентифицируется кодом.
Примеры создания:
public Transition transitionExample() throws JsonProcessingException {
//обычный переход без сохранения
Transition go = Transition.go("code");
//переход с сохранением «хлебной крошки». dataForSave — данные шага, title - заголовок «хлебной крошки», value — значение «хлебной крошки»
Transition goWithSave1 = Transition.goWithSave("code", "dataForSave", "title", "value");
//переход с сохранением «хлебной крошки» без title. Будет использовать title по умолчанию, который указан в xml.
Transition goWithSave2 = Transition.goWithSave("code", "dataForSave", "value");
//указываем статус «хлебной крошки»
Transition goWithSave3 = Transition.goWithSave("code", "dataForSave", "title", "value", StepStatus.ACTIVE);
//стоп, процесс остановится в текущем состоянии и будет возвращен результат исполнения
Transition stop = Transition.stop();
Transition stopWithSave1 = Transition.stopWithSave("dataForSave", "title", "value");
Transition stopWithSave2 = Transition.stopWithSave("dataForSave", "value");
//указываем статус «хлебной крошки»
Transition stopWithSave3 = Transition.stopWithSave("dataForSave", "title", "value", StepStatus.ACTIVE);
//rollback к шагу истории
Transition back = Transition.back("stepId");
Transition backByTitle = Transition.backByTitle("title"); //в случае нескольких шагов с таким title — ошибка
Transition backByTitle = Transition.backByTitle("title", "value"); //в случае нескольких шагов с таким title + value — ошибка
return Transition.go("somewhere");
}
Важно: при вызове
Transition.back()(либо при выполнении методаrollback) данные из сохраненного шага истории, на который осуществляется возврат, можно получить из Request атрибута OUTPUT_CONTEXT.
Работа с историей.
public Transition history() {
//получить историю в обратном историческом порядке
context.getHistory();
//скрыть последний шаг
context.hideLastHistoryItem();
//скрыть первый шаг
context.hideFirstHistoryItem();
//скрыть все шаги с определенный state
context.hideAllHistoryItemsByState("state");
//получение шагов по имени state
List<HistoryStep> res = context.findHistoryItemsByState("state");
//получение шага по имени state
HistoryStep step = context.findHistoryItemByState("state");
}
Более подробное описание методов смотрите в стандартном javadoc (в разделе API).
Внешний API#
Внешний Java API используется разработчиками, которые хотят вызывать созданный кем-то ранее процесс Workflow.
Несмотря на то, что во всех примерах все общение с Workflow Machine происходило по HTTP протоколу (через сервлет workflow-gate), у компонента Workflow (UIWF) существует и стандартный Java API.
Для работы с Workflow в Java-коде достаточно следующей конфигурации:
<workflow:configuration id="workflowConfiguration">
<workflow:default configService="configService" environment="environment" monitoringService="monitoringService"/>
</workflow:configuration>
<workflow:data-storage id="workflowData" configuration="workflowConfiguration"/>
<workflow:spring-executor id="workflowExecutor"/>
<workflow:local-meta-storage id="workflowMeta"/>
<workflow:flow name="first" path="flow/flow1.xml"/>
</workflow:local-meta-storage>
<workflow:workflow-machine id="workflowMachine"
dataStorage="workflowData"
metaStorage="workflowMeta"
handlerExecutor="workflowExecutor"
configuration="workflowConfiguration" />
Пример Spring Boot:
@HandlerExecutor
public interface WorkflowExecutor {
}
@LocalMetaStorage
@FlowMeta(name = "first", path = "flow/flow1.xml")
public interface WorkflowMeta {
}
@WorkflowMachine(metaStorage = WorkflowMeta.class, handlerExecutor = WorkflowExecutor.class)
public interface SomeFlowMachine {
}
Для использования Java API нет необходимости определять workflow-gate и реестр URL.
При использовании Java API интеграция компонента Workflow (UIWF) с компонентом Сервис шаблонов форм UI (UFTM) продукта Platform V Content Management (SDC) будет недоступна. Подробнее смотрите в разделе «Дополнительные функции модуля Workflow».
Java API и HTTP API функционально похожи друга на друга. Единственным ключевым отличием является работа с распределенными subflow. В случае использования HTTP API в момент возникновения внешнего перехода, клиенту уходит URL с адресом сервера, на которой расположен нужный subflow (Подробнее в пункте «Subflow в распределенной среде»). В Java API мы просто получим EventResult со статусом EXTERNAL_ENTER. Таким образом, машина просто сообщает нам, что она не нашла нужный flow в своем meta-storage и нужен переход на другую Workflow Machine, на которой расположен конечный flow. Пользователь API должен обработать данную ситуацию самостоятельно и определить где находится конечный flow. В случае использования HTTP API это задача решается на уровне workflow-gate, который вычисляет url нужного flow, и сообщает его клиенту.
Subflow#
Чтобы понять, что представляет собой Subflow в рамках одного приложения, выполните следующие действия:
Разбейте flow (из базового примера работы с Workflow) на два независимых flow.
createUser.xml
<flow xmlns="http://ufs.<домен>/platform/flow-definition"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ufs.<домен>/platform/flow-definition http://ufs.<домен>/platform/flow-definition.xsd"
start-state="loginForm">
<state name="loginForm">
<event name="saveLogin" handler="createUserHandler#saveLogin"/>
<state-transition name="transitionToEnd" to="end"/>
<flow-transition name="success" subflow="saveUsername" on-return="createUserHandler#onReturnFromSaveUsername"/>
</state>
</flow>
saveUsername.xml
<flow xmlns="http://ufs.<домен>/platform/flow-definition"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ufs.<домен>/platform/flow-definition http://ufs.<домен>/platform/flow-definition.xsd"
start-state="usernameForm">
<state name="usernameForm">
<event name="saveUsername" handler="createUserHandler#saveUsername"/>
<state-transition name="success" to="end"/>
</state>
</flow>
Форма usernameForm была вынесена в отдельный subflow. Для перехода в выделенный subflow используется flow-transition. Кроме этого был добавлен обработчик события возврата на родительский flow onReturnFromSaveUsername. Обработчик нужен для того, чтобы определить следующий шаг flow после возврата из subflow.
Код обработчика:
public Transition onReturnFromSaveUsername() {
return Transition.go("transitionToEnd");
}
В Spring-контексте добавьте описание нового flow.
<workflow:flow name="saveUsername" path="flow/saveUsername.xml"/>
Пример Spring Boot
@FlowMeta(name = "saveUsername", path = "flow/saveUsername.xml")
HTTP API flow не изменился. Но теперь можно переиспользовать форму saveUsername в других flow.
В данном примере оба flow существуют в рамках одного приложения. Альтернативный вариант — размещение flow в разных приложениях.
Subflow в распределенной среде#
Для работы Subflow в распределенной среде нужно обязательно использовать распределенное хранилище данных процессов. Сейчас в качестве хранилища используется компонент Сессионные данные (SUSD) продукта Platform V SessionsData (SUS). Либо можно использовать другой распределенный кеш.
Архитектура вызова subflow, расположенного на другом сервере, представлена на рисунке ниже:

В случае если в процессе исполнения происходит переход на другой flow (или возврат), расположенный на другом сервере, то Workflow Machine возвращает технический ответ (EXTERNAL_ENTER, в случае возврата — EXTERNAL_RETURN). В ответе указан новый URL, по которому расположен целевой flow. Клиент обращается по этому URL с событием on-enter (или on-return, если это возврат). После чего исполнение продолжается.
При вызове внешнего flow EXTERNAL_ENTER либо возврате на внешний flow EXTERNAL_RETURN flow, который необходимо запустить на вызываемой стороне, определяется исходя из имени flow, сохраненного в данных процесса при осуществлении внешнего вызова на вызывающей стороне. Таким образом возможно вызывать различные внешние flow, используя один и тот же URL gate сервлета, за которым они расположены. При осуществлении отката на внешний flow компонент Workflow Machine возвращает технический ответ EXTERNAL_ENTER, аналогичный тому, который возвращается при переходе на внешний flow.
Важно:
Для корректной работы название внешнего flow должно быть зарегистрировано в local-meta-storage с помощью тега external-flow. В противном случае переход будет прерываться ошибкой валидации. Валидацию для внешних flow можно отключить, явно указав
validate="false"дляlocal-meta-storage.
По данному URL расположена также клиентская часть Workflow (она доступна по GET-запросу, а flow доступен по POST-запросу). При использовании клиентской части Workflow обработка технического ответа о переходе на другой сервер происходит автоматически.
Для распределенной структуры flow в каждом приложении должен быть сконфигурирован url-registry. Это реестр URL, который хранит информацию о том, по какому URL расположен каждый flow.
В платформе есть две реализации реестра:
реализация реестра на базе компонента PACMAN (CFGA) продукта Platform V Frontend Std (#FS) —
config-url-registry;локальный реестр, с настройкой URL прямо в Spring-контексте (используется только для тестирования) —
local-url-registry.
Пример конфигурации config-url-registry:
<workflow:config-url-registry id="workflowRegistry" configProvider="configProvider"/>
Пример Spring Boot
@ConfigUrlRegistry(configProvider = SomeConfigProvider.class)
public interface WorkflowRegistry {
}
Реестр ищет URL в компоненте PACMAN (CFGA) продукта Platform V Frontend Std (#FS) по ключам вида platform.workflow.url.\<flowName\>.
При внешнем вызове EXTERNAL_ENTER полученный из компонента PACMAN (CFGA) URL текущего flow сохраняется в данных процесса на время внешнего вызова, что необходимо для последующего возврата на текущий flow.
Таким образом, подсистема, осуществляющая вызов внешнего подпроцесса, должна заводить параметр platform.workflow.url.\<flowName\> со своим flow, с которого осуществляется вызов, в своем разрезе. В дальнейшем при возврате EXTERNAL_RETURN, в случае если не удалось получить URL для возврата из компонента PACMAN (CFGA), используется URL, ранее сохраненный в данных процесса.
При старте любого flow, в случае если удалось обнаружить в компоненте PACMAN (CFGA) URL для этого flow в параметре вида platform.workflow.url.\<flowName\>, происходит сохранение этого URL в данных процесса. При проведении внешнего вызова EXTERNAL_ENTER или возврата EXTERNAL_RETURN осуществляется поиск URL вызываемого flow сначала в компоненте PACMAN (CFGA), затем, если не был найден, — в данных процесса.
Таким образом, подсистема, осуществляющая вызов внешнего подпроцесса, должна заводить параметры вида platform.workflow.url.\<flowName\>, содержащие URL всех flow подсистемы, на которые может производиться возврат EXTERNAL_RETURN либо откат EXTERNAL_ENTER.
Пример работы с распределенными flow по HTTP API:
Flow createUser.xml расположен по адресу /app1/workflow-gate. Flow saveUsername.xml расположен по адресу /app2/workflow-gate.
Старт flow.
/app1/workflow-gate?cmd=START&name=createUser
Тело ответа:
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "0732c18a-571e-4ebc-9519-5b9f445c12bf",
"flow": "createUser",
"state": "loginForm",
"history": []
}
}
Сохранение логина/пароля.
/app1/workflow-gate?cmd=EVENT&name=saveLogin%pid=0732c18a-571e-4ebc-9519-5b9f445c12bf
Тело запроса:
{
"login" : "oleg",
"password" : "some"
}
Тело ответа (переход на новый flow):
{
"success": true,
"body": {
"result": "EXTERNAL_ENTER",
"pid": "0732c18a-571e-4ebc-9519-5b9f445c12bf",
"url": "/app2/workflow-gate"
}
}
Переход на новый flow.
/app2/workflow-gate?cmd=EVENT&name=on-enter%pid=0732c18a-571e-4ebc-9519-5b9f445c12bf
Тело ответа:
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "0732c18a-571e-4ebc-9519-5b9f445c12bf",
"flow": "saveUsername",
"state": "usernameForm",
"history": []
}
}
Сохранение ФИО.
/app2/workflow-gate?cmd=EVENT&name=saveUsername%pid=0732c18a-571e-4ebc-9519-5b9f445c12bf
Тело запроса:
{
"name" : "Олег",
"surname" : "Переход"
}
Тело ответа (возврат на главный flow):
{
"success": true,
"body": {
"result": "EXTERNAL_RETURN",
"pid": "0732c18a-571e-4ebc-9519-5b9f445c12bf",
"url": "/app2/workflow-gate"
}
}
Возвращение на главный flow.
/app1/workflow-gate?cmd=EVENT&name=on-return%pid=0732c18a-571e-4ebc-9519-5b9f445c12bf
Тело ответа:
{
"success": true,
"body": {
"result": "END"
}
}
Внешний переход на flow происходит с использованием специального заголовка в запросе, который содержит в себе URL. Данная опция конфигурируется с помощью ключей компонента PACMAN (CFGA) продукта Platform V Frontend Std (#FS). Значения обновляются раз в 5 минут.
Ключи и их описание представлены в таблице ниже:
Ключ | Описание | Значение по умолчанию | Тип |
|---|---|---|---|
ufs.wf.header.redirect | Имя header в котором будет храниться URL адрес перехода | X-GW-Redirect | STRING |
ufs.wf.header.redirect.enable | Активировать подстановку header в ответ или нет | true | BOOLEAN |