Fluent Builder API

Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine

Чтобы создавать простые BPMN процессы, мы предоставили fluent builder API. С этим API вы можете легко создавать базовые процессы в несколько строк кода. В демо для быстрого начала работы с fluent API мы показываем, как создать довольно сложный процесс с пятью задачами и двумя шлюзами, использовав всего около 50 строк кода.

Fluent Builder API еще весьма далек от завершения, но уже предлагает вам следующие базовые элементы:

  • процесс

  • событие старта

  • исключающий шлюз

  • параллельный шлюз

  • скриптовая задача

  • сервисная задача

  • пользовательская задача

  • определение сигнального события

  • событие окончания

  • подпроцесс

Создание процесса с помощью Fluent Builder API

Чтобы создать пустой экземпляр модели с новым процессом, используется метод Bpmn.createProcess(). После этого вы можете добавить сколько угодно задач и шлюзов. В конце вы должны вызвать done(), чтобы вернуть сгенерированный экземпляр модели. Например, простой процесс с одной пользовательской задачей может быть создан вот так:

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .userTask()
  .endEvent()
  .done();

Чтобы добавить новый элемент, вам придется вызвать функцию, которая имеет такое же имя, как и добавляемый элемент. Дополнительно вы можете установить атрибуты последнего созданного элемента.

Например, давайте установим имя процесса и пометим его как выполняемый, а также дадим имя пользовательской задаче.

BpmnModelInstance modelInstance = Bpmn.createProcess()
    .name("Example process")
    .executable()
  .startEvent()
  .userTask()
    .name("Some work to do")
  .endEvent()
  .done();

Как вы можете видеть, последовательный процесс действительно очень простой и моделируется элементарным образом, но зачастую вам бывают нужны ветвления и параллельные пути выполнения, что тоже может быть сделано с помощью Fluent Builder API. Просто добавьте параллельный или исключающий шлюз и смоделируйте первый путь до конца события ил до следующего шлюза. После этого вызовите метод moveToLastGateway(), и вы вернетесь к последнему шлюзу, откуда вы сможете смоделировать следующий путь.

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .userTask()
  .parallelGateway()
    .scriptTask()
    .endEvent()
  .moveToLastGateway()
    .serviceTask()
    .endEvent()
  .done();

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

В нормальной ситуации вы захотели бы добавить условия на исходящие потоки исключающего шлюза, что тоже легко создать с помощью Fluent Builder API. Просто используйте метод condition() и дайте ему метку и выражение.

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .userTask()
  .exclusiveGateway()
  .name("What to do next?")
    .condition("Call an agent", "#{action = 'call'}")
    .scriptTask()
    .endEvent()
  .moveToLastGateway()
    .condition("Create a task", "#{action = 'task'}")
    .serviceTask()
    .endEvent()
  .done();

Если вы хотите использовать метод moveToLastGateway(), но у вас несколько входящих потоков последовательностей в вашей текущей позиции, вам придется использовать generic метод moveToNode с id шлюза. Это может произойти, например, если вы добавляете шлюз типа join к вашему процессу. Для этой цели, как и для циклов, мы добавили метод connectTo(elementId).

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .userTask()
  .parallelGateway("fork")
    .serviceTask()
    .parallelGateway("join")
  .moveToNode("fork")
    .userTask()
    .connectTo("join")
  .moveToNode("fork")
    .scriptTask()
    .connectTo("join")
  .endEvent()
  .done()

Этот пример создает процесс с тремя параллельными путями выполнения, которые все объединяются на втором шлюзе. Отметим, что первый вызов moveToNode не является необходимым, поскольку в этой точке объединяющий шлюз имеет только один входящий поток последовательности, но этот первый вызов был испооьзован для единообразия.

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .userTask()
  .id("question")
  .exclusiveGateway()
  .name("Everything fine?")
    .condition("yes", "#{fine}")
    .serviceTask()
    .userTask()
    .endEvent()
  .moveToLastGateway()
    .condition("no", "#{!fine}")
    .userTask()
    .connectTo("question")
  .done()

Этот пример создает параллельный шлюз с обратной петлей на втором пути выполнения.

Чтобы создать встроенный подпроцесс с помощью Fluent Builder API, вы можете добавить его напрямую при построении процесса или отсоединить его и создать элементы потока подпроцесса позднее.

// Directly define the subprocess
BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .subProcess()
    .camundaAsync()
    .embeddedSubProcess()
      .startEvent()
      .userTask()
      .endEvent()
    .subProcessDone()
  .serviceTask()
  .endEvent()
  .done();

// Detach the subprocess building
modelInstance = Bpmn.createProcess()
  .startEvent()
  .subProcess("subProcess")
  .serviceTask()
  .endEvent()
  .done();

SubProcess subProcess = (SubProcess) modelInstance.getModelElementById("subProcess");
subProcess.builder()
  .camundaAsync()
  .embeddedSubProcess()
    .startEvent()
    .userTask()
    .endEvent();

Нижеприведенный пример показывает, как создать определение события выбрасывания сигнала и задать полезную нагрузку, которую будет содержать этот сигнал. Используя camundaIn методы, можно задать, какие процессные переменные будут включены в полезную нагрузку сигнала, задать выражение, которое будет разрешено в экземплярах процессов, принимающих сигнал или объявить, что все процессные переменные в экземпляре подающего сигнал процесса должны передаваться. Можно также задать бизнес-ключ, который будет назначен экземплярам процессов, принимающих сигнал.

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .intermediateThrowEvent("throw")
    .signalEventDefinition("signal")
      .camundaInSourceTarget("source", "target1")
      .camundaInSourceExpressionTarget("${'sourceExpression'}", "target2")
      .camundaInAllVariables("all", true)
      .camundaInBusinessKey("aBusinessKey")
      .throwEventDefinitionDone()
  .endEvent()
  .done();

Расширение процесса с помощью Fluent Builder API

С помощью Fluent Builder API вы можете не только создавать процессы, но и расширять существующие.

Например, представьте себе процесс, содержащий параллельный шлюз с id равным значению gateway. Теперь вы хотите добавить к нему другой путь выполнения ради новой сервисной задачи, которая должна выполняться каждый раз.

BpmnModelInstance modelInstance = Bpmn.readModelFromFile(new File("PATH/TO/MODEL.bpmn"));
ParallelGateway gateway = (ParallelGateway) modelInstance.getModelElementById("gateway");

gateway.builder()
  .serviceTask()
    .name("New task")
  .endEvent();

Другой сценарий использования — это вставить новые задачи между существующими элементами. Представьте себе процесс, содержащий пользовательскую задачу с id, равным task1, за которой следует сервисная задача. Теперь вы хотите добавить скриптовую задачу и пользовательскую задачу между этими двумя.

BpmnModelInstance modelInstance = Bpmn.readModelFromFile(new File("PATH/TO/MODEL.bpmn"));
UserTask userTask = (UserTask) modelInstance.getModelElementById("task1");
SequenceFlow outgoingSequenceFlow = userTask.getOutgoing().iterator().next();
FlowNode serviceTask = outgoingSequenceFlow.getTarget();
userTask.getOutgoing().remove(outgoingSequenceFlow);

userTask.builder()
  .scriptTask()
  .userTask()
  .connectTo(serviceTask.getId());

Распространенные паттерны моделей

Контроль за границами транзакций

Транзакционные границы процесса, созданного при помощи Fluent Builder API, могут контролироваться с использованием методов camundaAsyncBefore() и camundaAsyncAfter(), предлагаемых для различных конструктов процесса.

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .serviceTask("servicetask")
    .camundaAsyncBefore()
  .userTask("task")
    .camundaAsyncAfter()
  .done();

Сервисная задача в приведенном выше примере получить транзакционные границы до своего выполнения, а пользовательская задача получит транзакционные границы после своего завершения.

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

BpmnModelInstance modelInstance = Bpmn.createProcess()
  .startEvent()
  .serviceTask("servicetask")
    .camundaAsyncBefore() // multi-instance body
    .multiInstance()
      .camundaAsyncBefore() // every instance
      .parallel()
    .multiInstanceDone()
  .endEvent()
  .done();

Генерация развязки диаграммы

Для рендеринга процесса необходимы элементы BPMN-диаграммы. Fluent Builder генерирует BPMN Shapes (геометрические формы) и BPMN Edges (края, грани) и размещает их автоматически для использования в узлах потоков и в потоках последовательностей.

final BpmnModelInstance myProcess = Bpmn.createExecutableProcess("process-payments")
      .startEvent()
      .serviceTask()
          .name("Process Payment")
      .endEvent()
      .done();

System.out.println(Bpmn.convertToString(myProcess));

Этот пример создает BPMN, содержащую как семантические элементы (например, сервисную задачу), так и элементы диаграммы:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_dfb1f18e-6034-448e-abae-0eb2f41469da" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

  <!-- Generated BPMN Semantic Elements -->
  <process id="process-payments" isExecutable="true">
    <startEvent id="startEvent_2b0abd37-75a9-47dd-9838-63f1390d7515">
      <outgoing>sequenceFlow_b1eec5b5-889d-4e75-854d-59768fbdc8a2</outgoing>
    </startEvent>
    <serviceTask id="serviceTask_f4c2413f-5b26-49e8-b71c-2603e469ce09" name="Process Payment">
      <incoming>sequenceFlow_b1eec5b5-889d-4e75-854d-59768fbdc8a2</incoming>
      <outgoing>sequenceFlow_5839394a-c0c2-4a5b-aa81-9412f169cc35</outgoing>
    </serviceTask>
    <sequenceFlow id="sequenceFlow_b1eec5b5-889d-4e75-854d-59768fbdc8a2" sourceRef="startEvent_2b0abd37-75a9-47dd-9838-63f1390d7515" targetRef="serviceTask_f4c2413f-5b26-49e8-b71c-2603e469ce09"/>
    <endEvent id="endEvent_8087f927-a53b-4126-95fc-c057736f3b73">
      <incoming>sequenceFlow_5839394a-c0c2-4a5b-aa81-9412f169cc35</incoming>
    </endEvent>
    <sequenceFlow id="sequenceFlow_5839394a-c0c2-4a5b-aa81-9412f169cc35" sourceRef="serviceTask_f4c2413f-5b26-49e8-b71c-2603e469ce09" targetRef="endEvent_8087f927-a53b-4126-95fc-c057736f3b73"/>
  </process>

  <!-- Generated Diagram Elements -->
  <bpmndi:BPMNDiagram id="BPMNDiagram_5b66dfb7-097b-4610-9681-43abb3ff97da">
    <bpmndi:BPMNPlane bpmnElement="process-payments" id="BPMNPlane_ad88b4cf-9d7a-4b86-8386-f8db23ff388d">
      <bpmndi:BPMNShape bpmnElement="startEvent_2b0abd37-75a9-47dd-9838-63f1390d7515" id="BPMNShape_d6c4e3c5-150c-43f7-adf8-1d388f466a69">
        <dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="serviceTask_f4c2413f-5b26-49e8-b71c-2603e469ce09" id="BPMNShape_51006773-13df-4327-a4cf-a5952c39e86a">
        <dc:Bounds height="80.0" width="100.0" x="186.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_b1eec5b5-889d-4e75-854d-59768fbdc8a2" id="BPMNEdge_fb360594-8863-4d5d-b515-49e02a88d55d">
        <di:waypoint x="136.0" y="118.0"/>
        <di:waypoint x="186.0" y="118.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape bpmnElement="endEvent_8087f927-a53b-4126-95fc-c057736f3b73" id="BPMNShape_23930820-5507-45a0-8630-b5e45ee8dd4d">
        <dc:Bounds height="36.0" width="36.0" x="336.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_5839394a-c0c2-4a5b-aa81-9412f169cc35" id="BPMNEdge_07ed502e-069f-42a0-bd1b-fed0d68efbda">
        <di:waypoint x="286.0" y="118.0"/>
        <di:waypoint x="336.0" y="118.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Поведение по умолчанию состоит в том, что каждый вновь добавляемый элемент потока будет размещен рядом с предыдущим элементом потока.

Когда элементы потока добавляются к встроенному подпроцессу, тогда у этого подпроцесса автоматически меняется размер, когда достигается его граница. Поэтому, мы рекомендуем сначала добавить новые элементы к подпроцессу, и затем создавать новые элементы. Иначе это можнт привести к накладыванию элементов в диаграмме друг на друга.

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

Лицензия и атрибуция

Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .

Оригинал документации: https://docs.camunda.org