【问题标题】:JEE Batch Job Specification with many optional Steps带有许多可选步骤的 JEE 批处理作业规范
【发布时间】:2019-11-26 16:06:27
【问题描述】:

有没有办法使用 JSR 352 Batch API 实现以下逻辑? 我有一系列步骤,每个步骤都需要根据开始工作时已知的不同条件执行。 条件实体由外部系统提供。

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"));
    }
}

【问题讨论】:

  • 我正在使用 Websphere Liberty。我只是注意到这可能是相关的,因为规范说应该可以从一个决定转到另一个决定。
  • 是的,这是相关的。请针对 Open Liberty(我正在研究)提出一个问题:github.com/openliberty/open-liberty/issues。
  • 我没有意识到我们有一个规范声明来支持这应该是可能的概念(@cheng 通过引用这个...在 Decider javadoc (jakarta.ee/specifications/platform/8/apidocs/javax/batch/api/…) 并注意到“从决策过渡”的情况没有描述。如果它在规范中,我认为我们可以支持向 Open Liberty 添加功能的情况。我们应该仔细考虑关于 StepExecution 是否通过。
  • @ScottKurz 我已经添加了问题:github.com/OpenLiberty/open-liberty/issues/10102

标签: jakarta-ee batch-processing jsr352 java-batch jbatch


【解决方案1】:

看起来失败是由于一个决策在运行时将另一个决策作为其下一个执行点这一事实造成的。根据 JSR 352 规范第 8.5 节,它应该是受支持的用例:

一项工作可能包含任意数量的决策元素。决策要素 是作业级步骤、流程中“下一个”属性的目标, 分裂,或其他决定。

作为一种解决方法,您可以尝试使用包含相同条件和逻辑的传递批处理步骤。例如,

<step id="pass-through-step">
   <batchlet ref="PassThroughBatchlet"/>
   <next on="EXECUTE" to="step2"/>
   <next on="SKIP" to="decider2"/>
</step>

或者,如果您的某些条件逻辑可以通过包含转换元素的批处理步骤来实现,您可以取消这些决定。

【讨论】:

  • 嗨成。是的,这行得通(我在我的更新中为此添加了完整的 POC)。有点遗憾,这似乎是最好的选择,因为它实际上并没有产生好的 xml:/
  • 顺便说一句,我对规范也有点困惑......在第 8.6.1 节中,它没有显示添加 decisionId 作为下一个元素的可能性:
【解决方案2】:

@cheng 有一个很好的答案,这与您正在做的事情相比只是一个很小的变化(您只需将 Decider 基本上更改为 Batchlet)。

至少对我来说,考虑规范在这里为您提供的其他选项是一个有趣的问题。另一种方法是用一个决策者做出一个决策,其中注入了所有“isExecuteStepNN”道具,你可以在每一步之后调用它。该决策者通过了 StepExecution,因此您知道上一步是什么,并且您可以将其与“isExecute ...”道具结合起来,让决策者返回要执行的下一步的 id。

虽然这可能很聪明,但我认为 cheng 的回答是一个更简单的解决方法。我也认为规范应该考虑允许这样做。可能不支持这一点的原因是为了避免回答这个问题:“应该将什么 StepExecution(s) 传递给决定方法?”这似乎可以解决。

【讨论】:

  • 嗨,斯科特。我也有想法以这种方式实现逻辑。我认为 xml 也将开始看起来超级难看,因为我需要在每个 Step 元素之前使用“Decided”,并且决定出现的越早,可能的下一个值列表越长(基本上之后的每一步都可能是下一个)。
  • 或者是否可以在Decider中返回下一步要执行的步骤的ID?
  • 我已经单独提出了这个问题,以免混淆这个线程:stackoverflow.com/questions/59214372/…
  • 是的,同意 XML 会很丑……但它会在一个地方,而不是重复。我认为其他问题没有很好的答案,但感谢您提出来。
【解决方案3】:

我对该问题有另一种可能的解决方案,与其他建议的解决方案相比,它具有不同的缺点。

可以让 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>

Batchlet 如下所示:

@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());

    }
}

这个测试还没有真正测试预期的行为。我实际上想跳过第 2 步,但是使用当前的解决方案第 2 步确实会被执行,但没有做任何事情。我还没有添加功能来测试这个。

@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());
}

【讨论】:

  • 几个快速的想法: 1. 将控制流移动到您的(非决策者)步骤工件的逻辑中可能会导致难以将它们组合到其他作业中(通过 JSL /工作设计师)。如果这不是问题和/或 Java 工件的作者通常也在创作 XML,那么您可能会接受。 2.小​​心 JobOperator.getParameters() 这只会获取作业启动/提交传递的作业参数。它不查看其值可以(更灵活)来自作业参数和元素范围的作业属性等的作业属性。
【解决方案4】:

我刚刚找到了另一种可能的方法来解决生成更易于理解的 xml 文件的问题。它避免了重复 xml,不依赖于虚拟步骤,并且避免了将 if/else 逻辑移动到 Batchlet。 基本方法是创建一个决策,并在执行每个步骤后继续将控制权交还给该决策。 (显然同一个决定可以执行多次。)

<?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://stackoverflow.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>

决策者(请注意,我只是快速破解了 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"));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-22
    • 1970-01-01
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    • 2015-05-27
    相关资源
    最近更新 更多