Сценарии использования цепочки навигации с примерами реализации#

Существует пять вариантов использования цепочки навигации:

  • Линейный.

  • Фантомный шаг.

  • Редактирование.

  • «Перепрыгивание» шага.

  • Уход в subflow.

Далее каждый сценарий будет подробно рассмотрен.

Линейный#

workflow_bc1

Это самый простой (и достаточно редкий) вариант — движение происходит по 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-модель и сохраняет шаг вместе с данными.

Пример работы:

  1. Старт.

/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"
      }
    ]
  }
}
  1. Переход на 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"
      }
    ]
  }
}
  1. Переход на 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"
      }
    ]
  }
}
  1. Возвращение на 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_bc2

Фантомный шаг — это промежуточный шаг, исчезающий после перехода с него.

Это стандартное поведение Workflow. Нужно просто использовать метод go() для создания Transition. Тогда текущий (head) шаг не будет сохранен.

Редактирование#

workflow_bc3

Редактирование — это ситуация, когда не отходя от какого-либо процесса, делается пара шагов, а затем возвращение назад. Например, при просмотре некого списка, осуществляется переход на редактирование записи списка, после чего происходит возврат назад.

Пример 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");
    }
}

Пример работы:

  1. Старт.

/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"
      }
    ]
  }
}
  1. Переход к редактированию.

/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"
      }
    ]
  }
}
  1. Редактирование.

/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"
      }
    ]
  }
}
  1. Завершение редактирования.

/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-ответа, активен только текущий шаг.

«Перепрыгивание» шага#

workflow_bc4

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

Пример 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.

Пример работы:

  1. Старт.

/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"
      }
    ]
  }
}
  1. Пропуск одного шага.

/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#

workflow_bc5

Данный сценарий представляет собой ситуацию, когда происходит уход в 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");
    }

}

Пример работы:

  1. Старт 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.

  1. Откат на 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
      }
    ]
  }
}