Skip to content

A simple task management framework like db-scheduler or quartz, but build with spring for spring boot.

License

Notifications You must be signed in to change notification settings

sterlp/spring-persistent-tasks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java CI with Maven

Spring Persistent Tasks

A simple task management framework designed to queue and execute asynchronous tasks with support for database persistence and a user-friendly interface. It can be used to implement scheduling patterns or outbound patterns.

Focus is the usage with spring boot and JPA.

Secondary goal is to support Poor mans Workflow

DBs for storage

Tested in the pipeline

  • H2
  • azure-sql-edge (MSSQL)
  • PostgreSQL
  • MariaDB

History

Supported in theory

  • MSSQL, as azure-sql-edge is tested

Not supported

  • mySQL: sequences are not supported

Setup and Run a Task

Maven

<dependency>
    <groupId>org.sterl.spring</groupId>
    <artifactId>spring-persistent-tasks-core</artifactId>
    <version>1.x.x</version>
</dependency>

Setup Spring

@SpringBootApplication
@EnableSpringPersistentTasks
public class ExampleApplication {

Setup a spring persistent task

As a class

@Component(BuildVehicleTask.NAME)
@RequiredArgsConstructor
@Slf4j
public class BuildVehicleTask implements PersistentTask<Vehicle> {

    private static final String NAME = "buildVehicleTask";
    public static final TaskId<Vehicle> ID = new TaskId<>(NAME);

    private final VehicleRepository vehicleRepository;

    @Override
    public void accept(Vehicle vehicle) {
        // do stuff
        // save
        vehicleRepository.save(vehicle);
    }
    // OPTIONAL
    @Override
    public RetryStrategy retryStrategy() {
        // run 5 times, multiply the execution count with 4, add the result in HOURS to now.
        return new MultiplicativeRetryStrategy(5, ChronoUnit.HOURS, 4)
    }
    // OPTIONAL
    // if the task in accept requires a DB transaction, join them together with the framework
    // if true the TransactionTemplate is used. Set here any timeouts.
    @Override
    public boolean isTransactional() {
        return true;
    }
}

Consider setting a timeout to the TransactionTemplate:

@Bean
TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
    TransactionTemplate template = new TransactionTemplate(transactionManager);
    template.setTimeout(10);
    return template;
}

As a closure

Simple task will use defaults:

  • Not a transactional task, e.g. HTTP calls
  • 4 executions, one regular and 3 retries, linear
  • using minutes with an offset of 1 which is added to now
@Bean
PersistentTask<Vehicle> task1(VehicleHttpConnector vehicleHttpConnector) {
    return v -> vehicleHttpConnector.send(v);
}

Task Transaction Management

Transaction-Management Task

Queue a task execution

Direct usage of the TriggerService or PersistentTaskService.

private final TriggerService triggerService;
private final PersistentTaskService persistentTaskService;

public void buildVehicle() {
    // Vehicle has to be Serializable
    final var v = new Vehicle();
    // set any data to v ...

    // EITHER: queue it, will run later
    triggerService.queue(BuildVehicleTask.ID.newUniqueTrigger(v));

    // OR: will queue it and run it if possible.
    // if the scheduler service is missing it is same as using the TriggerService
    persistentTaskService.runOrQueue(BuildVehicleTask.ID.newUniqueTrigger(v));
}

Build complex Trigger

private final PersistentTaskService persistentTaskService;

public void buildVehicle() {
   var trigger = TaskTriggerBuilder
            .<Vehicle>newTrigger("task2")
            .id("my-id") // will overwrite existing triggers
            .state(new Vehicle("funny"))
            .runAfter(Duration.ofHours(2))
            .build();

    persistentTaskService.runOrQueue(trigger);
}

Use a Spring Event

private final ApplicationEventPublisher eventPublisher;

public void buildVehicle() {
    // Vehicle has to be Serializable
    final var v = new Vehicle();
    // send an event with the trigger inside - same as calling the PersistentTaskService
    eventPublisher.publishEvent(TriggerTaskCommand.of(BuildVehicleTask.ID.newUniqueTrigger(v)));
}

JUnit Tests

Spring configuration options

Property Type Description Default Value
spring.persistent-tasks.poll-rate java.lang.Integer The interval at which the scheduler checks for new tasks, in seconds. 30
spring.persistent-tasks.max-threads java.lang.Integer The number of threads to use; set to 0 to disable task processing. 10
spring.persistent-tasks.task-timeout java.time.Duration The maximum time allowed for a task and scheduler to complete a task. PT5M (5 minutes)
spring.persistent-tasks.poll-task-timeout java.lang.Integer The interval at which the system checks for abandoned tasks, in seconds. 300 (5 minutes)
spring.persistent-tasks.scheduler-enabled java.lang.Boolean Indicates whether this node should handle triggers. true
spring.persistent-tasks.history.delete-after java.time.Duration The max age for a trigger in the hstory. PT72H (30 days)
spring.persistent-tasks.history.delete-rate java.time.Integer The interval at which old triggers are deleted, in hours. 24 (24 hours)

Setup DB with Liquibase

Liquibase is supported. Either import all or just the required versions.

Maven

Option 1: Just include the master file

<include file="spring-persistent-tasks/db.changelog-master.xml" />

Option 2: import changesets on by one

<include file="spring-persistent-tasks/db/pt-changelog-v1.xml" />

Setup UI

Maven

<dependency>
    <groupId>org.sterl.spring</groupId>
    <artifactId>spring-persistent-tasks-ui</artifactId>
    <version>1.x.x</version>
</dependency>

Setup Spring

@SpringBootApplication
@EnableSpringPersistentTasks
@EnableSpringPersistentTasksUI
public class ExampleApplication {

Open the UI

Schedulers

Schedulers

Triggers

Triggers

History

History

Spring Boot CSRF config for the UI

Axios should work with the following spring config out of the box with csrf:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .httpBasic(org.springframework.security.config.Customizer.withDefaults())
        .csrf(c ->
            c.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
             .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
        );
    return http.build();
}

more informations: https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html

Alternatives

  • quartz
  • db-scheduler
  • jobrunr