Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## v1.4.0 - (2025-01-02)

- @Transactional Annotation support
- PersistentTask instead of Task or SpringBeanTask


## v1.3.1 - (2025-01-02)

- Bugfixes
- Sprign Transaction Template support

## v1.3.0 - (2025-01-01)

- MariaDB support
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class ExampleApplication {
@Component(BuildVehicleTask.NAME)
@RequiredArgsConstructor
@Slf4j
public class BuildVehicleTask implements SpringBeanTask<Vehicle> {
public class BuildVehicleTask implements PersistentTask<Vehicle> {

private static final String NAME = "buildVehicleTask";
public static final TaskId<Vehicle> ID = new TaskId<>(NAME);
Expand Down Expand Up @@ -109,11 +109,15 @@ Simple task will use defaults:

```java
@Bean
SpringBeanTask<Vehicle> task1(VehicleHttpConnector vehicleHttpConnector) {
PersistentTask<Vehicle> task1(VehicleHttpConnector vehicleHttpConnector) {
return v -> vehicleHttpConnector.send(v);
}
```

### Task Transaction Management

[Transaction-Management Task](https://github.com/sterlp/spring-persistent-tasks/wiki/Transaction-Management)

## Queue a task execution

### Direct usage of the `TriggerService` or `PersistentTaskService`.
Expand Down
4 changes: 2 additions & 2 deletions RUN_AND_BUILD.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mvn versions:display-dependency-updates
mvn versions:set -DnewVersion=1.3.1 -DgenerateBackupPoms=false
mvn versions:set -DnewVersion=1.3.2-SNAPSHOT -DgenerateBackupPoms=false
mvn versions:set -DnewVersion=1.4.0-SNAPSHOT -DgenerateBackupPoms=false
mvn versions:set -DnewVersion=1.4.0 -DgenerateBackupPoms=false

## postgres
docker run --name pg-container -e POSTGRES_USER=sa -e POSTGRES_PASSWORD=veryStrong123 -p 5432:5432 -d postgres
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.sterl.spring</groupId>
<artifactId>spring-persistent-tasks-root</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/**
/**
* For any registered task a task trigger represent one unit of work, executing this task once.
* For any registered persistentTask a persistentTask trigger represent one unit of work, executing this persistentTask once.
* @param <T> state type which has to be of {@link Serializable}
*/
public record AddTriggerRequest<T extends Serializable>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.sterl.spring.persistent_tasks.api;

import java.io.Serializable;

/**
* A Spring persistent task whose state is saved in a {@link Trigger}.
*
* <p>This interface defines a task that accepts a state of type <code>T</code> and
* provides default implementations for retry strategies.
*
* @param <T> the type of the state, which must be {@link Serializable}
*/
@FunctionalInterface
public interface PersistentTask<T extends Serializable> {
void accept(T state);

default RetryStrategy retryStrategy() {
return RetryStrategy.THREE_RETRIES;
}

/**
* Whether the persistentTask is transaction or not. If <code>true</code> the execution
* is wrapped into the default transaction template together with the state update
* and the following events:
* <ol>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent</li>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent</li>
* </ol>
* @return {@code true} if the persistentTask is transactional; {@code false} otherwise.
*/
default boolean isTransactional() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
package org.sterl.spring.persistent_tasks.api;

import java.io.Serializable;
import java.util.function.Consumer;

/**
* Same as {@link PersistentTask}
*/
@Deprecated
@FunctionalInterface
public interface SpringBeanTask<T extends Serializable> extends Consumer<T> {
@Override
void accept(T state);

default RetryStrategy retryStrategy() {
return RetryStrategy.THREE_RETRIES;
}
public interface SpringBeanTask<T extends Serializable> extends PersistentTask<T> {

/**
* Whether the task is transaction or not. If <code>true</code> the execution
* is wrapped into the default transaction template together with the state update
* and the following events:
* <ol>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent</li>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent</li>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent</li>
* </ol>
* @return {@code true} if the task is transactional; {@code false} otherwise.
*/
default boolean isTransactional() {
return false;
}
}
13 changes: 0 additions & 13 deletions core/src/main/java/org/sterl/spring/persistent_tasks/api/Task.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import lombok.RequiredArgsConstructor;

/**
* Represents the ID of a task, which is currently not running.
* Represents the ID of a persistentTask, which is currently not running.
*/
public record TaskId<T extends Serializable>(String name) implements Serializable {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.sterl.spring.persistent_tasks.api;

import java.io.Serializable;

/**
* Similar to {@link PersistentTask} but specifically for transactional workloads.
* Use this interface when the task execution should be wrapped in a transaction.
*
* <p>This interface ensures that the task's execution is transactional, meaning that it will
* be executed within a transaction context, along with the state update and the dispatching of
* relevant events.
*
* @param <T> the type of the state, which must be {@link Serializable}
*/
@FunctionalInterface
public interface TransactionalTask<T extends Serializable> extends PersistentTask<T> {
/**
* Whether the persistentTask is transaction or not. If <code>true</code> the execution
* is wrapped into the default transaction template together with the state update
* and the following events:
* <ol>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerRunningEvent</li>
* <li>org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent</li>
* </ol>
* @return {@code true} if the persistentTask is transactional; {@code false} otherwise.
*/
default boolean isTransactional() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ public TaskId<Serializable> toTaskId() {
return new TaskId<>(taskName);
}
/**
* Builds a trigger for the given task name
* Builds a trigger for the given persistentTask name
*/
public TriggerKey(String taskName) {
id = UUID.randomUUID().toString();
this.taskName = taskName;
}

/**
* Just triggers the given task to be executed using <code>null</code> as state.
* Just triggers the given persistentTask to be executed using <code>null</code> as state.
*/
public <T extends Serializable> AddTriggerRequest<T> newTrigger(TaskId<T> taskId) {
return newTrigger(taskId, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.sterl.spring.persistent_tasks.api.TaskId.TaskTriggerBuilder;

/**
* An event to trigger one or multiple task executions
* An event to trigger one or multiple persistentTask executions
*/
public record TriggerTaskCommand<T extends Serializable>(Collection<AddTriggerRequest<T>> triggers) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public Optional<SchedulerEntity> findStatus(String name) {
}

/**
* Simply triggers the next task which is now due to be executed
* Simply triggers the next persistentTask which is now due to be executed
*/
@NonNull
public List<Future<TriggerKey>> triggerNextTasks() {
Expand Down Expand Up @@ -134,7 +134,7 @@ public <T extends Serializable> Future<TriggerKey> runOrQueue(
trigger = triggerService.markTriggersAsRunning(trigger, name);
pingRegistry().addRunning(1);
} else {
log.debug("Currently not enough free thread available {} of {} in use. Task {} queued.",
log.debug("Currently not enough free thread available {} of {} in use. PersistentTask {} queued.",
taskExecutor.getFreeThreads(), taskExecutor.getMaxThreads(), trigger.getKey());
}
return trigger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void triggerNextTasks() {
}
}

@Scheduled(fixedDelayString = "${spring.persistent-tasks.poll-task-timeout:300}", timeUnit = TimeUnit.SECONDS)
@Scheduled(fixedDelayString = "${spring.persistent-tasks.poll-persistentTask-timeout:300}", timeUnit = TimeUnit.SECONDS)
void rescheduleAbandonedTasks() {
var timeout = OffsetDateTime.now().minus(taskTimeout);
for (SchedulerService s : schedulerServices) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.sterl.spring.persistent_tasks.api.SpringBeanTask;
import org.sterl.spring.persistent_tasks.api.Task;
import org.springframework.transaction.support.TransactionTemplate;
import org.sterl.spring.persistent_tasks.api.PersistentTask;
import org.sterl.spring.persistent_tasks.api.TaskId;
import org.sterl.spring.persistent_tasks.task.model.RegisteredTask;
import org.sterl.spring.persistent_tasks.task.component.TaskTransactionComponent;
import org.sterl.spring.persistent_tasks.task.repository.TaskRepository;

import lombok.RequiredArgsConstructor;
Expand All @@ -22,69 +22,75 @@
@RequiredArgsConstructor
public class TaskService {

private final TaskTransactionComponent taskTransactionComponent;
private final TaskRepository taskRepository;

@Transactional(readOnly = true)
public Set<TaskId<? extends Serializable>> findAllTaskIds() {
return this.taskRepository.all();
}


public <T extends Serializable> Optional<Task<T>> get(TaskId<T> id) {
public <T extends Serializable> Optional<PersistentTask<T>> get(TaskId<T> id) {
return taskRepository.get(id);
}

public <T extends Serializable> Optional<TransactionTemplate> getTransactionTemplate(
PersistentTask<T> task) {
return taskTransactionComponent.getTransactionTemplate(task);
}

/**
* Check if the {@link Task} is known or not.
* Check if the {@link PersistentTask} is known or not.
*
* @param <T> the state type
* @param id the {@link TaskId} of the {@link Task}
* @param id the {@link TaskId} of the {@link PersistentTask}
* @throws IllegalStateException if the id is unknown
* @return the {@link Task} registered to the given id
* @return the {@link PersistentTask} registered to the given id
*/
@NonNull
public <T extends Serializable> Task<T> assertIsKnown(@NonNull TaskId<T> id) {
public <T extends Serializable> PersistentTask<T> assertIsKnown(@NonNull TaskId<T> id) {
final var task = taskRepository.get(id);
if (task.isEmpty()) {
throw new IllegalStateException("Task with ID " + id
throw new IllegalStateException("PersistentTask with ID " + id
+ " is unknown. Known tasks: " + taskRepository.all());
}
return task.get();
}

/**
* A way to manually register a task, usually better to use {@link SpringBeanTask}.
*/
public <T extends Serializable> TaskId<T> register(String name, Consumer<T> task) {
RegisteredTask<T> t = new RegisteredTask<>(name, task);
return register(t);
}
/**
* A way to manually register a task, usually not needed as spring beans will be added automatically.
* A way to manually register a persistentTask, usually better to use {@link PersistentTask}.
*/
public <T extends Serializable> TaskId<T> register(String name, SpringBeanTask<T> task) {
RegisteredTask<T> t = new RegisteredTask<>(name, task);
return register(t);
public TaskId<Serializable> register(String name, Consumer<Serializable> task) {
return register(name, new PersistentTask<Serializable>() {
@Override
public void accept(Serializable state) {
task.accept(state);
}
});
}
/**
* A way to manually register a task, usually not needed as spring beans will be added anyway.
* A way to manually register a persistentTask, usually not needed as spring beans will be added automatically.
*/
public <T extends Serializable> TaskId<T> register(RegisteredTask<T> task) {
return taskRepository.addTask(task);
@SuppressWarnings("unchecked")
public <T extends Serializable> TaskId<T> register(String name, PersistentTask<T> task) {
var id = (TaskId<T>)TaskId.of(name);
return register(id, task);
}

/**
* A way to manually register a task, usually not needed as spring beans will be added anyway.
* A way to manually register a persistentTask, usually not needed as spring beans will be added automatically.
*/
public <T extends Serializable> TaskId<T> replace(RegisteredTask<T> task) {
taskRepository.remove(task);
return register(task);
public <T extends Serializable> TaskId<T> register(TaskId<T> id, PersistentTask<T> task) {
// init any transaction as needed
taskTransactionComponent.getTransactionTemplate(task);
return taskRepository.addTask(id, task);
}
/**
* A way to manually register a task, usually not needed as spring beans will be added automatically.
* A way to manually register a persistentTask, usually not needed as spring beans will be added automatically.
*/
public <T extends Serializable> TaskId<T> replace(String name, SpringBeanTask<T> task) {
RegisteredTask<T> t = new RegisteredTask<>(name, task);
return replace(t);
@SuppressWarnings("unchecked")
public <T extends Serializable> TaskId<T> replace(String name, PersistentTask<T> task) {
var id = (TaskId<T>)TaskId.of(name);
taskRepository.remove(id);
return register(id, task);
}
}
Loading
Loading