Enterprise Java

Simple Workflow Engine With Spring

Few months ago, during working on one of the company project, we had need to developed REST services which is used for sending an email depending on data sent by client application. During developing this service we decide to create simple workflow engine which will be charged for sending an email, but also this engine can be used for any kind of simple flows.

In this article i will explain step by step how you can implement your simple workflow engine which can handle sequence flow.

For implementing of this workflow engine we used spring framework, but idea how to implement this should be same with any framework, if you use one, or without framework.

We will start with short introduction to sequence workflow pattern, after that we will take look at needed interfaces and at end we will start with implementing workflow engine with spring.

Sequence workflow pattern

Sequence workflow pattern describe workflows in which every step (action) is done step by step one after another. On next image you can see how that should looks like:

Every action which will be processed inside flow share same context, which allow flow’s participants to share information between each other. Idea of common context is used because every step should be independent one from each other and they should be easily added as part of some other flow.

If you want to get more information about sequence workflow pattern you can visit: Sequence pattern.

Defining needed interface

Next step is creating a set of interfaces which allow us to easy create workflow and define a workflow actions.

We can start with Workflow interface. This interface is responsible for processing workflow action, and actually it define what our workflow engine should to do. It is really simple interface with only one method ‘processWorkflow’.

This method is called by workflow engine and it used to supply workflow with initial objects which can be used inside of workflow, and it represent starting point of each workflow.

package ba.codecentric.workflow;

import java.util.Map;

/**

* Process email workflow.

*

* @author igor.madjeric

*

*/

public interface Workflow {

/**

* Method for processing workflow.

*

* @param parameters

* maps of object which are needed for workflow processing

* @return true in case that workflow is done without errors otherwise false

*/

public boolean processWorkflow(Map<String, Object> parameters);

}

Next what we need is interface used for defining workflow action. This is also simple interface whit only one method too.

package ba.codecentric.workflow;
/**

* Define workflow action

*

* @author igor.madjeric

*

*/

public interface WorkflowAction {

/**

* Execute action.

*

* @param context

* @throws Exception

*/

public void doAction(Context context) throws Exception;

}

So this interface define only doAction method which will be called by workflow implementation.

Last interface which we need to define is Context interface. This interface define two methods, one for setting object in context and another for retrieving it.

package ba.codecentric.workflow;

/**

* Context interface.

*

* Class which extend this interface should be able to provide mechanism for keeping object in context.<br />

* So they can be shared between action inside workflow.

*

* @author igor.madjeric

*

*/

public interface Context {

/**

* Set value with specified name in context.

* If value already exist it should overwrite value with new one.

*

* @param name of attribute

* @param value which should be stored for specified name

*/

public void setAttribute(String name, Object value);

/**

* Retrieve object with specified name from context,

* if object does not exists in context it will return null.

*

* @param name of attribute which need to be returned

* @return Object from context or null if there is no value assigned to specified name

*/

public Object getAttribute(String name);

}

This is all interfaces which we need to define for the our simple workflow

Implementing simple workflow engine

After we defined interfaces we can start with implementation of workflow engine. There is a some of requirements what engine should be able to do.

This engine should support sequence workflow what mean that action are executed one after another.

Also the engine should be able to precess more then one flow.

Workflow action should be able to share information between each other.

As we see there is no lot of requirements so we should start with implementing it.

First of all we can create context class which will be used for handling information between actions. This class implement Context interface, and don’t do much other stuff.

package ba.codecentric.workflow.impl;

import java.util.HashMap;
import java.util.Map;
import ba.codecentric.workflow.Context;

/**
* Save states between different workflow action.
*
* @author igor.madjeric
*
*/
public class StandardContext implements Context {

private Map<String, Object> context;

/**

* Create context object based.
*
* @param parameters
*/
public StandardContext(Map<String, Object> parameters) {
if (parameters == null) {
this.context = new HashMap<String, Object>();
} else {
this.context = parameters;
}
}

@Override
public Object getAttribute(String name) {
return context.get(name);
}

@Override
public void setAttribute(String name, Object value) {
context.put(name, value);
}

}

Second step is creating class which implementing Workflow interface. We called this class StandardWorkflow. Beside implementing Workflow interface this class also implement ApplicationContextAware interface because of need for accessing to spring bean repository. If you don’t use spring you don’t need to implement it.

We already said that workflow should support more then one flow.
So action of one workflow can be defined as a list, and every of this list should be assigned to some logical name. So for the registration of actions we can use something like Map<String, List<WorkflowAction>>. First we will see spring bean definition of StandardWorkflow and of one custom flow and after that we will see implementation of StandardWorkflow.

Bean definition of StandardWorkflow:

<bean id='standardWorkflow'

class='de.codecentric.oev.external.services.workflow.standard.StandardWorkflow'>

<property name='workflowActions'>

<map>

<!-- <entry key='<CID>_action'><ref bean='<CID>_action'/></entry>-->

<!-- OEVBS -->

<entry key='action1_action'>

<ref bean='action1_action' />

</entry>

<!-- PVN -->

<entry key='action2_action'>

<ref bean='action2_action' />

</entry>

<!-- WPV -->

<entry key='action3_action'>

<ref bean='action3_action' />

</entry>

</map>

</property>

</bean>

From this bean definition we can see that we define action per customer and the list of action are defined in referenced beans.

Here is an example of one of that customer beans:

<bean id='action1_action' class='java.util.ArrayList'>

<constructor-arg>

<!-- List of Actions -->

<list value-type='ba.codecentric.workflow.WorkflowAction' >

<ref local='createEmailAction'/>

<ref bean='sendEmailAction'/>

</list>

</constructor-arg>

</bean>

Now we can see how StandardWorkflow look likes:

package ba.codecentric.workflow.impl;

import java.util.List;

import java.util.Map;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

import ba.codecentric.workflow.Context;

import ba.codecentric.workflow.Workflow;

import ba.codecentric.workflow.WorkflowAction;

/**

* Define standard workflow for sending email.

*

* @see Workflow

*

* @author igor.madjeric

*

*/

public class StandardWorkflow implements Workflow,

ApplicationContextAware {

private final Log LOG = LogFactory.getLog(StandardWorkflow.class);

private static final String ACTION = 'action';

private Map<String, List<WorkflowAction>> workflowActions;

private ApplicationContext applicationContext;

/**

*@see de.codecentric.oev.external.services.workflow.Workflow#processWorkflow(java.util.Map)

*/

@Override

public boolean processWorkflow(String workflofName, Map<String, Object> parameters) {

Context context = new StandardContext(parameters);

List<WorkflowAction> actions = getWorkflowActions(workflofName);

for (WorkflowAction action : actions) {

try {

action.doAction(context);

} catch (Exception e) {

StringBuilder message = new StringBuilder(
'Failed to complete action:' + action.toString());

message.append('\n');

message.append(e.getMessage());

LOG.error(message.toString());

return false;

}

}

return true;

}
private List<WorkflowAction> getWorkflowActions(String actionName) {

List<WorkflowAction> actions = workflowActions.get(actionName);

if (actions == null || actions.isEmpty()) {

LOG.error('There is no defined action for ' + actionName);

throw new IllegalArgumentException(
'There is no defined action for ' + actionName);

}

return actions;

}
@Override

public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException{
this.applicationContext = applicationContext;

}
// Getter/Setter

public Map<String, List<WorkflowAction>> getWorkflowActions() {

return workflowActions;

}
public void setWorkflowActions(

Map<String, List<WorkflowAction>> workflowActions) {

this.workflowActions = workflowActions;

}
}

Again as you can see this also is a simple class all work is done in processWorkflow method, to which we provide flow name and input parameters. This method create Context with a specified parameter, after that it try to load actions defined for specified flow, and if there is flow with specified name it start running flow.

How to start the flow

This depend of yours need. You can use a rest services such we, or use any other mechanism like MBeans, scheduled jobs or you can make direct call from some of your services. All what you need to do is to call processWorkflow method.
 

Reference: Simple Workflow Engine With Spring from our JCG partner Igor Madjeric at the Igor Madjeric blog.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
mani
mani
7 years ago

please upload source code

Cesar
Cesar
6 years ago

Can you give an example of how to call the processWorkflow method?

Back to top button