幾個月前,在處理一個公司項目時,我們需要開發REST服務,該服務用于根據客戶端應用程序發送的數據發送電子郵件。 在開發此服務期間,我們決定創建簡單的工作流引擎,該引擎將為發送電子郵件收費,但該引擎也可用于任何類型的簡單流。
在本文中,我將逐步說明如何實現可處理序列流的簡單工作流引擎。
為了實現此工作流引擎,我們使用了spring框架,但是無論使用哪種框架,也可以不使用任何框架,如何在任何框架上實現該想法都應相同。
我們將從對序列工作流模式的簡短介紹開始,然后,我們將研究所需的接口,最后,我們將從使用Spring實現工作流引擎開始。
序列工作流程模式
序列工作流程模式描述了其中每個步驟(動作)一步一步地完成的工作流程。 在下一張圖片上,您可以看到它的外觀:
流中要處理的每個動作都共享相同的上下文,這使流的參與者之間可以共享信息。 使用公共上下文的想法是因為每個步驟都應該彼此獨立,并且應該將它們作為其他流程的一部分輕松添加。
如果要獲取有關序列工作流程模式的更多信息,請訪問: 序列模式 。
定義所需的界面
下一步是創建一組接口,使我們可以輕松創建工作流程并定義工作流程操作。
我們可以從Workflow界面開始。 此接口負責處理工作流程操作,實際上它定義了我們的工作流程引擎應該執行的操作。 這是一個非常簡單的界面,只有一種方法“ processWorkflow”。
此方法由工作流引擎調用,用于為工作流提供可在工作流內部使用的初始對象,它表示每個工作流的起點。
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);}
這是我們為簡單工作流程需要定義的所有接口
實施簡單的工作流引擎
定義接口后,我們可以從實現工作流引擎開始。 引擎應具備的功能有一些要求。
該引擎應支持順序工作流程,這意味著一個接一個地執行動作。
發動機也應該能夠進動多于一個的流量。
工作流操作應該能夠彼此共享信息。
如我們所見,并沒有很多要求,所以我們應該從實現它開始。
首先,我們可以創建上下文類,該上下文類將用于處理動作之間的信息。 此類實現Context接口,并且不執行其他任何操作。
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);
}}
第二步是創建實現Workflow接口的類。 我們稱此類為StandardWorkflow。 除了實現Workflow接口之外,該類還實現了ApplicationContextAware接口,因為需要訪問spring bean存儲庫。 如果您不使用spring,則不需要實現它。
我們已經說過,工作流應該支持一個以上的流程。
因此,可以將一個工作流程的操作定義為一個列表,并且每個列表都應分配一個邏輯名稱。 因此,對于動作注冊,我們可以使用諸如Map <String,List <WorkflowAction >>之類的東西。 首先,我們將看到StandardWorkflow和一個自定義流程的spring bean定義,然后我們將看到StandardWorkflow的實現。
Bean的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>
從這個bean定義中,我們可以看到我們為每個客戶定義了操作,并且在引用bean中定義了操作列表。
這是其中一個客戶Bean的示例:
<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>
現在我們可以看到StandardWorkflow的樣子:
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)*/@Overridepublic 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;}
@Overridepublic void setApplicationContext(ApplicationContext applicationContext)
throws BeansException{
this.applicationContext = applicationContext;}
// Getter/Setterpublic Map<String, List<WorkflowAction>> getWorkflowActions() {return workflowActions;}
public void setWorkflowActions(Map<String, List<WorkflowAction>> workflowActions) {this.workflowActions = workflowActions;}
}
再次您可以看到,這也是一個簡單的類,所有工作都在processWorkflow方法中完成,我們向其提供流程名稱和輸入參數。 此方法使用指定的參數創建Context,然后嘗試加載為指定的流定義的操作,如果存在具有指定名稱的流,它將開始運行流。
如何開始流程
這取決于您的需要。 您可以使用我們這樣的休息服務,也可以使用其他任何機制,例如MBean,預定作業,也可以直接從某些服務中進行呼叫。 您需要做的就是調用processWorkflow方法。
參考:來自ICG Madjeric博客的JCG合作伙伴 Igor Madjeric的Spring提供的簡單工作流引擎 。
翻譯自: https://www.javacodegeeks.com/2012/11/simple-workflow-engine-with-spring.html