易拓展、易修改的状态流程设计和实现

1,前言

Workflow(https://en.wikipedia.org/wiki/Workflow)是一个极其常见的业务场景,基本所有行业都能涉及到流程管理上的问题。工作流,个人认为可以等价的理解为状态流(state flow),因为工作流的主要工作就是流程管理或者就是状态转移。如果用状态转移来抽象描述问题的话,基本大多数业务系统都可以状态转移来描述,且不说OA、ERP等软件,在常见交易系统软件里产品管理的流程、在线交易系统里订单的各个状态流程等。

使用状态转移来描述问题的优势是语义简洁、易于图像化描述(计算机专业所熟知的计算理论中的状态机)。日前已有像Spring webflow、jBPM等开源workflow实现,但本文对这些不相关,本文主要是围绕使用Spring Statemachine(http://projects.spring.io/spring-statemachine/),以一个具体应用实例来设计和实现一个简单基本但易拓展、易修改的状态流程。

2,问题描述

在一些网络上的金融产品平台上,我们可以看到各种类型的定期理财产品。这些理财产品实际都是金融公司依据国家的一些监管要求,打包成的一系列金融资产。所以这些资产在平台上正式对外公开募集资金前,实际是需要是需要经过一系列评审流程的。例如假设有一笔资产A,对这笔资产首先需要发起一场沟通会议,然后风控经理做好第一道最重要的风控问题、再是产品经理审批,最后进行评审、表决是否通过、拒绝或待议。

既然是状态转移问题,忽略异常状态和中间状态终止或转移流程,我们通过语义来描述上述问题的正常流程:

1) 状态定义: Meeting("项目沟通会"), RiskManager("风控经理"), ProdManager("产品经理"), Review(“评审"), Pass("通过"), Reject("否决"), Discussing("待议");

2) 状态转移事件: {from=Meeting, to=RiskManager, on=PASS_MEETING("通过项目沟通会")},

{from=RiskManager, to=ProdManager, on=PASS_RISK_MANAGER("风控经理审核通过")},

{from=ProdManager, to=Review, on=PASS_PROD_MANAGER("产品经理审核通过")},

{from=Review, to=Pass, on=REVIEW_PASS("通过")},

{from=Review, to=Reject, on=REVIEW_REJECT("否决")},

{from=Review, to=Discussing, on=REVIEW_DISCUSSING("待议")};

图1

问题如果使用状态机描述的话,则如图1所示。

3,领域建模

图2 领域抽象

从图2中可以看出,资产评审的领域核心Entity即是Review。State和Feature是刻画Review状态转移的基本组件,如第2部分语义所描述的。状态转移的发生都是通过FeatureEvent来触发,从FeatureEvent我们可以看到每一个Feature都是对应一个单例的触发Event,每一个FeatureEvent都标明了它将使Review从具体一个状态跳转到另一个状态。Review的构成除了基本user和asset数据外,featureCode和state记录了该Review的状态转移语义。state表明该Review当前所处的状态,featureCode标识了对应的触发状态转移的FeatureEvent。特别的,所有的流程都应当有一个初始的状态,这就是Review#initial()需要完成的工作。

4,编码实现

4.1, 领域核心实现

Review.java

  1 import org.springframework.beans.factory.support.StaticListableBeanFactory;
  2 import org.springframework.core.task.SyncTaskExecutor;
  3 import org.springframework.messaging.Message;
  4 import org.springframework.statemachine.StateMachine;
  5 import org.springframework.statemachine.config.StateMachineBuilder;
  6 import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
  7 import org.springframework.statemachine.listener.StateMachineListenerAdapter;
  8 import org.springframework.statemachine.transition.Transition;
  9 import org.springframework.util.Assert;
 10
 11 import java.util.*;
 12
 13 /**
 14 * @author shenjixiaodao
 15 */
 16 public class Review {
 17     private Long reviewId;
 18     private Users user;
 19     private String featureCode;
 20     private Assets asset;
 21     private State state;
 22
 23     public enum State{
 24          Meeting("项目沟通会"), RiskManager("风控经理审批"),
 25          ProdManager("产品经理审批"), Review("评审"),
 26          Pass("评审通过"), Reject("否决"), Discussing("待议");
 27          private String desc;
 28         State(String desc) {
 29             this.desc = desc;
 30         }
 31         public String getDesc(){return this.desc;}
 32     }
 33
 34     private StateMachine<State, FeatureEvent> machine = null;
 35 /**
 36 * 触发状态转移动作
 37 * @return true:允许状态转移
 38 */
 39     public boolean fire(){
 40         return this.fireEvent(FeatureEvent.codeOf(featureCode));
 41     }
 42     private boolean fireEvent(FeatureEvent event){
 43         try {
 44             if(machine == null) {
 45                 machine = buildSyncMachine();
 46                 machine.addStateListener(new StateMachineListenerAdapter<State, FeatureEvent>() {
 47                     @Override
 48                     public void eventNotAccepted(Message<FeatureEvent> msg) {
 49                         FeatureEvent event = msg.getPayload();
 50                         StringBuilder appender = new StringBuilder();
 51                         appender.append("【").append(event.featureName()).append("】只能将资产从")
 52                                 .append(event.reviewState()).append("修改为").append(event.nextState());
 53                         throw new IllegalArgumentException(appender.toString());
 54                     }
 55
 56                     @Override
 57                     public void transitionEnded(Transition<State, FeatureEvent> transition) {
 58                         FeatureEvent event = transition.getTrigger().getEvent();
 59                         state = event.nextState();
 60                     }
 61                 });
 62             }
 63             return machine.sendEvent(event);
 64         } catch (Exception e) {
 65             throw new RuntimeException(e);
 66         }
 67     }
 68
 69     public Review(Integer userId, Integer assetId, String featureCode) {
 70         this.user = new Users();
 71         this.user.setId(userId);
 72         this.asset = new Assets();
 73         this.asset.setId(assetId);
 74         this.featureCode = featureCode;
 75     }
 76
 77     public static Review initial(Integer assetId){
 78         Review review = new Review();
 79         review.asset = new Assets();
 80         review.asset.setId(assetId);
 81         review.state = State.Publish;
 82         return review;
 83     }
 84
 85     public void setReviewId(Long reviewId) {
 86         this.reviewId = reviewId;
 87         for(Attachment attachment:attachments)
 88             attachment.setReviewId(reviewId);
 89     }
 90
 91     public State state() {
 92         return state;
 93     }
 94
 95     public Review state(State state) {
 96         this.state = state;
 97         return this;
 98     }
 99
100     Review() {
101         //for ORM
102     }
103 /**
104 * 构建线程同步状态机
105 */
106     private StateMachine<State, FeatureEvent> buildSyncMachine() throws Exception {
107         Assert.notNull(state,"状态不能为空");
108         StateMachineBuilder.Builder<State, FeatureEvent> builder = StateMachineBuilder.builder();
109         builder.configureConfiguration().withConfiguration()
110                 .taskExecutor(new SyncTaskExecutor())
111                 .beanFactory(new StaticListableBeanFactory())
112                 .autoStartup(true);
113         //配置状态
114         builder.configureStates()
115                 .withStates()
116                 .initial(state)
117                 .states(EnumSet.allOf(State.class));
118         //配置状态转移
119         StateMachineTransitionConfigurer<State, FeatureEvent> transition = builder.configureTransitions();
120         for(FeatureEvent event:FeatureEvent.values()) {
121             transition = transition.withExternal()
122                     .source(event.reviewState())
123                     .target(event.nextState())
124                     .event(event)
125                     .and();
126         }
127         return builder.build();
128     }
129
130 }

从Review的状态转移实现主要依赖 buildSyncMachine()方法,在 buildSyncMachine()方法里使用Spring Statemachine(的使用在本文不作描述)实现了第2部分描述的状态转移语义,定义状态和状态转移事件。fireEvent(FeatureEvent event)是触发review发生状态转移的动作,其中主要是实现对拒绝动作和状态正确转移地操作。

4.2,调用接口设计

ReviewServiceImpl.java

 1 import org.springframework.beans.factory.annotation.Autowired;
 2 import org.springframework.stereotype.Service;
 3 import org.springframework.transaction.annotation.Transactional;
 4 import java.util.List;
 5
 6 /**
 7 * @author shenjixiaodao
 8 */
 9 @Service
10 public class ReviewServiceImpl implements ReviewService {
11
12     @Autowired
13     private ReviewRepository repository;
14
15     @Transactional
16     public void review(Review review) {
17         //// FIXME: 2017/7/10 检查资产是否存在
18         Review lastReview = repository.findLastUpdated(review.asset().getId());
19         boolean accept = review.state(lastReview.state()).fire();
20         if(!accept) return;
21         repository.save(review);
22     }
23
24 }

如上ReviewServiceImpl.java源码所示,定义了一个ReviewService#review(Review)接口来执行所有评审动作。从review(Review)的实现源码看,在触发状态转移之前,我需要从数据库中恢复Review当前所处的状态。最后如果状态迁移成功,则将Review持久化存储。

时间: 2024-10-27 11:50:20

易拓展、易修改的状态流程设计和实现的相关文章

System Center 2012 R2 POC部署之Orchestrator设计监控DHCP服务状态流程

System Center 2012 R2 POC部署之Orchestrator设计监控DHCP服务状态流程 本文介绍如何设计流程以监控DHCP服务状态,当服务停止时触发流程自动启动DHCP服务. 打开Runbook Designer,右键Runbook---新建---文件夹 输入文件夹名称Monitor 右键Monitor---新建----Runbook 输入Runbook名称 Monitor DHCP 从右侧窗口中,找到SC 2012 Operations Manager中的Monitor

佩特来项目经验小集合(5)___系统流程设计

在佩特来项目设计中有一个流程设计问题,虽然.NET 和Java都有工作流,但是考虑到这个项目小,这里就简单的借用一点工作流的思想,设计了几张表,然后通过代码来控制流程.下面以"维修鉴定单业务流程"中的有实物流程为例,谈一下具体的流程设计.有实物的维修鉴定业务流程包含大致步骤:代理商填单.打印二维码.拆包.沟通转办.拆分.故障分析.各角色对费用进行审批.费用提交到费用池(统计各代理商金钱的地方).维修鉴定单流程见下图: 因为系统中不止这一个业务流程,所以系统流程设计的表有任务表(如维修鉴

流程设计--页面介绍

上图为流程设计器的主界面,左边栏目为树形结构,提供两种树形,一个为流程定义的分类的树形,另外一个为根据模块菜单的树形. [功能描述] 1.新增:新增功能主要处理定义的工作流程的建立. 2.删除:删除定义的工作流,注意,删除只能删除处于设计状态的工作流,处于发布和停用状态的工作流是不允许删除的. 3.修改:处于设计状态下的工作流是可以继续修改的. 4.复制:提供流程模版的拷贝功能,生成新的工作流程.再继续修改. 5.分组:将设计的工作流程归分到所属的分类目录中去,便于查找和分类. 6.导出:提供工

流程设计--引擎理念

工作流引擎是指workflow作为应用系统的一部分,并为之提供对各应用系统有决定作用的根据角色.分工和条件的不同决定信息传递路由.内容等级等核心解决方案.工作流引擎是为工作流实例提供执行环境,它是工作流管理系统的核心服务,因此,工作流引擎设计的好坏直接关系到工作流的执行效率与可扩展性. 工作流引擎是工作流管理系统中为过程实例和活动实例提供运行环境的服务软件,是整个工作流管理系统的核心,工作流引擎的主要功能有: (1).解释流程定义 (2).创建流程实例(人工创建.自动创建:自动创建又包括自动定时

流程设计-流程模式

工作流基本包含如下多种模式用于工作流过程建模和分析: (1).基本模式 5种 ● 顺序模式---- 按照顺序执行各项活动 ● 并行分支模式----同时运行两个活动 ● 同步模式----同步两个并行的执行线程 ● 单选模式----从多条路径中选择一个执行 ● 简单合并模式----合并两个二选一路径 (2).高级分支与同步模式 5种 ● 多选模式----从多条执行路径中选出几条 ● 同步合并模式----合并多条路径,如果有多条路径被选择,则进行同步:如果只有一条路径被选择,则进行简单合并 ● 多合并

流程设计(抽象节点法)

如何设计一个配置灵活的流程? 要做到以下几点 1.节点状态.节点按钮可以动态的配置,不会影响流程的运行,可以在现有的流程中更改流程的走向 2.节点可以对应多个按钮,即流程进入某一节点的时候可以显示多个按钮 FlowNode 流程节点状态,流程节点状态用于表达流程的状态 那么NodeState和ButtonState分别代表什么意思呢? 为什么需要两种状态?举个简单的例子,以一个外卖员接单为例,当外卖员接单后,可能会有两个按钮供他选择,受理订单和拒绝受理(假如超出了他的配送范围),假如受理订单,我

审核流(2)流程设计-SNF.WorkFlow功能使用说明--SNF快速开发平台3.1

流程设计 图形化的流程设计,更方便.直观 1.打开“流程设计“程序,如上.点击”新建“如下: 2.红色部分为必填项,审批对象是选择要审批的程序菜单,单据名称是在审核流流转时用于提示的单据名称,还要选择审核的数据表和审核状态字段.当维护完成后进行“确定“保存.再点击 进行图形化设计审核流. 3.从左边拖拽审核节点和连接线. 4.双击节点 ,设计节点此节点的名称.审核人.投票比例等. 注:可以选择多人再结合投票设置可以达到会审需求. 5.双击连接线可以配置走此分支需要具备的条件.如果业务人员可以用鼠

流程中心使用详情(三)流程设计

第4章流程使用说明 以[出差申请]流程为例,讲述如何通过流程中心定义一个完整的流程. 4.1 新建分类 流程中心->流程设置->分类设置,新建[行政管理]分类,如下图所示: 点击[保存]后,界面如下图所示: 此菜单下的流程分类与工作流中的流程分类概念是一致的.流程分类更方 便了流程的管理,把不同性质的流程放在不同的分类下,也方便了流程的查 找.同时根据流程分类的所属部门,实现了流程分类按部门进行独立管理的目 的. 4.2 新建流程 点击[新建流程],进入新建流程的界面,如下图所示: 在该界面上

jQuery+jsPlumb简易流程设计

jsPlumb是一个比较强大的绘图组件,它提供了一种方法,主要用于连接网页上的元素. 1.主要三个JS组件:jQuery.jQuery UI.jsPlumb 2.结果展示: 3.源码地址: 源码下载 4.注意 源码中test.jsp中JS和CSS的引入根据自己项目的实际位置引入. String contextPath = request.getContextPath();       if ("/".equals(contextPath))        {            co