1
votes

I am having problems with a spring batch flow that does the following (simplified)

  1. execute a step (always)
  2. at runtime decide on what to do next (jobexecutiondecider)
  3. if the decider determines to "continue"-> execute a more complex innerflow
  4. if the decider determines to "completed" -> do not execute the innerflow but simply complete the job

Given the following MVE:

@Configuration
@EnableBatchProcessing
public class BatchExample {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job exampleJob() {
        final FlowBuilder<Flow> innerFlowBuilder = new FlowBuilder<>("innerFlow");
        final Flow innerFlow = innerFlowBuilder
            .start(dummyStep("first innerflow step"))
            .next(dummyStep("second innerflow step"))
            .next(dummyStep("last innerflow step"))
            .build();

        final FlowBuilder<Flow> outerFlowBuilder = new FlowBuilder<>("outerFlow");
        final Flow outerFlow = outerFlowBuilder
            .start(dummyStep("always execute me"))
            .next(decide()).on("CONTINUE").to(innerFlow)
            .from(decide()).on("COMPLETED").end("COMPLETED")
            .build();

        return jobBuilderFactory.get("exampleJob")
            .start(outerFlow)
            .end()
            .build();
    }

    private Step dummyStep(String arg) {
        return stepBuilderFactory.get("step_" + arg)
            .tasklet(dummyTasklet(arg))
            .build();
    }

    private Tasklet dummyTasklet(String arg) {
        return new DummyTasklet(arg);
    }

    private DummyDecider decide() {
        return new DummyDecider();
    }

    class DummyTasklet implements Tasklet {

        private final String arg;

        DummyTasklet(String arg) {
            this.arg = arg;
        }

        @Override
        public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
            System.out.println("hello from dummy tasklet: " + arg);
            return RepeatStatus.FINISHED;
        }
    }

    class DummyDecider implements JobExecutionDecider {

        @Override
        public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
            final Random random = new Random();
            final int i = random.nextInt();

            if (i % 2 == 0) {
                return new FlowExecutionStatus("CONTINUE");
            } else {
                return FlowExecutionStatus.COMPLETED;
            }
        }
    }    
}

When executing the code, the "continue" flow goes well, but the "completed" flow always exit with a failed job status.

 Job: [FlowJob: [name=exampleJob]] completed with the following
 parameters: [{}] and the following status: [FAILED]

How do I make the job finish with status COMPLETED? In other words, what have I done wrong with the coding of flow?

The code can be run with a spring boot application

@SpringBootApplication
public class FlowApplication implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(FlowApplication.class, args);
    }

    @Autowired
    private Job exampleJob;
    @Autowired
    private JobLauncher jobLauncher;


    @Override
    public void run(ApplicationArguments args) throws Exception {
        jobLauncher.run(exampleJob, new JobParameters());
    }
}

Edit

When implementing the suggested answer:

final Flow outerFlow = outerFlowBuilder
            .start(dummyStep("always execute me"))
            .on("*").to(decide())
            .from(decide()).on("CONTINUE").to(innerFlow)
            .from(decide()).on("COMPLETED").end("COMPLETED")
            .build();

And forcing the decider to "CONTINUE":

class DummyDecider implements JobExecutionDecider {
        @Override
        public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
            return new FlowExecutionStatus("CONTINUE");
        }
    }

the innerflow is not being started anymore:

Executing step: [step_always execute me]
hello from dummy tasklet: always execute me
Job: [FlowJob: [name=exampleJob]] completed with the following parameters: [{}] and the following status: [FAILED]
Job: [FlowJob: [name=exampleJob]] launched with the following parameters: [{}]
Step already complete or not restartable, so no action to execute: StepExecution: id=0, version=3, name=step_always execute me, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
Job: [FlowJob: [name=exampleJob]] completed with the following parameters: [{}] and the following status: [FAILED]
1

1 Answers

2
votes

Your outerFlow should be defined as follows:

final Flow outerFlow = outerFlowBuilder
        .start(dummyStep("always execute me"))
        .on("*").to(decide())
        .from(decide()).on("CONTINUE").to(innerFlow)
        .from(decide()).on("COMPLETED").end("COMPLETED")
        .build();

I tested this with your example (Thank you for the MVE!) and the job does not fail when the decider returns COMPLETED.

The reason is that without routing all outcomes of the dummyStep("always execute me") step to the decider with .on("*").to(decide()), the flow definition is not correct.

Hope this helps.