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
Entire code available at git repo: https://github.com/ricsr/spring-batch-demo/tree/exercise_06
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();
}
// 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.
Entire code available at git repo: https://github.com/ricsr/spring-batch-demo/tree/exercise_06
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