Spring Cloud Task Reference Guide

Michael Minella, Glenn Renfro

1.0.0.M1

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.


Table of Contents

I. Preface
1. About the documentation
2. Getting help
3. First Steps
II. Getting started
4. Introducing Spring Cloud Task
5. System Requirements
5.1. Database Requirements
6. Developing your first Spring Cloud Task application
6.1. Creating the POM
6.2. Adding classpath dependencies
6.3. Writing the code
6.3.1. The @EnableTask annotation
6.3.2. The main method
6.3.3. The CommandLineRunner
6.4. Running the example
III. Features
7. The lifecycle of a Spring Cloud Task
7.1. The TaskExecution
7.2. Mapping Exit Codes
8. Configuration
8.1. DataSource
8.2. TaskConfigurer
8.3. Task Name
IV. Appendices
9. Task repository schema
10. Building this documentation

Part I. Preface

This section provides a brief overview of the Spring Cloud Task reference documentation. Think of it as a map for the rest of the document. You can read this reference guide in a linear fashion, or you can skip sections if something doesn’t interest you.

1. About the documentation

The Spring Cloud Task reference guide is available as html, pdf and epub documents. The latest copy is available at docs.spring.io/spring-cloud-task/docs/current-SNAPSHOT/reference/html/.

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

2. Getting help

Having trouble with Spring Cloud Task, We’d like to help!

[Note]Note

All of Spring Cloud Task is open source, including the documentation! If you find problems with the docs; or if you just want to improve them, please get involved.

3. First Steps

If you’re just getting started with Spring Cloud Task, or 'Spring' in general, this is the place to start!

Part II. Getting started

If you’re just getting started with Spring Cloud Task, this is the section for you! Here we answer the basic “what?”, “how?” and “why?” questions. You’ll find a gentle introduction to Spring Cloud Task. We’ll then build our first Spring Cloud Task application, discussing some core principles as we go.

4. Introducing Spring Cloud Task

Spring Cloud Task makes it easy to create short lived microservices. We provide capabilities that allow short lived JVM processes to be executed on demand in a production environment.

5. System Requirements

You need Java installed (Java 7 or better, we recommend Java 8) and to build you need to have Maven installed as well.

5.1 Database Requirements

Spring Cloud Task uses a relational database to store the results of an executed task. While you can begin developing a task without a database (the status of the task is logged as part of the task repository’s updates), for production environments, you’ll want to utilize a supported database. Below is a list of the ones currently supported:

  • H2
  • HSQLDB
  • MySql
  • Oracle
  • Postgres

6. Developing your first Spring Cloud Task application

A good place to start is with a simple "Hello World!" application so we’ll create the Spring Cloud Task equivalent to highlight the features of the framework. We’ll use Apache Maven as a build tool for this project since most IDEs have good support for it.

[Note]Note

The spring.io web site contains many “Getting Started” guides that use Spring Boot. If you’re looking to solve a specific problem; check there first. You can shortcut the steps below by going to start.spring.io and creating a new project. This will automatically generate a new project structure so that you can start coding right the way. Check the documentation for more details.

Before we begin, open a terminal to check that you have valid versions of Java and Maven installed.

$ java -version
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
$ mvn -v
Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-11T15:58:10-05:00)
Maven home: /usr/local/Cellar/maven/3.2.3/libexec
Java version: 1.8.0_31, vendor: Oracle Corporation
[Note]Note

This sample needs to be created in its own folder. Subsequent instructions assume you have created a suitable folder and that it is your "current directory".

6.1 Creating the POM

We need to start by creating a Maven pom.xml file. The pom.xml is the recipe that will be used to build your project. Open your favorite text editor and add the following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>myproject</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.3.2.RELEASE</version>
	</parent>

	<properties>
		<start-class>com.example.SampleTask</start-class>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

This should give you a working build. You can test it out by running mvn package (you can ignore the "jar will be empty - no content was marked for inclusion!" warning for now).

[Note]Note

At this point you could import the project into an IDE (most modern Java IDE’s include built-in support for Maven). For simplicity we will continue to use a plain text editor for this example.

6.2 Adding classpath dependencies

A Spring Cloud Task is made up of a Spring Boot application that is expected to end. In our POM above, we created the shell of a Spring Boot application from a dependency perspective by setting our parent to use the spring-boot-starter-parent.

Spring Boot provides a number of additional "Starter POMs". Some of which are appropriate for use within tasks (spring-boot-starter-batch, spring-boot-starter-jdbc, etc) and some may not be ('spring-boot-starter-web` is probably not going to be used in a task). The indicator of if a starter makes sense or not comes down to if the resulting application will end (batch based applications typically end, the spring-boot-starter-web dependency bootstraps a servlet container which probably wont').

For this example, we’ll only need to add a single additional dependency, the one for Spring Cloud Task itself:

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-task-core</artifactId>
			<version>1.0.0.BUILD-SNAPSHOT</version>
		</dependency>

6.3 Writing the code

To finish our application, we need to create a single Java file. Maven will compile the sources from src/main/java by default so you need to create that folder structure. Then add a file named src/main/java/com/example/SampleTask.java:

package com.example;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableTask
public class SampleTask {

	@Bean
	public CommandLineRunner commandLineRunner() {
		return new HelloWorldCommandLineRunner();
	}

	public static void main(String[] args) {
		SpringApplication.run(SampleTask.class, args);
	}

	public static class HelloWorldCommandLineRunner implements CommandLineRunner {

		@Override
		public void run(String... strings) throws Exception {
			System.out.println("Hello World!");
		}
	}
}

While it may not look like much, quite a bit is going on. To read more about the Spring Boot specifics, take a look at their reference documentation here: http://docs.spring.io/spring-boot/docs/current/reference/html/

6.3.1 The @EnableTask annotation

The first non boot annotation in our example is the @EnableTask annotation. This class level annotation tells Spring Cloud Task to bootstrap it’s functionality. This occurs by importing an additional configuration class, SimpleTaskConfiguration by default. This additional configuration registers the TaskRepository and the infrastructure for its use.

Out of the box, the TaskRepository will use an in memory Map to record the results of a task. Obviously this isn’t a practical solution for a production environment since the Map goes away once the task ends. However, for a quick getting started experience we use this as a default as well as echoing to the logs what is being updated in that repository. Later in this documentation we’ll cover how to customize the configuration of the pieces provided by Spring Cloud Task.

When our sample application is run, Spring Boot will launch our HelloWorldCommandLineRunner outputting our "Hello World!" message to standard out. The TaskLifecyceListener will record the start of the task and the end of the task in the repository.

6.3.2 The main method

The main method serves as the entry point to any java application. Our main method delegates to Spring Boot’s SpringApplication class. You can read more about it in the Spring Boot documentation.

6.3.3 The CommandLineRunner

In Spring, there are many ways to bootstrap an application’s logic. Spring Boot provides a convenient method of doing so in an organized manor via their *Runner interfaces (CommandLineRunner or ApplicationRunner). A well behaved task will bootstrap any logic via one of these two runners.

The lifecycle of a task is considered from before the *Runner#run methods are executed to once they are all complete. Spring Boot allows an application to use multiple *Runner implementation and Spring Cloud Task doesn’t attempt to impede on this convention.

[Note]Note

Any processing bootstrapped from mechanisms other than a CommandLineRunner or ApplicationRunner (using InitializingBean#afterPropertiesSet for example) will not be recorded by Spring Cloud Task.

6.4 Running the example

At this point, your application should work. Since this application is Spring Boot based, we can run it from the command line via the command $ mvn spring-boot:run from the root of our applicaiton:

$ mvn clean spring-boot:run
....... . . .
....... . . . (Maven log output here)
....... . . .


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.2.RELEASE)

2016-01-25 11:08:10.183  INFO 12943 --- [           main] com.example.SampleTask                   : Starting SampleTask on Michaels-MacBook-Pro-2.local with PID 12943 (/Users/mminella/Documents/IntelliJWorkspace/spring-cloud-task-example/target/classes started by mminella in /Users/mminella/Documents/IntelliJWorkspace/spring-cloud-task-example)
2016-01-25 11:08:10.185  INFO 12943 --- [           main] com.example.SampleTask                   : No active profile set, falling back to default profiles: default
2016-01-25 11:08:10.226  INFO 12943 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.spring[email protected]2a2c3676: startup date [Mon Jan 25 11:08:10 CST 2016]; root of context hierarchy
2016-01-25 11:08:11.051  INFO 12943 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-01-25 11:08:11.065  INFO 12943 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Creating: TaskExecution{executionId=0, externalExecutionID='null', exitCode=0, taskName='application', startTime=Mon Jan 25 11:08:11 CST 2016, endTime=null, statusCode='null', exitMessage='null', parameters=[]}
Hello World!
2016-01-25 11:08:11.071  INFO 12943 --- [           main] com.example.SampleTask                   : Started SampleTask in 1.095 seconds (JVM running for 3.826)
2016-01-25 11:08:11.220  INFO 12943 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.spring[email protected]2a2c3676: startup date [Mon Jan 25 11:08:10 CST 2016]; root of context hierarchy
2016-01-25 11:08:11.222  INFO 12943 --- [       Thread-1] o.s.c.t.r.support.SimpleTaskRepository   : Updating: TaskExecution{executionId=0, externalExecutionID='null', exitCode=0, taskName='application', startTime=Mon Jan 25 11:08:11 CST 2016, endTime=Mon Jan 25 11:08:11 CST 2016, statusCode='null', exitMessage='null', parameters=[]}
2016-01-25 11:08:11.222  INFO 12943 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

If you notice, there are three lines of interest in the above output:

  • SimpleTaskRepository logged out the creation of the entry in the TaskRepository.
  • The execution of our CommandLineRunner, demonstrated by the "Helo World!" output.
  • SimpleTaskREpository logging the completion of the task in the TaskRepository.

Part III. Features

This section goes into more detail about Spring Cloud Task. How to use it, how to configure it, as well as the appropriate extension points are all covered in this section.

7. 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, they are typically restarted. While most platforms do have some method to execute a process that isn’t restarted when it ends, the results of that execution are typically not maintained in a consumable way. Spring Cloud Task brings the ability to execute short lived processes in an environment and record the results. This allows for a microservices architecture around short lived processes as well as longer running services.

While this functionality is useful in a cloud environment, the same issues can arise in a traditional deployment model as well. When executing Spring Boot applications via a scheduler like cron, it can be useful to be able to monitor the results of the application after it’s completion.

A 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 just one example of where short lived processes can be helpful. Spring Cloud Task records lifecycle events of a given task.

The lifecycle consists of a single task execution. This is a physical execution of a Spring Boot application configured to be a task (annotated with the @EnableTask annotation).

At the beginning of a task, an entry in the TaskRepository is created recording the start event. This event is triggered via the ContextRefreshEvent being triggered by Spring Framework.

[Note]Note

As Spring Cloud Task is expected to consist of a single application context. If multiple application contexts are used (parent/child relationships for example), the first ContextRefreshEvent that is published by Spring will be recorded as the start of the task.

[Note]Note

The recording of a task will only occur upon the successful bootstrapping of an ApplicationContext. If the context fails to bootstrap at all, the task’s execution will not be recorded.

Upon completion of all of the *Runner#run calls from Spring Boot or the failure of an ApplicationContext (indicated via a ApplicationFailedEvent), the task execution is updated in the repository with the results.

7.1 The TaskExecution

The information stored in the TaskRepository is modeled in the TaskExecution class and consists of the following information:

FieldDescription

executionid

The unique id for the task’s execution.

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’s assumed to be 0.

taskName

The name for the task as determined by the configured TaskNameResolver.

starTime

The time the task was started as indicated by the ContextRefreshEvent.

endTime

The time the task was completed as indicated by the ContextClosedEvent.

exitMessage

Any information available at the time of exit. If an exception is the cause of the end of the task (as indicated via an ApplicationFailedEvent), the stack trace for that exception will be stored here.

parameters

A List of the string parameters as they were passed into the executable boot application.

7.2 Mapping Exit Codes

When a task completes, it will want to return an exit code to the OS. If we take a look at our original example, we can see that we are not controlling that aspect of our application. So if an exception is thrown, the JVM will return a code that may or may not be of any use to you in the debugging of that.

As such, Spring Boot provides an interface, ExitCodeExceptionMapper that allows you to map uncaught exceptions to exit codes. This allows you to be able to indicate at that level what went wrong. Also, by mapping exit codes in this manor, Spring Cloud Task will record the exit code returned.

8. Configuration

Spring Cloud Task provides an out of the box configuration as defined in the DefaultTaskConfigurer and SimpleTaskConfiguration. This section will walk through the defaults as well as how to customize Spring Cloud Task for your needs

8.1 DataSource

Spring Cloud Task utilizes 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’ll want to configure your own DataSource.

If your application utilizes only a single DataSource and that will serve as both your business schema as well as the task repository, all you need to do is provide any DataSource (via Spring Boot’s configuration conventions is the easiest way). This will be automatically used by Spring Cloud Task for the repository.

If your application utilizes more than one DataSource, you’ll need to configure the task repository with the appropriate DataSource. This customization can be done via an implementation of the TaskConfigurer.

8.2 TaskConfigurer

The TaskConfigurer is a strategy interface allowing for users to 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 if there is a DataSource available.

The TaskConfigurer allows the configuration of three main components:

ComponentDescriptionDefault (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 executing updates for tasks.

DataSourceTransactionManager if a DataSource is used, ResourcelessTransactionManager if it is not.

8.3 Task Name

In most cases, the name of the task will be the application name as configured via Spring Boot. However, there are some cases, where you may want to map the run of a task to a different name. Spring Data Flow is an example of this (where you want the task to be run with the name of the task definition). Because of this, we offer the ability to customize how the task is named via the TaskNameResolver interface.

By default, Spring Cloud Task provides the SimpleTaskNameResolver which will use the following options (in order of precedence):

  1. A Spring Boot property (configured any of the ways Spring Boot allows) spring.cloud.task.name.
  2. The application name as resolved using Spring Boot’s rules (obtained via ApplicationContext#getId).

Part IV. Appendices

9. Task repository schema

This appendix provides an ERD for the database schema used in the task repository.

task schema

10. Building this documentation

This project uses Maven to generate this documentation. To generate it for yourself, execute the command: $ ./mvnw clean package -P full.