Статьи

Как использовать временные переменные в Activiti

Функция, которая была запрошена совсем немного — временные переменные — появилась в бета-версии Activiti v6, которую мы выпустили вчера . В этом посте я покажу вам пример того, как временные переменные могут использоваться, чтобы охватить некоторые сложные варианты использования, которые раньше были (или не оптимальны) невозможны.

До сих пор все переменные в Activiti были постоянными . Это означает, что переменная и значение хранятся в хранилище данных, а исторические данные аудита сохраняются. С другой стороны, временные переменные действуют и ведут себя как обычные переменные, но они не сохраняются. Помимо того, что оно не сохраняется, для переходных переменных характерно следующее:

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

Более подробную информацию о переходных переменных и API можно найти в документации .

пример

Определение процесса, которое мы будем использовать для демонстрации некоторых битов переходных переменных, показано ниже. Это довольно простой процесс: идея в том, что мы будем запрашивать у пользователя некоторые вещи, такие как ключевое слово и язык, и использовать его для вызова API GitHub. В случае успеха результаты показываются пользователю. Для этого легко написать пользовательский интерфейс (или использовать новые формы в приложении Beta3 angularJS ), но в этом посте мы сосредоточимся только на коде.

BPMN 2.0 xml и код можно найти в этом репозитории Github: https://github.com/jbarrez/transient-vars-example

Скриншот-от-2016-09-01-114450

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

1
2
3
4
5
6
repositoryService.createDeployment().addClasspathResource("process.bpmn20.xml").deploy();
 
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("keyWord", "workflow");
variables.put("language", "java");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("githubsearch", variables);

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

Первый выполняемый шаг — это шаг «выполнить HTTP-вызов», который представляет собой задачу службы с делегатом Java:

1
<serviceTask name="Execute HTTP call" activiti:class="org.activiti.ExecuteHttpCallDelegate"></serviceTask>

Java-код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ExecuteHttpCallDelegate implements JavaDelegate {
 
    public void execute(DelegateExecution execution) {
 
        String keyword = (String) execution.getVariable("keyWord");
        String language = (String) execution.getVariable("language");
 
        String url = "https://api.github.com/search/repositories?q=%s+language:%s&sort=starsℴ=desc";
        url = String.format(url, keyword, language);
        HttpGet httpget = new HttpGet(url);
 
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            CloseableHttpResponse response = httpclient.execute(httpget);
 
            execution.setTransientVariable("response", IOUtils.toString(response.getEntity().getContent(), "UTF-8"));
            execution.setTransientVariable("responseStatus", response.getStatusLine().getStatusCode());
 
            response.close();
 
        } catch (Exception e) {
            e.printStackTrace();
        }
 
    }
 
}

Здесь мы выполняем простую HTTP-процедуру с использованием API GitHub, используя переменные «ключевое слово» и «язык», которые мы передали при запуске экземпляра процесса. Особое здесь в строке 16 и 17, что мы храним ответ и статус ответа в переходных переменных (это вызов setTransientVariable () ). Причины выбора переходных переменных здесь

  • Ответ json от Github API очень велик. Конечно, его можно хранить постоянным способом, но это не скажется на производительности.
  • С точки зрения аудита весь ответ имеет очень мало значения. Мы извлечем важные биты позже из этого ответа, и они будут сохранены в исторических данных.

После получения ответа и сохранения его во временной переменной мы передаем исключительный шлюз. Последовательность потока выглядит следующим образом:

1
2
3
4
5
6
<sequenceFlow ... >
  <extensionElements>
    <activiti:executionListener event="take" class="org.activiti.ProcessResponseExecutionListener"></activiti:executionListener>
  </extensionElements>
  <conditionExpression xsi:type="tFormalExpression"><![CDATA[${responseStatus == 200}]]></conditionExpression>
</sequenceFlow>

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ProcessResponseExecutionListener implements ExecutionListener {
 
    private ObjectMapper objectMapper = new ObjectMapper();
 
    public void notify(DelegateExecution execution) {
 
        List<String> searchResults = new ArrayList<String>();
 
        String response = (String) execution.getVariable("response");
        try {
            JsonNode jsonNode = objectMapper.readTree(response);
            JsonNode itemsNode = jsonNode.get("items");
            if (itemsNode != null && itemsNode.isArray()) {
                for (JsonNode itemNode : (ArrayNode) itemsNode) {
                    String url = itemNode.get("html_url").asText();
                    searchResults.add(url);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        execution.setTransientVariable("searchResults", searchResults);
    }
 
}

Причина сохранения списка в качестве временной переменной является важной. Как вы можете видеть на диаграмме, следует подпроцесс с несколькими экземплярами. Подпроцесс часто принимает переменную коллекции для создания экземпляров. До сих пор это была постоянная переменная в виде сериализованного с помощью Java ArrayList. Мне никогда не нравилось это (я всегда использовал DelegateExpressions с бобом, если мне приходилось делать это раньше). Это всегда немного беспокоило меня. Теперь массив данных является временным и не будет храниться в хранилище данных:

1
2
3
<subProcess name="subProcess">
    <multiInstanceLoopCharacteristics isSequential="false"
          activiti:collection="searchResults" activiti:elementVariable="searchResult" />

Обратите внимание, что указанная выше переменная searchResult будет постоянной.

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

Если вы запустите экземпляр процесса, например, так:

1
2
3
4
5
6
7
8
9
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("keyWord", "workflow");
variables.put("language", "java");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("githubsearch", variables);
 
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
for (Task task : tasks) {
  System.out.println("Current task : " + task.getName());
}

Что дает в качестве примера вывод (ограничен 5 результатами):

Текущая задача: просмотреть результат https://github.com/Activiti/Activiti
Текущая задача: просмотреть результат https://github.com/twitter/ambrose
Текущая задача: просмотреть результат https://github.com/azkaban/azkaban
Текущая задача: просмотреть результат https://github.com/romannurik/AndroidDesignPreview
Текущая задача: просмотреть результат https://github.com/spring-projects/spring-xd

Теперь пользователь может посмотреть детали каждого из результатов и продолжить процесс.

Последние слова

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