OSworkflow Feasibility Report
What is the OSworkflow?
OSworkflow是一款开源的框架。提供了非常灵活的处理业务流程的解决方案。可以通过手动编写XML文件来描述一个流程,也可以通过图形工具完成流程的定义。
Which kinds of questions can we solve by using OSworkflow ?
使用OSworkflow,我们可以解决如下问题:
当完成一个业务流程需要几个步骤(Step),每个步骤由不同的人来参与,每个步骤根据条件的不同,可以完成不同的动作(Action),步骤与步骤之间,动作与动作之间,可能又存在其他的条件限制,先后顺序 …… 根据业务逻辑的不同,可能有各种各样的关系。
如果您目前的项目存在上面的业务逻辑,或者存在类似的业务需求。
那么按照传统的做法,项目起初会针对业务逻辑划分系统的功能,设计系统的层次,定义系统架构(architecture)…… 而且最重要的是,需要将系统的流程逻辑溶入到系统架构之中,一些逻辑会散布在系统的某一层或者某几层之中。
当然一个有经验的架构师(architect)可以将系统设计的很好、很清晰。但是随着业务逻辑的变化,需求的变更,系统架构会被修改的面目全非,因此,越来越多的人感觉到将流程逻辑与业务逻辑分开的重要性。
如果能通过配置,方便地修改流程逻辑,同时又不用担心对业务逻辑产生影响,那么这样的系统是强壮的(strong)。
OSworkflow 就完成这样的需求,利用它,您可以将某些业务逻辑挂在流程的某个步骤的某个方法之中,从而可以方便的定制流程,同时不用担心对业务逻辑的影响。
A practical example :
下面介绍一下我要实现的功能。
(a user story)
实现一个请假申请审批流程。
(use casees)
一个员工请假系统:
雇员(employee)提出申请(发起流程),部门经理查看所有的请假申请,填写审批意见,同意后,由人事部门(Human Resource Department)进行审批,根据申请天数判断,如果申请大于3天,那么由总经理审批(the boss the highest power)小于3天申请成功。流程结束。期间任何一步被拒绝,流程都将归档,并告知申请人。
Concrete realization
Working environment
为了更好的使用OSworkflow,我们使用了一下几个框架:
webwork- 2.1.7 、spring-1.2.8、hibernat-2.1、osworkflow-2.8.0、ibatis-2.1.7.597、junit。
database : mysql-4.*
Quick Start
The picture can help you to better understand the relevant configuration information.
存储流程变量有几种方式:
内存管理
数据库存储
数据库存储流程变量可以有2种实现:
使用tomcat的JNDI
使用 spring 管理hibernate的数据源,看上去这样的实现更有意义。
I used spring and hibernate framework to integrat another framework:osworkflow
If you want to know how to use the frist implement, you can ask me ,
I’ll give you the solution J
I’ll show you the second implement.
Actually , what you need are only two configuration files.
applicationcontext.xml & leave.xml
Applicationcontext.xml 用来整合 Osworkflow & hibernate 以及之后的webwork framework,如果有业务层对象,那么也可以利用ioc的方式写在该配置文件中。
leave.xml 流程的描述都在其中。
有人一定会问两个文件是如何关联在一起的,他们是如何知道对方存在呢?
Someone will ask how these two files correlate, and how they get to know each other's existence? L
我用一个工具类SpringConfig.java去将他们关联在一起的。
这个方法大家一定不会陌生。
public static XmlBeanFactory getXmlBeanFactory(){
if (xmlBeanFactory == null) {
xmlBeanFactory
= new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
}
return xmlBeanFactory;
}
利用Interface Workflow 的initialize(….)方法,可以将/leave.xml做为参数传入进去。
这样就回答了上面的问题。
我想这只是问题的开始,^_^ 让我来看看具体是如何实现的吧。
<beans>
<!-- DataSource Property -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/osworkflow</value>
</property>
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="hibernateProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<!--<prop key="hibernate.hbm2ddl.auto">update</prop>-->
<prop key="hibernate.dialect">
net.sf.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.query.substitutions">
true 'T', false 'F'
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.c3p0.minPoolSize">1</prop>
<prop key="hibernate.c3p0.maxPoolSize">10</prop>
<prop key="hibernate.c3p0.timeout">600</prop>
<prop key="hibernate.c3p0.max_statement">50</prop>
<prop key="hibernate.c3p0.testConnectionOnCheckout">
false
</prop>
<prop key="hibernate.c3p0.idle_test_period">3000</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="hibernateProperties">
<ref bean="hibernateProperties" />
</property>
<!-- OR mapping files. -->
<property name="mappingResources">
<list>
<value>
com/opensymphony/workflow/spi/hibernate/HibernateCurrentStep.hbm.xml
</value>
<value>
com/opensymphony/workflow/spi/hibernate/HibernateHistoryStep.hbm.xml
</value>
<value>
com/opensymphony/workflow/spi/hibernate/HibernateWorkflowEntry.hbm.xml
</value>
<value>
com/opensymphony/module/propertyset/hibernate/PropertySetItemImpl.hbm.xml
</value>
</list>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<bean id="workflowStore"
class="com.opensymphony.workflow.spi.hibernate.SpringHibernateWorkflowStore">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<bean id="workflowFactory"
class="com.opensymphony.workflow.loader.URLWorkflowFactory" />
<bean id="osworkflowConfiguration"
class="com.opensymphony.workflow.config.SpringConfiguration">
<property name="store">
<ref local="workflowStore" />
</property>
<property name="factory">
<ref local="workflowFactory" />
</property>
</bean>
<bean id="workflow"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="transactionManager" />
</property>
<property name="target">
<ref local="workflowTarget" />
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="workflowTarget"
class="com.opensymphony.workflow.basic.BasicWorkflow"
singleton="false">
<constructor-arg>
<value>test</value>
</constructor-arg>
<property name="configuration">
<ref local="osworkflowConfiguration" />
</property>
</bean>
</beans>
这是整合spring&hibernate&OSworkflw的一些关键配置,大家仔细看一定能看的懂这个配置文件的,不具体细说了,强调一下:
<prop key="hibernate.hbm2ddl.auto">update</prop>
它是很使用的一个配置。意思是说,根据hibernate的映射文件自动生成数据库的表。
What a useful configuration. :D
前期准备好了,我的习惯,真对每个环节和可能完成的功能写个测试先(test driven design)
Leave.xml内容如下:
<workflow>
<initial-actions>
<action id="0" name="开启请假流程">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
<function type="beanshell">
<arg name="script">
propertySet.setString("type",
transientVars.get("type"));
propertySet.setString("day",
transientVars.get("day"));
propertySet.setString("user",
transientVars.get("user"));
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="1" owner="${caller}" />
</results>
<post-functions>
<function type="class">
<arg name="class.name">
com.woaika.osworkflow.util.OutputPropertySet
</arg>
<arg name="author">童伯</arg>
</function>
</post-functions>
</action>
</initial-actions>
<steps>
<step id="1" name="填写请假单">
<actions>
<action id="1" name="发送申请">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="2" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="2" name="部门经理审批">
<actions>
<action id="2" name="同意">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="3" owner="${caller}" />
</results>
</action>
<action id="3" name="驳回">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="1" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="3" name="人事部门复审">
<actions>
<action id="4" name="同意">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<result old-status="Finished" status="Queued" step="4" >
<conditions type="AND">
<condition type="beanshell">
<arg name="script">
Integer.parseInt(propertySet.getString("day"))>3;
</arg>
</condition>
</conditions>
</result>
<unconditional-result old-status="Finished"
status="Queued" step="5" owner="${caller}" />
</results>
</action>
<action id="5" name="驳回">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="5" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="4" name="经理审批">
<actions>
<action id="7" name="同意">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="5" owner="${caller}" />
</results>
</action>
<action id="8" name="驳回">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="5" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="5" name="一个流程over" />
</steps>
流程变量可以使用默认OSworkflow提供的4张表来完成。但是最好的方法是将流程变量和我们需要用的其他的信息,写到我们自己建立的一张表中:
CREATE TABLE `os_flowvariable` (
`user` varchar(80) NOT NULL default '',
`flowid` int(11) NOT NULL default '0',
`state` varchar(20) default NULL,
`step` int(11) NOT NULL default '0',
`date` varchar(80) default NULL,
`notion` varchar(80) default NULL,
PRIMARY KEY (`user`,`flowid`,`step`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
这里没有把请假时间放到里面,很遗憾,I will be refactoring it. J !
对这张表的操作,我没有使用hibernate,我用了ibatis,没有别的意思,只是ibatis用的多一些。(但是这样做是不合理的 !!!!)
单元测试完成以后,直到看到传说中的幸福的小绿条以后就可以放心大胆去写页面的逻辑。毕竟得让别人看到实际的效果(结果很重要!)
有了单元测试的保障,完成这个例子就是早晚的事了。工作量的问题 ……
利用OSworkflow可以对这个问题做出很灵活的解决方案(solution)。这说明OSworkflow在处理流程的时候可以发挥出巨大的作用。可以帮助设计人员,将系统设计变得模块化,实现单一职责原则(Single-Responsibility Principle)。提高系统的稳定性和健壮性。
以上内容已经基本上描述了几个最重要的细节,这些细节也是让我开始最为棘手的问题。分享给大家。如果大家还有实现上的问题。欢迎大家通过email和我联系:
if you have any questions, please feel free to ask. I’m very glad to help you out.
这里非常感谢我的leader:石鑫 ,我的朋友们:李瑞强,刘创给予我的帮助。谢谢!也希望这个文档可以给想要使用osworkflow的朋友一些帮助。