Conditional Flow - Spring Batch Part 6

There can be muliple scenario where you want your jobs to proceed depending on the outcome of your steps.

For example, in case of our Corporate gifts shop, in case the automatic packaging of the gifts fails, we want some manual intervention to be done i.e. somebody should manually package the item. 
 
if packageStep fails
                        then manualPackageStep
                    else if packageStep executes successfully
                        then sendOutForDelivery


Using SpringBatch you can achieve it as follows:
Create you manualPackageStep, which is very similar to your packageStep, except that it does the packaging manually.
@Bean
public Step manualPackageStep() {
return this.stepBuilderFactory.get("manualPackageStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Manually packaging the gift to be delivered");
return RepeatStatus.FINISHED;
}
}).build();
}
Our packageStep is all set for failing:
// Toggle true/false to test rerunning failed jobs
private boolean throwException = true;
@Bean
public Step packageStep() {
return this.stepBuilderFactory.get("packageStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if(throwException){
throw new RuntimeException("Exception while Packaging");
}
System.out.println("Packaging the gift");
return RepeatStatus.FINISHED;
}
}).build();
}

Lets now modify the Job to reflect IF-ELSE IF loop for packaging:
@Bean
public Job packageJob(){
return this.jobBuilderFactory.get("giftShopJob").start(readOrderStep())
.next(packageStep())
.on("FAILED").to(manualPackageStep())
.from(packageStep())
.on("*").to(deliveryStep())
.end()
.build();
}
So if the packageStep status is FAILED, then the job will proceed to manualPackageStep. In case of any other status (*), the job will proceed to delivery Step.

Lets look at the output:
2020-07-19 18:32:52.782  INFO 16052 --- [           main] c.r.s.SpringbootdemoApplication          : Started SpringbootdemoApplication in 1.611 seconds (JVM running for 1.903)
2020-07-19 18:32:52.784  INFO 16052 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2020-07-19 18:32:52.850  INFO 16052 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=giftShopJob]] launched with the following parameters: [{}]
2020-07-19 18:32:52.898  INFO 16052 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [readOrderStep]
Order Received
2020-07-19 18:32:52.921  INFO 16052 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [readOrderStep] executed in 21ms
2020-07-19 18:32:52.938  INFO 16052 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [packageStep]
2020-07-19 18:32:52.951 ERROR 16052 --- [           main] o.s.batch.core.step.AbstractStep         : Encountered an error executing step packageStep in job giftShopJob

java.lang.RuntimeException: Exception while Packaging
at com.ricsr.springbootdemo.SpringbootdemoApplication$2.execute(SpringbootdemoApplication.java:57) ~[main/:na]
at ..........
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at com.ricsr.springbootdemo.SpringbootdemoApplication.main(SpringbootdemoApplication.java:99) ~[main/:na]

2020-07-19 18:32:52.956  INFO 16052 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [packageStep] executed in 18ms
2020-07-19 18:32:52.984  INFO 16052 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [manualPackageStep]
Manually packaging the gift to be delivered
2020-07-19 18:32:52.998  INFO 16052 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=giftShopJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 122ms
2020-07-19 18:32:53.001  INFO 16052 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-07-19 18:32:53.011  INFO 16052 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

BUILD SUCCESSFUL in 20s

Your packageJob is in ABANDONED Status in BATCH_STEP_EXECUTION 
But your job has executed successfully according to BATCH_JOB_EXECUTION  table 


You can have custom status as the output of your jobs, which will help you determine your next step.
In our example, lets consider that our clients may ask for Delivery or Pickup. There is a 50% chance of them asking for either. We can achieve that by using JobExecutionDecider
which will help in determining if the gift, once packaged should be delivered or made available for pickup.

Lets create a JobExecutionDecider, which Randomly determines if the package should be delivered or pickedup using java's Random class. We will add a Sysout to know that the flow is running fine.
package com.ricsr.springbootdemo;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;

import java.util.Random;

public class PickupOrDeliveryDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {

Random random = new Random();
int randomInt = random.nextInt(10);
String response = randomInt > 5 ? "DELIVER" : "PICKUP";
System.out.println("Customer choose: "+ response);
return new FlowExecutionStatus(response);
}
}
Let us also create the pickupStep Bean:
@Bean
public Step pickupStep() {
return this.stepBuilderFactory.get("pickupStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Package ready for pickup");
return RepeatStatus.FINISHED;
}
}).build();
}
Also, the JobExecutionDecider bean, which simple returns new instance of our PickupOrDeliveryDecider:
@Bean
public JobExecutionDecider pickupOrDeliveryDecider(){
return new PickupOrDeliveryDecider();
}

We need to modify our Jobs definition, the logic here should be:
if packageStep is successful,
                        use the pickUpOrDeliveryDecider to determing if its pickup? then pickupStep
                                                                                                      else if its delivery? then deliveryStep

which looks like this:
@Bean
public Job packageJob(){
return this.jobBuilderFactory.get("giftShopJob").start(readOrderStep())
.next(packageStep())
.on("FAILED").to(manualPackageStep())
.from(packageStep())
.on("*").to(pickupOrDeliveryDecider())
.on("DELIVER").to(deliveryStep())
.from(pickupOrDeliveryDecider())
.on("PICKUP").to(pickupStep())
.end()
.build();
}
Also, as per our requirement, the gift should be packaged, so lets modify the toggle:
// Toggle true/false to test rerunning failed jobs
private boolean throwException = false;
Lets run our application, 

2020-07-19 19:27:51.789  INFO 11680 --- [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: H2
2020-07-19 19:27:51.963  INFO 11680 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2020-07-19 19:27:52.083  INFO 11680 --- [           main] c.r.s.SpringbootdemoApplication          : Started SpringbootdemoApplication in 1.926 seconds (JVM running for 2.315)
2020-07-19 19:27:52.084  INFO 11680 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2020-07-19 19:27:52.147  INFO 11680 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=giftShopJob]] launched with the following parameters: [{}]
2020-07-19 19:27:52.179  INFO 11680 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [readOrderStep]
Order Received
2020-07-19 19:27:52.203  INFO 11680 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [readOrderStep] executed in 23ms
2020-07-19 19:27:52.218  INFO 11680 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [packageStep]
Packaging the gift
2020-07-19 19:27:52.226  INFO 11680 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [packageStep] executed in 8ms
Customer choose: PICKUP
2020-07-19 19:27:52.238  INFO 11680 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [pickupStep]
Package ready for pickup
2020-07-19 19:27:52.246  INFO 11680 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [pickupStep] executed in 8ms
2020-07-19 19:27:52.254  INFO 11680 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=giftShopJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 86ms
2020-07-19 19:27:52.259  INFO 11680 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-07-19 19:27:52.267  INFO 11680 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

If you rerun the application multiple times, you can see that sometimes the customer chooses delivery option and the deliveryStep is executed.


Note: I have dropped the Spring Job Repository tables just to able to rerun the application just to make it easy to rerun the Job

DROP TABLE BATCH_STEP_EXECUTION_CONTEXT ;
DROP TABLE BATCH_STEP_EXECUTION ;
DROP TABLE BATCH_JOB_INSTANCE ;
DROP TABLE BATCH_JOB_EXECUTION_PARAMS ;
DROP TABLE BATCH_JOB_EXECUTION_CONTEXT ;
DROP TABLE BATCH_JOB_EXECUTION ;

Comments

Popular posts from this blog

Writing your own ejabberd Module

npm ECONNREFUSED error