Сценарии использования цепочки навигации с примерами реализации#
Существует пять вариантов использования цепочки навигации:
Линейный.
Фантомный шаг.
Редактирование.
«Перепрыгивание» шага.
Уход в subflow.
Далее каждый сценарий будет подробно рассмотрен.
Линейный#

Это самый простой (и достаточно редкий) вариант — движение происходит по flow шаг за шагом.
Пример простого flow с тремя шагами:
<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="view1">
<state name="view1">
<event name="next" handler="handler#nextWithSave"/>
<state-transition name="next" to="view2"/>
</state>
<state name="view2">
<event name="next" handler="handler#nextWithSave"/>
<state-transition name="next" to="view3"/>
</state>
<state name="view3">
<event name="next" handler="handler#nextWithSave"/>
<state-transition name="next" to="end"/>
</state>
</flow>
Код обработчика:
public class Handler {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
public Transition nextWithSave() {
final FormDTO data = context.parseInputToJson(FormDTO.class);
//do some business
return Transition.goWithSave("next", data, "title", "value");
}
public static class FormDTO implements Serializable {
private static final long serialVersionUID = -1739403514315570713L;
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
}
Обработчик осуществляет parsing данных формы в DTO-модель и сохраняет шаг вместе с данными.
Пример работы:
Старт.
/workflow-gate?cmd=START&name=history1
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9567501b-b926-4845-9968-91294ca07464",
"flow": "history1",
"state": "view1",
"history": [
{
"id": "685d8c96-e252-472c-a6b8-74101c928907",
"flow": "history1",
"state": "view1",
"title": "",
"status": "ACTIVE"
}
]
}
}
Переход на view2.
/workflow-gate?cmd=EVENT&name=next&pid=9567501b-b926-4845-9968-91294ca07464
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9567501b-b926-4845-9968-91294ca07464",
"flow": "history1",
"state": "view2",
"history": [
{
"id": "bf3377cc-8374-434d-9bcc-f5d9332a4376",
"flow": "history1",
"state": "view2",
"title": "",
"status": "ACTIVE"
},
{
"id": "685d8c96-e252-472c-a6b8-74101c928907",
"flow": "history1",
"state": "view1",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Переход на view3.
/workflow-gate?cmd=EVENT&name=next&pid=9567501b-b926-4845-9968-91294ca07464
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9567501b-b926-4845-9968-91294ca07464",
"flow": "history1",
"state": "view3",
"history": [
{
"id": "0b8c0b1b-4fc3-498b-be39-d928a6f44178",
"flow": "history1",
"state": "view3",
"title": "",
"status": "ACTIVE"
},
{
"id": "bf3377cc-8374-434d-9bcc-f5d9332a4376",
"flow": "history1",
"state": "view2",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "685d8c96-e252-472c-a6b8-74101c928907",
"flow": "history1",
"state": "view1",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Возвращение на view1 и получение данных формы на этом шаге.
/workflow-gate?cmd=ROLLBACK&name=685d8c96-e252-472c-a6b8-74101c928907&pid=9567501b-b926-4845-9968-91294ca07464
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9567501b-b926-4845-9968-91294ca07464",
"flow": "history1",
"state": "view1",
"output": { "name": "oleg" },
"history": [
{
"id": "685d8c96-e252-472c-a6b8-74101c928907",
"flow": "history1",
"state": "view1",
"title": "",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Фантомный шаг#

Фантомный шаг — это промежуточный шаг, исчезающий после перехода с него.
Это стандартное поведение Workflow. Нужно просто использовать метод go() для создания Transition. Тогда текущий (head) шаг не будет сохранен.
Редактирование#

Редактирование — это ситуация, когда не отходя от какого-либо процесса, делается пара шагов, а затем возвращение назад. Например, при просмотре некого списка, осуществляется переход на редактирование записи списка, после чего происходит возврат назад.
Пример flow 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="list">
<state name="list">
<event name="edit" handler="handler#toEdit"/>
<state-transition name="success" to="edit1"/>
</state>
<state name="edit1">
<event name="doEdit" handler="handler#doEdit"/>
<state-transition name="success" to="edit2"/>
</state>
<state name="edit2">
<event name="endEdit" handler="handler#returnToList"/>
<state-transition name="success" to="list"/>
</state>
</flow>
Flow состоит из трех состояний: список данных и два состояния редактирования. После редактирования процесс возвращается к списку.
Код обработчика:
public class Handler {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
public Transition toEdit() {
final FormDTO data = context.parseInputToJson(FormDTO.class);
//do some business
return Transition.goWithSave("success", data, "title", "value");
}
public Transition doEdit() {
final FormDTO data = context.parseInputToJson(FormDTO.class);
//do some business
return Transition.goWithSave("success", data, "title", "value");
}
public Transition returnToList() {
Iterator<HistoryStep> iterator = context.getHistory().iterator();
//1. Пропускаем текущий шаг
iterator.next();
//2. Ставим статус HIDDEN. Таким образом говорим клиенту, о том, что эти "крошки" отображать не нужно
iterator.next().setStatus(HIDDEN);
iterator.next().setStatus(HIDDEN);
return Transition.go("success");
}
}
Пример работы:
Старт.
/workflow-gate?cmd=START&name=edit
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "2ddcda6f-1c48-485b-9bed-98e63742a3fe",
"flow": "edit",
"state": "list",
"history": [
{
"id": "b24fd0bf-60e8-44ef-9a96-9196a0a4c8c2",
"flow": "edit",
"state": "list",
"title": "",
"status": "ACTIVE"
}
]
}
}
Переход к редактированию.
/workflow-gate?cmd=EVENT&name=edit&pid=2ddcda6f-1c48-485b-9bed-98e63742a3fe
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "2ddcda6f-1c48-485b-9bed-98e63742a3fe",
"flow": "edit",
"state": "edit1",
"history": [
{
"id": "33df2d4d-e037-4936-81da-2b1778803cbe",
"flow": "edit",
"state": "edit1",
"title": "",
"status": "ACTIVE"
},
{
"id": "b24fd0bf-60e8-44ef-9a96-9196a0a4c8c2",
"flow": "edit",
"state": "list",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Редактирование.
/workflow-gate?cmd=EVENT&name=doEdit&pid=2ddcda6f-1c48-485b-9bed-98e63742a3fe
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "2ddcda6f-1c48-485b-9bed-98e63742a3fe",
"flow": "edit",
"state": "edit2",
"history": [
{
"id": "9e427ff7-1b44-4a57-8672-5fb00ad3e74c",
"flow": "edit",
"state": "edit2",
"title": "",
"status": "ACTIVE"
},
{
"id": "33df2d4d-e037-4936-81da-2b1778803cbe",
"flow": "edit",
"state": "edit1",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "b24fd0bf-60e8-44ef-9a96-9196a0a4c8c2",
"flow": "edit",
"state": "list",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Завершение редактирования.
/workflow-gate?cmd=EVENT&name=endEdit&pid=2ddcda6f-1c48-485b-9bed-98e63742a3fe
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "2ddcda6f-1c48-485b-9bed-98e63742a3fe",
"flow": "edit",
"state": "list",
"history": [
{
"id": "8dcd4e96-a9a8-4ec0-b938-06815df6d112",
"flow": "edit",
"state": "list",
"title": "",
"status": "ACTIVE"
},
{
"id": "33df2d4d-e037-4936-81da-2b1778803cbe",
"flow": "edit",
"state": "edit1",
"title": "title",
"value": "value",
"status": "HIDDEN"
},
{
"id": "b24fd0bf-60e8-44ef-9a96-9196a0a4c8c2",
"flow": "edit",
"state": "list",
"title": "title",
"value": "value",
"status": "HIDDEN"
}
]
}
}
Как видно из JSON-ответа, активен только текущий шаг.
«Перепрыгивание» шага#

Ситуация, когда продвижение происходит сразу на несколько шагов вперед, но при этом пропущенные шаги отображаются в истории.
Пример flow 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="step1">
<state name="step1">
<event name="next" handler="handler#next"/>
<event name="nextOnTwoStep" handler="handler#nextOnTwoStep"/>
<state-transition name="next" to="step2"/>
</state>
<state name="step2" on-enter="handler#checkForSkip">
<event name="next" handler="handler#next"/>
<state-transition name="next" to="step3"/>
</state>
<state name="step3">
<event name="next" handler="handler#next"/>
<state-transition name="next" to="end"/>
</state>
</flow>
Код обработчика:
public class Handler {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
public Transition next() {
return Transition.goWithSave("next", "data", "title", "value");
}
public Transition nextOnTwoStep() {
context.setFlowAttr("skip", true);
return Transition.goWithSave("next", "data", "title", "value");
}
public Transition checkForSkip() {
if (context.getFlowAttr("skip") != null && (boolean) context.getFlowAttr("skip")) {
return Transition.goWithSave("next", "data", "title", "value");
}
return Transition.stop();
}
}
На шаге step1 определено событие nextOnTwoStep, обработчик которого добавляет во flow-scope флаг skip=true. На шаге step2 обработчик системного события on-enter делает проверку на наличия этого флага. В случае если флаг присутствует, происходит переход на step3.
Пример работы:
Старт.
/workflow-gate?cmd=START&name=skip
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9568c6f0-d515-425b-b177-20f1b5bf9924",
"flow": "skip",
"state": "step1",
"history": [
{
"id": "4223820f-4524-4c27-a189-516f7bfc9855",
"flow": "skip",
"state": "step1",
"title": "",
"status": "ACTIVE"
}
]
}
}
Пропуск одного шага.
/workflow-gate?cmd=EVENT&name=nextOnTwoStep&pid=9568c6f0-d515-425b-b177-20f1b5bf9924
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "9568c6f0-d515-425b-b177-20f1b5bf9924",
"flow": "skip",
"state": "step3",
"history": [
{
"id": "4772b61f-bffb-4c5c-a6f0-48cd371cd294",
"flow": "skip",
"state": "step3",
"title": "",
"status": "ACTIVE"
},
{
"id": "9421d70a-c590-44ee-92cb-dfd914d7be4d",
"flow": "skip",
"state": "step2",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "a1017f6a-f999-40f7-a096-acfbb6cd5b6d",
"flow": "skip",
"state": "step1",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Как видно по JSON-ответу, был произведен переход сразу на шаг step3, но при этом step3 тоже отражен в истории.
Уход в subflow#

Данный сценарий представляет собой ситуацию, когда происходит уход в subflow. Каждый шаг subflow отображается в цепочке навигации. После завершения subflow, он отображается в цепочке навигации одним шагом.
Данная функциональность реализуется за счет того факта, что клиент отображает только «хлебные крошки» текущего flow.
Пример mainFlow.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="step1">
<state name="step1">
<event name="toSubflow" handler="handler#toSubflow"/>
<state-transition name="toSubflow" to="toSubflow"/>
</state>
<state name="toSubflow" on-enter="handler#subflowCall">
<state-transition name="next" to="step2"/>
<flow-transition name="toSubflow" subflow="subFlow" on-return="handler#saveSubflowData"/>
</state>
<state name="step2">
<event name="next" handler="handler#next"/>
<state-transition name="next" to="end"/>
</state>
</flow>
mainFlow состоит из двух шагов: step1 и step2. State toSubflow — это специальный промежуточный шаг, который отвечает только за вызов subflow. Этот промежуточный шаг нужен для того, чтобы сохранить в истории вызов subflow как отдельную «хлебную крошку», чтобы потом можно было нажать на неё и запустить subflow.
Пример subFlow.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="step1">
<state name="step1">
<event name="next" handler="handler#next"/>
<state-transition name="next" to="end"/>
</state>
</flow>
Код обработчика:
public class Handler {
@Autowired
@Qualifier("workflowExecutor")
private EventContext context;
public Transition next() {
return Transition.goWithSave("next", "data", "title", "value");
}
public Transition toSubflow() {
return Transition.goWithSave("toSubflow", "data", "title", "value");
}
public Transition subflowCall() {
return Transition.goWithSave("toSubflow", "data", "title", "value");
}
public Transition onReturn() {
return Transition.go("next");
}
}
Пример работы:
Старт flow, переход на subflow и завершение subflow.
/workflow-gate?cmd=START&name=mainFlow
/workflow-gate?cmd=EVENT&name=toSubflow&pid=517dd795-3be8-477f-b32e-6b69eb99ce58
/workflow-gate?cmd=EVENT&name=next&pid=517dd795-3be8-477f-b32e-6b69eb99ce58
JSON-ответ:
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "517dd795-3be8-477f-b32e-6b69eb99ce58",
"flow": "mainFlow",
"state": "step2",
"history": [
{
"id": "d68ac044-925d-4997-9059-69af95bc1144",
"flow": "mainFlow",
"state": "step2",
"title": "",
"status": "ACTIVE"
},
{
"id": "e5835bf7-230f-480a-8d0d-ae853000e1ab",
"flow": "subFlow",
"state": "step1",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "747cf035-a657-4a10-a8ed-11bc924c7cfc",
"flow": "mainFlow",
"state": "toSubflow",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "ef104bb4-8b00-4bd2-8e65-40b66b610e8a",
"flow": "mainFlow",
"state": "step1",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Как видно, это шаг step2 главного flow.
Откат на toSubflow (повторный вызов subflow).
/workflow-gate?cmd=EVENT&name=next&pid=517dd795-3be8-477f-b32e-6b69eb99ce58
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "517dd795-3be8-477f-b32e-6b69eb99ce58",
"flow": "subFlow",
"state": "step1",
"output": "data",
"history": [
{
"id": "ac79db31-64ff-4d2f-a8f1-47589fe2bcc2",
"flow": "subFlow",
"state": "step1",
"title": "",
"status": "ACTIVE"
},
{
"id": "747cf035-a657-4a10-a8ed-11bc924c7cfc",
"flow": "mainFlow",
"state": "toSubflow",
"title": "title",
"value": "value",
"status": "ACTIVE"
},
{
"id": "ef104bb4-8b00-4bd2-8e65-40b66b610e8a",
"flow": "mainFlow",
"state": "step1",
"title": "title",
"value": "value",
"status": "ACTIVE"
}
]
}
}
Дополнительные возможности
При старте каждого flow в рамках процесса ему присваивается уникальный (в рамках процесса) идентификатор, который добавляется в каждую «хлебную крошку», создаваемую в рамках flow. Идентификатор в цепочке навигации возможно переопределить методом HistoryStep.setFlowId.
{
"success": true,
"body": {
"result": "SUCCESS",
"pid": "517dd795-3be8-477f-b32e-6b69eb99ce58",
"flow": "subFlow",
"state": "step1",
"output": "data",
"history": [
{
"id": "ac79db31-64ff-4d2f-a8f1-47589fe2bcc2",
"flow": "subFlow",
"state": "step1",
"title": "",
"status": "ACTIVE"
"flowId": 2
},
{
"id": "747cf035-a657-4a10-a8ed-11bc924c7cfc",
"flow": "mainFlow",
"state": "toSubflow",
"title": "title",
"value": "value",
"status": "ACTIVE"
"flowId": 1
},
{
"id": "ef104bb4-8b00-4bd2-8e65-40b66b610e8a",
"flow": "mainFlow",
"state": "step1",
"title": "title",
"value": "value",
"status": "ACTIVE"
"flowId": 1
}
]
}
}