Files
spring-cloud-task/docs/modules/ROOT/pages/features.adoc

455 lines
20 KiB
Plaintext

[[features]]
= Features
[[partintro]]
--
This section goes into more detail about Spring Cloud Task, including how to use it, how
to configure it, and the appropriate extension points.
--
[[features-lifecycle]]
== The lifecycle of a Spring Cloud Task
In most cases, the modern cloud environment is designed around the execution of processes
that are not expected to end. If they do end, they are typically restarted. While most
platforms do have some way to run a process that is not restarted when it ends, the
results of that run are typically not maintained in a consumable way. Spring Cloud
Task offers the ability to execute short-lived processes in an environment and record the
results. Doing so allows for a microservices architecture around short-lived processes as
well as longer running services through the integration of tasks by messages.
While this functionality is useful in a cloud environment, the same issues can arise in a
traditional deployment model as well. When running Spring Boot applications with a
scheduler such as cron, it can be useful to be able to monitor the results of the
application after its completion.
Spring Cloud Task takes the approach that a Spring Boot application can have a start and
an end and still be successful. Batch applications are one example of how processes that
are expected to end (and that are often short-lived) can be helpful.
Spring Cloud Task records the lifecycle events of a given task. Most long-running
processes, typified by most web applications, do not save their lifecycle events. The
tasks at the heart of Spring Cloud Task do.
The lifecycle consists of a single task execution. This is a physical execution of a
Spring Boot application configured to be a task (that is, it has the Sprint Cloud Task dependencies).
At the beginning of a task, before any `CommandLineRunner` or `ApplicationRunner`
implementations have been run, an entry in the `TaskRepository` that records the start
event is created. This event is triggered through `SmartLifecycle#start` being triggered
by the Spring Framework. This indicates to the system that all beans are ready for use and
comes before running any of the `CommandLineRunner` or `ApplicationRunner` implementations
provided by Spring Boot.
NOTE: The recording of a task only occurs upon the successful bootstrapping of an
`ApplicationContext`. If the context fails to bootstrap at all, the task's run is not
recorded.
Upon completion of all of the `*Runner#run` calls from Spring Boot or the failure of an
`ApplicationContext` (indicated by an `ApplicationFailedEvent`), the task execution is
updated in the repository with the results.
NOTE: If the application requires the `ApplicationContext` to be closed at the
completion of a task (all `*Runner#run` methods have been called and the task
repository has been updated), set the property `spring.cloud.task.closecontextEnabled`
to true.
[[features-task-execution-details]]
=== The TaskExecution
The information stored in the `TaskRepository` is modeled in the `TaskExecution` class and
consists of the following information:
|===
|Field |Description
|`executionid`
|The unique ID for the task's run.
|`exitCode`
|The exit code generated from an `ExitCodeExceptionMapper` implementation. If there is no
exit code generated but an `ApplicationFailedEvent` is thrown, 1 is set. Otherwise, it is
assumed to be 0.
|`taskName`
|The name for the task, as determined by the configured `TaskNameResolver`.
|`startTime`
|The time the task was started, as indicated by the `SmartLifecycle#start` call.
|`endTime`
|The time the task was completed, as indicated by the `ApplicationReadyEvent`.
|`exitMessage`
|Any information available at the time of exit. This can programmatically be set by a
`TaskExecutionListener`.
|`errorMessage`
|If an exception is the cause of the end of the task (as indicated by an
`ApplicationFailedEvent`), the stack trace for that exception is stored here.
|`arguments`
|A `List` of the string command line arguments as they were passed into the executable
boot application.
|===
[[features-lifecycle-exit-codes]]
=== Mapping Exit Codes
When a task completes, it tries to return an exit code to the OS. If we take a look
at our xref:getting-started.adoc#getting-started-developing-first-task[original example], we can see that we are
not controlling that aspect of our application. So, if an exception is thrown, the JVM
returns a code that may or may not be of any use to you in debugging.
Consequently, Spring Boot provides an interface, `ExitCodeExceptionMapper`, that lets you
map uncaught exceptions to exit codes. Doing so lets you indicate, at the level of exit
codes, what went wrong. Also, by mapping exit codes in this manner, Spring Cloud Task
records the returned exit code.
If the task terminates with a SIG-INT or a SIG-TERM, the exit code is zero unless
otherwise specified within the code.
NOTE: While the task is running, the exit code is stored as a null in the repository.
Once the task completes, the appropriate exit code is stored based on the guidelines described
earlier in this section.
[[features-configuration]]
== Configuration
Spring Cloud Task provides a ready-to-use configuration, as defined in the
`DefaultTaskConfigurer` and `SimpleTaskConfiguration` classes. This section walks through
the defaults and how to customize Spring Cloud Task for your needs.
[[features-data-source]]
=== DataSource
Spring Cloud Task uses a datasource for storing the results of task executions. By
default, we provide an in-memory instance of H2 to provide a simple method of
bootstrapping development. However, in a production environment, you probably want to
configure your own `DataSource`.
If your application uses only a single `DataSource` and that serves as both your business
schema and the task repository, all you need to do is provide any `DataSource` (the
easiest way to do so is through Spring Boot's configuration conventions). This
`DataSource` is automatically used by Spring Cloud Task for the repository.
If your application uses more than one `DataSource`, you need to configure the task
repository with the appropriate `DataSource`. This customization can be done through an
implementation of `TaskConfigurer`.
[[features-table-prefix]]
=== Table Prefix
One modifiable property of `TaskRepository` is the table prefix for the task tables. By
default, they are all prefaced with `TASK_`. `TASK_EXECUTION` and `TASK_EXECUTION_PARAMS`
are two examples. However, there are potential reasons to modify this prefix. If the
schema name needs to be prepended to the table names or if more than one set of task
tables is needed within the same schema, you must change the table prefix. You can do so
by setting the `spring.cloud.task.tablePrefix` to the prefix you need, as follows:
`spring.cloud.task.tablePrefix=yourPrefix`
By using the `spring.cloud.task.tablePrefix`, a user assumes the responsibility to
create the task tables that meet both the criteria for the task table schema but
with modifications that are required for a user's business needs.
You can utilize the Spring Cloud Task Schema DDL as a guide when creating your own Task DDL as seen
https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task[here].
[[features-table-initialization]]
=== Enable/Disable table initialization
In cases where you are creating the task tables and do not wish for Spring Cloud Task to
create them at task startup, set the `spring.cloud.task.initialize-enabled` property to
`false`, as follows:
`spring.cloud.task.initialize-enabled=false`
It defaults to `true`.
NOTE: The property `spring.cloud.task.initialize.enable` has been deprecated.
[[features-generated_task_id]]
=== Externally Generated Task ID
In some cases, you may want to allow for the time difference between when a task is
requested and when the infrastructure actually launches it. Spring Cloud Task lets you
create a `TaskExecution` when the task is requested. Then pass the execution ID of the
generated `TaskExecution` to the task so that it can update the `TaskExecution` through
the task's lifecycle.
A `TaskExecution` can be created by calling the `createTaskExecution` method on an
implementation of the `TaskRepository` that references the datastore that holds
the `TaskExecution` objects.
In order to configure your Task to use a generated `TaskExecutionId`, add the
following property:
`spring.cloud.task.executionid=yourtaskId`
[[features-external_task_id]]
=== External Task Id
Spring Cloud Task lets you store an external task ID for each
`TaskExecution`. In order to configure your Task to use a generated `TaskExecutionId`, add the
following property:
`spring.cloud.task.external-execution-id=<externalTaskId>`
[[features-parent_task_id]]
=== Parent Task Id
Spring Cloud Task lets you store a parent task ID for each `TaskExecution`. An example of
this would be a task that executes another task or tasks and you want to record which task
launched each of the child tasks. In order to configure your Task to set a parent
`TaskExecutionId` add the following property on the child task:
`spring.cloud.task.parent-execution-id=<parentExecutionTaskId>`
[[features-task-configurer]]
=== TaskConfigurer
The `TaskConfigurer` is a strategy interface that lets you customize the way components of
Spring Cloud Task are configured. By default, we provide the `DefaultTaskConfigurer` that
provides logical defaults: `Map`-based in-memory components (useful for development if no
`DataSource` is provided) and JDBC based components (useful if there is a `DataSource`
available).
The `TaskConfigurer` lets you configure three main components:
|===
|Component |Description |Default (provided by `DefaultTaskConfigurer`)
|`TaskRepository`
|The implementation of the `TaskRepository` to be used.
|`SimpleTaskRepository`
|`TaskExplorer`
|The implementation of the `TaskExplorer` (a component for read-only access to the task
repository) to be used.
|`SimpleTaskExplorer`
|`PlatformTransactionManager`
|A transaction manager to be used when running updates for tasks.
|`JdbcTransactionManager` if a `DataSource` is used.
`ResourcelessTransactionManager` if it is not.
|===
You can customize any of the components described in the preceding table by creating a
custom implementation of the `TaskConfigurer` interface. Typically, extending the
`DefaultTaskConfigurer` (which is provided if a `TaskConfigurer` is not found) and
overriding the required getter is sufficient. However, implementing your own from scratch
may be required.
NOTE: Users should not directly use getter methods from a `TaskConfigurer` directly
unless they are using it to supply implementations to be exposed as Spring Beans.
[[features-task-execution-listener]]
=== Task Execution Listener
`TaskExecutionListener` lets you register listeners for specific events that occur during
the task lifecycle. To do so, create a class that implements the
`TaskExecutionListener` interface. The class that implements the `TaskExecutionListener`
interface is notified of the following events:
* `onTaskStartup`: Prior to storing the `TaskExecution` into the `TaskRepository`.
* `onTaskEnd`: Prior to updating the `TaskExecution` entry in the `TaskRepository` and
marking the final state of the task.
* `onTaskFailed`: Prior to the `onTaskEnd` method being invoked when an unhandled
exception is thrown by the task.
Spring Cloud Task also lets you add `TaskExecution` Listeners to methods within a bean
by using the following method annotations:
* `@BeforeTask`: Prior to the storing the `TaskExecution` into the `TaskRepository`
* `@AfterTask`: Prior to the updating of the `TaskExecution` entry in the `TaskRepository`
marking the final state of the task.
* `@FailedTask`: Prior to the `@AfterTask` method being invoked when an unhandled
exception is thrown by the task.
The following example shows the three annotations in use:
[source,java]
----
public class MyBean {
@BeforeTask
public void methodA(TaskExecution taskExecution) {
}
@AfterTask
public void methodB(TaskExecution taskExecution) {
}
@FailedTask
public void methodC(TaskExecution taskExecution, Throwable throwable) {
}
}
----
NOTE: Inserting an `ApplicationListener` earlier in the chain than `TaskLifecycleListener` exists may cause unexpected effects.
[[features-task-execution-listener-Exceptions]]
==== Exceptions Thrown by Task Execution Listener
If an exception is thrown by a `TaskExecutionListener` event handler, all listener
processing for that event handler stops. For example, if three `onTaskStartup` listeners
have started and the first `onTaskStartup` event handler throws an exception, the other
two `onTaskStartup` methods are not called. However, the other event handlers (`onTaskEnd`
and `onTaskFailed`) for the `TaskExecutionListeners` are called.
The exit code returned when a exception is thrown by a `TaskExecutionListener`
event handler is the exit code that was reported by the
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/ExitCodeEvent.html[ExitCodeEvent].
If no `ExitCodeEvent` is emitted, the Exception thrown is evaluated to see
if it is of type
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit[ExitCodeGenerator].
If so, it returns the exit code from the `ExitCodeGenerator`. Otherwise, `1`
is returned.
In the case that an exception is thrown in an `onTaskStartup` method, the exit code for the application will be `1`.
If an exception is thrown in either a `onTaskEnd` or `onTaskFailed`
method, the exit code for the application will be the one established using the rules enumerated above.
NOTE: In the case of an exception being thrown in a `onTaskStartup`, `onTaskEnd`, or `onTaskFailed`
you can not override the exit code for the application using `ExitCodeExceptionMapper`.
[[features-task-execution-listener-exit-messages]]
==== Exit Messages
You can set the exit message for a task programmatically by using a
`TaskExecutionListener`. This is done by setting the `TaskExecution's` `exitMessage`,
which then gets passed into the `TaskExecutionListener`. The following example shows
a method that is annotated with the `@AfterTask` `ExecutionListener` :
[source,java]
@AfterTask
public void afterMe(TaskExecution taskExecution) {
taskExecution.setExitMessage("AFTER EXIT MESSAGE");
}
An `ExitMessage` can be set at any of the listener events (`onTaskStartup`,
`onTaskFailed`, and `onTaskEnd`). The order of precedence for the three listeners follows:
. `onTaskEnd`
. `onTaskFailed`
. `onTaskStartup`
For example, if you set an `exitMessage` for the `onTaskStartup` and `onTaskFailed`
listeners and the task ends without failing, the `exitMessage` from the `onTaskStartup`
is stored in the repository. Otherwise, if a failure occurs, the `exitMessage` from
the `onTaskFailed` is stored. Also if you set the `exitMessage` with an
`onTaskEnd` listener, the `exitMessage` from the `onTaskEnd` supersedes
the exit messages from both the `onTaskStartup` and `onTaskFailed`.
[[features-single-instance-enabled]]
=== Restricting Spring Cloud Task Instances
Spring Cloud Task lets you establish that only one task with a given task name can be run
at a time. To do so, you need to establish the <<features-task-name, task name>> and set
`spring.cloud.task.single-instance-enabled=true` for each task execution. While the first
task execution is running, any other time you try to run a task with the same
<<features-task-name, task name>> and `spring.cloud.task.single-instance-enabled=true`, the
task fails with the following error message: `Task with name "application" is already
running.` The default value for `spring.cloud.task.single-instance-enabled` is `false`. The
following example shows how to set `spring.cloud.task.single-instance-enabled` to `true`:
`spring.cloud.task.single-instance-enabled=true or false`
To use this feature, you must add the following Spring Integration dependencies to your
application:
[source,xml]
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
</dependency>
NOTE: The exit code for the application will be 1 if the task fails because this feature
is enabled and another task is running with the same task name.
[[single-instance-usage-for-spring-aot-and-native-compilation]]
==== Single Instance Usage for Spring AOT And Native Compilation
To use Spring Cloud Task's single-instance feature when creating a natively compiled app, you need to enable the feature at build time.
To do so, add the process-aot execution and set `spring.cloud.task.single-step-instance-enabled=true` as a JVM argument, as follows:
[source,xml]
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
<configuration>
<jvmArguments>
-Dspring.cloud.task.single-instance-enabled=true
</jvmArguments>
</configuration>
</execution>
</executions>
</plugin>
[[enabling-observations-for-applicationrunner-and-commandlinerunner]]
=== Enabling Observations for ApplicationRunner and CommandLineRunner
To Enable Task Observations for `ApplicationRunner` or `CommandLineRunner` set `spring.cloud.task.observation.enabled` to true.
An example task application with observations enables using the `SimpleMeterRegistry` can be found https://github.com/spring-cloud/spring-cloud-task/tree/main/spring-cloud-task-samples/task-observations[here].
[[disabling-spring-cloud-task-auto-configuration]]
=== Disabling Spring Cloud Task Auto Configuration
In cases where Spring Cloud Task should not be autoconfigured for an implementation, you can disable Task's auto configuration.
This can be done either by adding the following annotation to your Task application:
```
@EnableAutoConfiguration(exclude={SimpleTaskAutoConfiguration.class})
```
You may also disable Task auto configuration by setting the `spring.cloud.task.autoconfiguration.enabled` property to `false`.
[[closing-the-context]]
=== Closing the Context
If the application requires the `ApplicationContext` to be closed at the
completion of a task (all `*Runner#run` methods have been called and the task
repository has been updated), set the property `spring.cloud.task.closecontextEnabled`
to `true`.
Another case to close the context is when the Task Execution completes however the application does not terminate.
In these cases the context is held open because a thread has been allocated
(for example: if you are using a TaskExecutor). In these cases
set the `spring.cloud.task.closecontextEnabled` property to `true` when launching your task.
This will close the application's context once the task is complete.
Thus allowing the application to terminate.
[[enable-task-metrics]]
=== Enable Task Metrics
Spring Cloud Task integrates with Micrometer and creates observations for the Tasks it executes.
To enable Task Observability integration, you must add `spring-boot-starter-actuator`, your preferred registry implementation (if you want to publish metrics), and micrometer-tracing (if you want to publish tracing data) to your task application.
An example maven set of dependencies to enable task observability and metrics using Influx would be:
[source,xml]
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
<scope>runtime</scope>
</dependency>
[[spring-task-and-spring-cloud-task]]
=== Spring Task and Spring Cloud Task Properties
The term `task` is frequently used word in the industry. In one such example Spring Boot offers the `spring.task` while Spring Cloud Task offers the `spring.cloud.task` properties.
This has caused some confusion in the past that these two groups of properties are directly related. However, they represent 2 different set of features offered in the Spring ecosystem.
* `spring.task` refers to the properties that configure the `ThreadPoolTaskScheduler`.
* `spring.cloud.task` refers to the properties that configure features of Spring Cloud Task.