Async Tasks

This module works only for Ontimize Boot version 3.8.0 or above. Actual release version: Ontimize Boot

Introduction

The Async Task system will allow you to run decoupled, asynchronous tasks. This module will let you run any service method in a separate, newly created thread, by simply adding an annotation to its controller method.

Previous concepts

  • Task: It is the generic representation of a decoupled task. It stores information such as its UUID, its current status and the result of the execution.
  • Aspect: It is a modularization of a concern that cuts across multiple classes. It allows us to intercept the execution of any given method or class and implement some alternative or extra behaviour for it.

Prerequisites

You can follow this tutorial using your own application, although for this example we will use an application created using the archetype that can be found on this page and with a REST service.

There are 2 options to follow this tutorial, clone the repository with the initial state and follow the tutorial step by step, or download the final example and see which files are new and which have been updated.

Initial project

/$ git clone https://github.com/ontimize/ontimize-examples 
/ontimize-examples$ cd ontimize-examples
/ontimize-examples$ git checkout boot-async-task-initial

Final example

/$ git clone https://github.com/ontimize/ontimize-examples 
/ontimize-examples$ cd ontimize-examples
/ontimize-examples$ git checkout boot-async-task

To simplify the code being written, three dots (…) may appear in some parts of the code. This indicates that there may be previous code before and after those dots.

Steps

Database

Tasks Table

With the database started, we create the new table that will store the tasks information.

CREATE TABLE TASKS(ID INTEGER IDENTITY NOT NULL PRIMARY KEY, UUID VARCHAR(255) NOT NULL, STATUS VARCHAR(255), RESULT VARBINARY(16777216));

Server

Add Ontimize AsyncTask dependencies

The decoupled tasks system is integrated in the Ontimize Core module, so we need to declare it as a project dependency.

boot/pom.xml

...
<dependencies>
  ...
  <dependency>
    <groupId>com.ontimize.boot</groupId>
    <artifactId>ontimize-boot-starter-core</artifactId>
  </dependency>
  ...
</dependencies>
...

model/pom.xml

...
<dependencies>
  ...
  <dependency>
    <groupId>com.ontimize.boot</groupId>
    <artifactId>ontimize-boot-core</artifactId>
  </dependency>
  ...
</dependencies>
...

Modify application.yml

The application.yml file will be modified to enable the decoupled tasks module, indicate the storage engine it will use, the URL base path for the service, and its thread pool configuration. In this link you have information about the configuration of the asynchronous tasks system in the application.yml file.

The enable property must be set to true and the storage engine type must be specified in the engine property before the server is started.

The asynchronous tasks system requires the Ontimize TaskExecutor to be configured, see this link.

application.yml

For database storage

ontimize:
  asynctask:
    enable: true
    engine: database
    url: /tasks

Add Task DAO

A specific DAO will be created for the tasks table, and it will implement the DAO interface in the tasks module.

TaskDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<JdbcEntitySetup xmlns="http://www.ontimize.com/schema/jdbc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.ontimize.com/schema/jdbc http://www.ontimize.com/schema/jdbc/ontimize-jdbc-dao.xsd"
	table="TASKS" datasource="mainDataSource" sqlhandler="dbSQLStatementHandler">
	<DeleteKeys>
		<Column>ID</Column>
	</DeleteKeys>
	<UpdateKeys>
		<Column>ID</Column>
	</UpdateKeys>
	<GeneratedKey>ID</GeneratedKey>
</JdbcEntitySetup>

TaskDao.java

package com.imatia.qsallcomponents.model.dao;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;

import com.ontimize.boot.core.asynctask.IAsyncTaskDao;
import com.ontimize.jee.server.dao.common.ConfigurationFile;
import com.ontimize.jee.server.dao.jdbc.OntimizeJdbcDaoSupport;

@Lazy
@Repository(value = "TaskDao")
@ConfigurationFile(configurationFile = "dao/TaskDao.xml", configurationFilePlaceholder = "dao/placeholders.properties")
public class TaskDao extends OntimizeJdbcDaoSupport implements IAsyncTaskDao {
	
	public static final String	ATTR_ID				= "ID";
	public static final String	ATTR_UUID			= "UUID";
	public static final String	ATTR_STATUS			= "STATUS";
	public static final String	ATTR_RESULT			= "RESULT";
	
	public TaskDao() {
		super();
	}

}

Annotate controller method

In order to run some service method asynchronously, we need to annotate its respective REST controller method with @OAsyncTask. This way, a new thread will be created in order to handle the method’s execution, and we will recieve an instant response with the URL where we can check the execution status and retrieve its result when it’s finished.

The service’s method MUST return a serializable object with getters and setters, as well as the controller’s method must return a ResponseEntity object. In this case, the query() method returns a Serializable object, the EntityResult.

We will override the query() method of the ORestController class.

CandidateRestController.java

package com.ontimize.projectwiki.ws.core.rest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.ontimize.boot.core.asynctask.OAsyncTask;
import com.ontimize.jee.common.dto.EntityResult;
import com.ontimize.jee.server.rest.ORestController;
import com.ontimize.projectwiki.api.core.service.ICandidateService;

@RestController
@RequestMapping("/candidates")
@ComponentScan(basePackageClasses = { com.ontimize.projectwiki.api.core.service.ICandidateService.class })
public class CandidateRestController extends ORestController<ICandidateService>{

	@Autowired
	private ICandidateService candidateService;

	@Override
	public ICandidateService getService() {
		return this.candidateService;
	}
	
    @OAsyncTask
    @Override
    public ResponseEntity<EntityResult> query(@PathVariable("name") String name,
            @RequestParam(name = "filter", required = false) String filter,
            @RequestParam(name = "columns", required = false) String columns) {
        return super.query(name, filter, columns);
    }
}

Delay service method

To know all the states through which the asynchronous request passes, we will add a delay in the candidateQuery() method.

CandidateService.java

package com.ontimize.projectwiki.model.core.service;

import java.util.concurrent.TimeUnit;

. . .

@Service("CandidateService")
@Lazy
public class CandidateService implements ICandidateService {

. . .

	@Override
	public EntityResult candidateQuery(Map<String, Object> keyMap, List<String> attrList)
			throws OntimizeJEERuntimeException {
		try {
			TimeUnit.MINUTES.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return this.daoHelper.query(this.candidateDao, keyMap, attrList);
	}
. . .
}

Testing

To test the asynchronous tasks we need to execute a REST request to the method that we have marked with the annotation @OAsyncTask.

In this case, the request is GET and has the following structure: http://localhost:33333/candidates/candidate?columns=ID,NAME,SURNAME

Element Meaning
localhost:33333 Indicates the host
/candidates Indicates the service to be queried
/candidate Indicates the DAO that will access that service
?columns= Indicates the columns to be queried

The authorization used for this requests is authorization of the type BASIC.

The access must be done with a user and password example:

    User: demo
Password: demouser

When you run the query, it should return a 202 Accepted with the following header: Location.

This header contains the relative path to the asynchronous task that you have to execute to receive the data.

Example

Location: /tasks/f16e9af7-ec0f-444c-a173-5b0179f5d57f

To execute the query the request needs to be GET and have the following structure: http://localhost:33333/tasks/f16e9af7-ec0f-444c-a173-5b0179f5d57f

The uuid that goes after /tasks varies in each execution of the previous query.

The first time you run this query the status becomes Started. When the time set in the delay expires the second time you execute the query, the status becomes Completed, returns the request data and removes the task from the TASKS table.