Спецификация пакетного задания JEE с множеством дополнительных шагов
Есть ли способ достичь следующей логики с помощью JSR 352 Batch API? У меня есть ряд шагов, каждый из которых должен быть выполнен в зависимости от различных условий, известных при запуске работы. Условия Entity предоставляется внешней системой.
public List<Steps> createStepSequence(ConditionsEntity conditions) {
if (conditions.isStep1Enabled()) {
steps.add(step1)
}
if (conditions.isStep2Enabled()) {
steps.add(step2)
}
if (conditions.isStep3Enabled()) {
steps.add(step3)
}
//many more ifs
return steps;
}
Моя первая попытка не удалась из-за: com.ibm.jbatch.container.exception.BatchContainerRuntimeException: решение не может предшествовать другому решению. Я добавляю сюда НЕУДАЧНЫЙ код
<?xml version="1.0" encoding="UTF-8"?>
<job id="myJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd" version="1.0">
<properties>
<property name="isExecuteStep2" value="false"/>
<property name="isExecuteStep3" value="false"/>
</properties>
<step id="step1" next="decider1">
<batchlet ref="myBatchlet1"/>
</step>
<decision id="decider1" ref="SkipNextStepDecider">
<properties>
<property name="isExecuteNextStep" value="#{jobProperties['isExecuteStep2']}"/>
</properties>
<next on="EXECUTE" to="step2"/>
<next on="SKIP" to="decider2"/>
</decision>
<step id="step2">
<batchlet ref="myBatchlet2"/>
</step>
<decision id="decider2" ref="SkipNextStepDecider">
<properties>
<property name="isExecuteNextStep" value="#{jobProperties['isExecuteStep3']}"/>
</properties>
<next on="EXECUTE" to="step3"/>
<end on="SKIP"/>
</decision>
<step id="step3">
<batchlet ref="myBatchlet3"/>
</step>
</job>
@Named
public class SkipNextStepDecider implements Decider {
@Inject
@BatchProperty
private String isExecuteNextStep;
@Override
public String decide(StepExecution[] ses) throws Exception {
if (isExecuteNextStep.equalsIgnoreCase("true")) {
return "EXECUTE";
} else {
return "SKIP";
}
}
}
ОБНОВЛЕНИЕ Я реализовал следующее предлагаемое решение с помощью passThroughStep. Он работает правильно, но мне все равно хотелось бы избежать всего этого дублирования кода.
<?xml version="1.0" encoding="UTF-8"?>
<job id="decisionpoc" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<step id="dummy0" next="decider1">
<batchlet ref="dummyBatchlet"/>
</step>
<decision id="decider1" ref="skipNextStepDecider">
<properties>
<property name="condition" value="isExecuteStep1"/>
</properties>
<next on="EXECUTE" to="step1"/>
<next on="SKIP" to="dummy1"/>
</decision>
<step id="step1" next="decider2">
<batchlet ref="myBatchlet1"/>
</step>
<step id="dummy1" next="decider2">
<batchlet ref="dummyBatchlet"/>
</step>
<decision id="decider2" ref="skipNextStepDecider">
<properties>
<property name="condition" value="isExecuteStep2"/>
</properties>
<next on="EXECUTE" to="step2"/>
<next on="SKIP" to="dummy2"/>
</decision>
<step id="step2">
<batchlet ref="myBatchlet2"/>
</step>
<step id="dummy2" next="decider3">
<batchlet ref="dummyBatchlet"/>
</step>
<decision id="decider3" ref="skipNextStepDecider">
<properties>
<property name="condition" value="isExecuteStep3"/>
</properties>
<next on="EXECUTE" to="step3"/>
<end on="SKIP"/>
</decision>
<step id="step3">
<batchlet ref="myBatchlet3"/>
</step>
</job>
Решающий
@Named
public class SkipNextStepDecider implements Decider {
@Inject
@BatchProperty
private String condition;
@Inject
private JobContext jobContext;
@Override
public String decide(StepExecution[] ses) throws Exception {
Properties parameters = getParameters();
String isExecuteNextStep = parameters.getProperty(condition);
if (isExecuteNextStep.equalsIgnoreCase("true")) {
return "EXECUTE";
} else {
return "SKIP";
}
}
private Properties getParameters() {
JobOperator operator = getJobOperator();
return operator.getParameters(jobContext.getExecutionId());
}
}
Мой тест
public class DecisionPOCTest extends AbstractBatchLOT {
@Test
public void testProcess() throws Exception {
JobOperator jobOperator = getJobOperator();
Properties properties = new Properties();
properties.setProperty("isExecuteStep1", "true");
properties.setProperty("isExecuteStep2", "false");
properties.setProperty("isExecuteStep3", "true");
Long executionId = jobOperator.start("poc/decisionPOC", properties);
JobExecution jobExecution = jobOperator.getJobExecution(executionId);
jobExecution = BatchTestHelper.keepTestAlive(jobExecution);
List<StepExecution> stepExecutions = jobOperator.getStepExecutions(executionId);
List<String> executedSteps = new ArrayList<>();
for (StepExecution stepExecution : stepExecutions) {
executedSteps.add(stepExecution.getStepName());
}
assertEquals(COMPLETED, jobExecution.getBatchStatus());
assertEquals(4, stepExecutions.size());
assertArrayEquals(new String[]{"dummy0", "step1", "dummy2", "step3"}, executedSteps.toArray());
assertFalse(executedSteps.contains("step2"));
}
}
4 ответа
Похоже, что сбой был вызван тем, что одно решение имело другое решение в качестве следующей точки выполнения во время выполнения. Согласно спецификации JSR 352, раздел 8.5, это должен быть поддерживаемый вариант использования:
Работа может содержать любое количество элементов решения. Элемент решения является целью атрибута "следующий" из этапа, потока, разделения или другого решения на уровне задания.
В качестве обходного пути вы можете попробовать использовать сквозной пакетный шаг, содержащий то же условие и логику. Например,
<step id="pass-through-step">
<batchlet ref="PassThroughBatchlet"/>
<next on="EXECUTE" to="step2"/>
<next on="SKIP" to="decider2"/>
</step>
Или, если часть вашей условной логики может быть достигнута с помощью пакетного шага, содержащего элементы перехода, вы можете отказаться от этих решений.
У меня есть другое возможное решение проблемы, которое имеет другие недостатки, чем другие предлагаемые решения.
Можно позволить Step сам решать, нужно ли ему что-то выполнять или нет.
XML выглядит намного аккуратнее:
<?xml version="1.0" encoding="UTF-8"?>
<job id="decisionpoc" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<step id="step1" next="step2">
<batchlet ref="myBatchletWithDecision1">
<properties>
<property name="condition" value="isExecuteStep1"/>
</properties>
</batchlet>
</step>
<step id="step2" next="step3">
<batchlet ref="myBatchletWithDecision2">
<properties>
<property name="condition" value="isExecuteStep2"/>
</properties>
</batchlet>
</step>
<step id="step3">
<batchlet ref="myBatchletWithDecision3">
<properties>
<property name="condition" value="isExecuteStep3"/>
</properties>
</batchlet>
</step>
</job>
Пакетный пакет будет выглядеть следующим образом:
@Named
public class MyBatchletWithDecision1 extends AbstractBatchlet {
@Inject
@BatchProperty
private String condition;
@Inject
private JobContext jobContext;
@Override
public String process() {
Properties parameters = getParameters();
String isExecuteStep = parameters.getProperty(condition);
if (isExecuteStep.equalsIgnoreCase("true")) {
System.out.println("Running inside a batchlet 1");
} else {
//TODO somehow log that the step was skipped
}
return "COMPLETED";
}
private Properties getParameters() {
JobOperator operator = getJobOperator();
return operator.getParameters(jobContext.getExecutionId());
}
}
Этот тест пока что не совсем проверяет ожидаемое поведение. Я действительно хочу пропустить Step2, но с текущим решением step2 выполняется, но ничего не делает. Я еще не добавил функциональности для проверки этого.
@Test
public void testProcess() throws Exception {
JobOperator jobOperator = getJobOperator();
Properties properties = new Properties();
properties.setProperty("isExecuteStep1", "true");
properties.setProperty("isExecuteStep2", "false");
properties.setProperty("isExecuteStep3", "true");
Long executionId = jobOperator.start("poc/decisionWithoutDeciderPOC", properties);
JobExecution jobExecution = jobOperator.getJobExecution(executionId);
jobExecution = BatchTestHelper.keepTestAlive(jobExecution);
List<StepExecution> stepExecutions = jobOperator.getStepExecutions(executionId);
List<String> executedSteps = new ArrayList<>();
for (StepExecution stepExecution : stepExecutions) {
executedSteps.add(stepExecution.getStepName());
}
assertEquals(COMPLETED, jobExecution.getBatchStatus());
assertEquals(3, stepExecutions.size());
assertArrayEquals(new String[]{"step1", "step2", "step3"}, executedSteps.toArray());
}
@cheng имеет хороший ответ, который будет очень небольшим изменением по сравнению с тем, что вы делаете (вам просто нужно изменить свой Decider на Batchlet в основном).
По крайней мере, для меня это интересный вопрос - подумать о том, какие еще параметры предлагает вам здесь спецификация. Другой вариант - иметь одно решение с Decider со ВСЕМИ пропсами isExecuteStepNN, которые можно вызывать после каждого шага. Этому Decider передается StepExecution, чтобы вы знали, что такое предыдущий шаг, и вы могли бы объединить его с реквизитами "isExecute...", чтобы Decider вернул идентификатор следующего шага для выполнения.
Хотя это может быть умно, я думаю, что ответ Ченга - более простой обходной путь. Я также думаю, что спецификации следует рассмотреть возможность этого. Вероятно, причина того, что это не поддерживается, состоит в том, чтобы избежать ответа на вопрос: "Какие StepExecution(s) следует передать методу решения?" что кажется разрешимым.
Я только что нашел еще один возможный способ решения проблемы, который создает гораздо более простой для понимания XML-файл. Он избегает дублирования xml, не полагается на фиктивные шаги и избегает необходимости перемещать логику if/else в пакетный пакет. Базовый подход состоит в том, чтобы создать одно решение и продолжать передавать управление этому решению после каждого выполняемого шага. (Очевидно, одно и то же решение может выполняться несколько раз.)
<?xml version="1.0" encoding="UTF-8"?>
<job id="decisionpoc" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<!-- This dummy step is needed because it's not possible to start with a decision-->
<step id="dummy0" next="decider">
<batchlet ref="dummyBatchlet"/>
</step>
<decision id="decider" ref="nextStepDecider">
<properties>
<property name="condition" value="isExecuteSteps"/>
</properties>
<!-- Need to list all steps, see https://stackru.com/questions/59214372/jsr352-decide-next-step-based-on-return-parameter-from-decider-->
<next on="STEP1" to="step1"/>
<next on="STEP2" to="step2"/>
<next on="STEP3" to="step3"/>
<end on="SKIP"/>
</decision>
<step id="step1" next="decider">
<batchlet ref="myBatchlet1"/>
</step>
<step id="step2" next="decider">
<batchlet ref="myBatchlet2"/>
</step>
<step id="step3">
<batchlet ref="myBatchlet3"/>
</step>
</job>
Decider (обратите внимание, я просто быстро взломал логику POC, не используйте этот код напрямую):
@Named
public class NextStepDecider implements Decider {
@Inject
@BatchProperty
private String condition;
@Inject
private JobContext jobContext;
@Override
public String decide(StepExecution[] ses) throws Exception {
//FIXME: very hacky code in this method
if (ses.length != 1) {
// Decider not reached by transitioning from a step
return "ERROR";
}
Properties parameters = getParameters();
String executeSteps = parameters.getProperty(condition);
String[] steps = executeSteps.split(",");
int start = 0;
//advance start index to the next step based on the previous step that was executed
String previousStepName = ses[0].getStepName();
if (previousStepName.startsWith("step")) {
start = convertCharToInt(previousStepName);
}
//Loop through the remaining steps until we find a step that has its executeStep property set to true
for (int i = start; i < steps.length; i++) {
if (steps[i].equalsIgnoreCase("true")) {
return "STEP" + (i + 1);
}
}
return "SKIP";
}
private Properties getParameters() {
JobOperator operator = getJobOperator();
return operator.getParameters(jobContext.getExecutionId());
}
private int convertCharToInt(String previousStepName) {
return previousStepName.charAt(previousStepName.length()-1) - '0';
}
}
Тест:
@Test
public void testProcess() throws Exception {
JobOperator jobOperator = getJobOperator();
Properties properties = new Properties();
properties.setProperty("isExecuteSteps", "true,false,true");
Long executionId = jobOperator.start("poc/decisionWithDeciderPOC", properties);
JobExecution jobExecution = jobOperator.getJobExecution(executionId);
jobExecution = BatchTestHelper.keepTestAlive(jobExecution);
List<StepExecution> stepExecutions = jobOperator.getStepExecutions(executionId);
List<String> executedSteps = new ArrayList<>();
for (StepExecution stepExecution : stepExecutions) {
executedSteps.add(stepExecution.getStepName());
}
assertEquals(COMPLETED, jobExecution.getBatchStatus());
assertEquals(3, stepExecutions.size());
assertArrayEquals(new String[]{"dummy0", "step1", "step3"}, executedSteps.toArray());
assertFalse(executedSteps.contains("step2"));
}