关于Domain Model的讨论已经非常多了,炒炒冷饭,这里是自己的一些做法。
以Workitem(工作流里的工作项)作为例子
最开始的做法:
一个实体类叫做Workitem,指的是一个工作项或者称为任务项
一个DAO类叫做WorkitemDao
一个业务逻辑类叫做WorkitemManager(或者叫做WorkitemService)
主要看看WorkitemManager,因为主要逻辑集中在这里
public class WorkitemManager {
private WorkItemDAO workItemDAO;
public void setWorkItemDAO(WorkItemDAO workItemDAO) {
this.workItemDAO = workItemDAO;
}
/**
* 提交工作项
* @param workitemId 工作项ID
*/
public void commitWorkitem(String workitemId){
WorkItem workitem = workItemDAO.getWorkItem(workitemId);
//当前工作项结束
workitem.complete();
int sID = workitem.getSequenceId();
//找到所对应的节点
InstActivity instActivity=workitem.getInstActivity();
//查找是否存在下一工作项
WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);
//如果不存在则触发节点流转
if (sequenceWorkitem == null) {
instActivity.signal();
}
//否则把下一工作项激活
else {
sequenceWorkitem.setExecutive();
}
}
}
Workitem类里有一些状态转换的逻辑,这样避免直接调用get/set属性方法
public class Workitem{
private int state = WorkitemInfo.PREPARE;
/**
* 委派工作项
*/
public void commission() {
if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED
&& state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)
throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);
setState(WorkitemInfo.COMMISSIONED);
setCommitted(new Timestamp(System.currentTimeMillis()));
}
/**
* 完成工作项
*/
public void complete() {
if (state != WorkitemInfo.SIGNINED)
throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);
setState(WorkitemInfo.COMPLETE);
setCompleted(new Timestamp(System.currentTimeMillis()));
}
}
接下来的做法:
三个类不变,将WorkitemManager打平,将逻辑移动到Workitem
public class WorkitemManager {
private WorkItemDAO workItemDAO;
public void setWorkItemDAO(WorkItemDAO workItemDAO) {
this.workItemDAO = workItemDAO;
}
/**
* 提交工作项
* @param workitemId 工作项ID
*/
public void commitWorkitem(String workitemId){
WorkItem workitem = workItemDAO.getWorkItem(workitemId);
//当前工作项提交
workitem.commit();
}
}
实际上此时WorkitemManager的功能非常有限,仅仅是事务边界和获取workitem对象,甚至在一些情况下可以省略。
通过一个Container类将spring的applicationContext进行封装,然后通过getBean()的静态方法即可访问被spring所管理的bean。实际是将workItemDAO隐式注入了Workitem。
public class Workitem{
/**
* 提交工作项
*/
public void commit() {
if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED
&& state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)
throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);
setState(WorkitemInfo.COMMISSIONED);
setCommitted(new Timestamp(System.currentTimeMillis()));
int sID = workitem.getSequenceId();
WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO");
//查找是否存在下一工作项
WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);
//如果不存在则触发节点流转
if (sequenceWorkitem == null) {
instActivity.signal();
}
//否则把下一工作项激活
else {
sequenceWorkitem.setExecutive();
}
}
}
这样带来的好处是业务逻辑全部被封装到Domain Model,Domain Model之间的交互变得非常的简单,没有频繁的set/get,直接调用有业务语义的Domain Model的方法即可。问题在于单元测试时脱离不了spring的容器,workItemDAO需要stub。我觉得这个问题不大,问题是Domain Model开始变得臃肿,在业务逻辑复杂时代码行急剧膨胀。
现在的做法
以上三个类保持不变,增加一个类WorkitemExecutor,将业务逻辑移步。
public class Workitem{
/**
* 提交工作项
*/
public void commit() {
if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED
&& state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)
throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);
setState(WorkitemInfo.COMMISSIONED);
setCommitted(new Timestamp(System.currentTimeMillis()));
WorkitemExecutor workitemExecutor=(WorkitemExecutor)Container.getBean("workitemExecutor");
workitemExecutor.commitWorkitem(this);
}
}
public class WorkitemExecutor {
private WorkItemDAO workItemDAO;
public void setWorkItemDAO(WorkItemDAO workItemDAO) {
this.workItemDAO = workItemDAO;
}
/**
* 提交工作项
* @param workitemId 工作项ID
*/
public void commitWorkitem(Workitem workitem){
int sID = workitem.getSequenceId();
//找到所对应的节点
InstActivity instActivity=workitem.getInstActivity();
//查找是否存在下一工作项
WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);
//如果不存在则触发节点流转
if (sequenceWorkitem == null) {
instActivity.signal();
}
//否则把下一工作项激活
else {
sequenceWorkitem.setExecutive();
}
}
}
将业务逻辑拆分成两部分,一部分在Workitem,另一部分委托给WorkitemExecutor。实际上是Domain Model将复杂逻辑的情况重新外包出去。调用的时候,面向的接口还是Domain Model的方法。注意到WorkitemExecutor和WorkitemManager的API是非常相似的。实际可以这样认为,传统的方式
Client->(Business Facade)->service(Business Logic 部分依赖Domain Model)->Data Access(DAO)。
现在的方式
Client->(Business Facade)->Domain Model->service->Data Access(DAO)。
另外,在返回client端的查询的时候还是倾向于直接调用DAO,而不是通过Domain Model。
改进:
注意到代码中有这么一行
WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO");
确实是一个bad smell.当代码中大量出现后,这种造型是很恐怖的。所以采取了一种处理方式:给所有Domain Model继承一个父类,在父类里集中管理所有Domain Model所依赖的services,在父类里进行造型。
posted @
2008-07-03 18:23 ronghao 阅读(2609) |
评论 (2) |
编辑 收藏
回退(Rollback WorkItem)
回退是工作流参与者对自己“待办任务”(实际是对工作项)的一种操作,即参与者主动回退待办任务列表中的任务到已经执行过的人工节点。
为什么要回退?
参与者接受任务后,发现不应由自己办理此任务或以前的执行者办理有错误等情况后,需要将此接受的任务回退给以前某个节点的执行者重新办理。
回退模式
回退的情况实际上是非常复杂的,其中包括了参与者的重新选择以及回退的条件判断等等。这里先列出常见的回退模式(其实也是我们支持的模式)。
串行
这种情况最为简单,后续节点可以回退到前续任意人工节点。回退后,节点重走。
分支
这种情况也相对简单,实际执行的分支上的节点可以回退到前续任意人工节点(不区分主支和分支)。同样,主支上的节点也可以回退到任意实际执行的分支上的节点。
可能的问题:多次回退后的回退节点选择。例如:第一次流程经过节点2、节点3到达节点5,节点5可以回退到节点1、节点2和节点3的任意一个,此时节点5回退到节点1,节点1重走,这一次流程改为经过节点4到达节点5,节点5回退时如何选择回退节点?此时的策略是以最近实际执行的分支为准,即节点5只允许回退到节点4和节点1,不允许回退到节点2和节点3。(抹去记忆)
并发
对于并发的情况,分支节点只允许在分支的节点间回退。
同理,主支节点也只允许在主支的节点间回退。
多实例汇聚
在这种情况下,节点5会产生2个实例,实际相当于继续并发。节点5根据具体哪个节点触发的它而产生回退节点。同时不允许回退到节点1以及前续的节点去。
子流程
支持子流程到父流程的回退,也支持父流程到子流程节点的回退。需要注意的是子流程节点有可能产生多个子流程实例,在这种情况下不支持父子流程之间的相互回退。
回退节点的参与者选择
默认策略是由原先节点的实际参与者重新处理,比如节点2回退到节点1,则节点1的实际参与者重新处理该节点任务。这也符合大多数实际的业务场景。
在节点任务竞争参与的情况下,提供另一种策略,即让人员重新竞争。
回退的条件判断
对于多人(或者多部门,用户)参与的工作项,提供不同的回退策略
任意人回退即回退,剩余工作项手工终止
最后提交人回退才回退
流程定义期定义该策略。
另外流程定义时提供节点可回退列表,由用户在定义期对可回退的节点进行限制。
关于业务补偿
业务补偿是一个很重要的概念,在回退的情况下需要相应的回退部分业务操作。这里由引擎提供统一的接口,返回回退路径,由客户自定义代码进行匹配处理。
关于实现
很多工作流引擎通过流程定义时绘出回退线来显式的支持回退,这种实现在业务复杂的情况下会造成流程图的异常烦琐,但是比较清晰,实现比较容易。隐式实现相比而言优点更多。
posted @
2008-06-24 09:12 ronghao 阅读(2364) |
评论 (3) |
编辑 收藏
收到这本书已经好久,甚至读完这本书都已经好久,一直想着写个书评,却一直被这事那事拖着,直到今天。我只想说,这是一本好书。
关于Hibernate似乎不必说太多。和朋友聊天,朋友说,你对Hibernate熟吗?我说,还好,用了两年了。朋友说,如果10分是满
分,你给自己打几分?我认真想了想,6分吧。说实话还真没有底气,会用而已。在此之前,我就看过一本Hibernate的书籍,《深入浅出
Hibernate》,然后就是满江红翻译的Hibernate中文手册。我想,也许没有必要再买一本Hibernate的书了,有问题查查
Hibernate中文手册就好了。
问题在于,我收到了这本书。
花了两个礼拜大概看过这本书。看的快是因为一些内容本来就很熟悉,还因为把jpa这部分全部略过了。一口气读完的。原谅自己曾经的浅薄吧,看
完这本书才发现自己对Hibernate的了解其实少的可怜,知道如何映射实体对象,知道对象的3种状态,知道save和
saveorupdate,get和load的区别,知道级联和关系控制反转。。。。知道这些就够了吗,实际差得还很远呢。这本书把以前许多零散的知识点
全部系统的串联起来,很多时候,看得我暗自流汗,哎,原来是这样的啊。
实际这本书不仅仅局限于Hibernate,我更认为是对java持久化的一个完整介绍和总结。我想,像without ejb一样,是应该人手一本的。
关于翻译,这本书被人诟病的厉害,原因是认为翻译的很差。我的感觉是,确实存在问题,但是也不至于差到网上说得那种地步。至少我读过一遍,基
本上没有碰到读不过去的地方,相反,还是比较流畅的。但是不是说没有问题,一些句子是根据上下文很快得出意思的。我想,对于刚使用Hibernate不久
的人来说,这种理解很可能就显得比较困难,会显得无所适从。另外,单纯从英文版来说,这本书也不适合作为Hibernate的入门书。另外,这本书的定价
太高,我想,这也是读者反应很激烈的一个原因。很高的期望,很贵的价格,结果不是很满意,自然谩骂的厉害。我想,出版社的读者定位、市场策略包括定价都是
存在问题的。
最后说说翻译,翻译确实不是一件轻松的事情,这点我有很深的体会。去年翻译《Enterprise AJAX》时,每天的进度只有两三页,有时一个句子要反复揣摩好长时间,真是痛苦的一个过程(但是却是收获良多)。
最后,一本好书。
posted @
2008-06-22 15:22 ronghao 阅读(2024) |
评论 (3) |
编辑 收藏
测试在sqlserver2000上进行,对工作流操作的相关方法在单元测试里进行多线程并发。测试发现sqlserver出现死锁的情况相当多,一些典型的情况:
1、对同一张表先insert再update是很快会引起死锁的,不管操作的是否是同一记录
解决方法:对于同一记录,需要调整hibernate的映射策略,使得一次insert完成操作。对于不同的记录需要在代码中手动flush,使得update先于insert。
2、对两张表进行多次update操作时,两张表交替update也会很快引起死锁
解决方法:在代码中手动flush,保证对两张表的update不会出现交替的情况。
3、部分大范围扫描的select和update混合也会导致死锁
解决方法:优化sql,尽量减少sql语句,通过给po增加持久化字段的方式减少关联查询
经过优化,大部分情况下数据库死锁的情况得以避免,另外奇怪的是通过事件探查器在死锁时并未发现锁升级的事件。但是在一些特殊情况下(例如多个并发汇聚的直接联合),死锁依旧发生。最后不得不对方法进行synchronized关键字同步,这个通过synchronized flush完成。业务方法不必同步,最后批量操作数据库时进行同步。
换oracle进行测试,在未synchronized的情况下,未发生死锁情况。由此可见sqlserver与oracle锁实现机制存在很大的差别。对sqlserver鄙视之。另,同事说,sqlserver2005后性能和机制发生了很大的变化,未测试。
补充一下我的一个最简单情况下的测试用例:
PO:
public class TestPO {
String id;
String name;
int num;
.
}
映射文件 hibernate3:
<hibernate-mapping default-access="field">
<class table="WFMS_TESTPO" name="com.eway.workflow.test.po.TestPO">
<id name="id" column="ID"><generator class="uuid" /></id>
<property name="name" column="NAME" type="string"/>
<property name="num" column="NUM" type="integer"/>
</class>
</hibernate-mapping>
被测试方法(都配置有事务):
public void testSave(int num) {
TestPO po = new TestPO();
po.setName("ronghao");
po.setNum(num);
theadTestDao.save(po);
po.setName("haorong");
}
public void testSaveByJdbc(int num) {
String sql = "insert into WFMS_TESTPO (ID,NAME,NUM) values (?,'RONGHAO',?)";
Object[] params = new Object[]{num,num};
jdbcTemplate.update(sql, params);
sql="update WFMS_TESTPO set name='haorong' where id=?" ;
params = new Object[]{num};
jdbcTemplate.update(sql, params);
}
测试用例:
public void testSave() throws Exception {
TheadtestTemplate template = new TheadtestTemplate();
template.execute(new TheadtestCallback() {
public void doInThead(int suquence) {
// theadTestManager.testSave(suquence);
theadTestManager.testSaveByJdbc(suquence);
}
}, 10);
}
测试结果:不论是hibernate还是jdbc,并发情况下都很快就会引起sqlserver2000的死锁,换用两种数据库驱动jtds和jturbo死锁的情况没有变化。
结论:sqlserver2000数据库的lock配置策略,不支持,或者数据库本身,就不支持对不同的行做同时操作(或者支持不完善),所谓的行锁支持很不完善,死锁情况非常容易发生。
补充:我对数据库的一些实现机制也并不是很了解,所以这里也只能列出现象而不能解释死锁的根本原因。另外感谢Alex的讨论。
posted @
2008-06-19 13:34 ronghao 阅读(6264) |
评论 (22) |
编辑 收藏
今天用hsqldb做单元测试时碰到这么个异常
failed batch; nested exception is java.sql.BatchUpdateException: failed batch
经过检查发现是HSQLDB的问题
The bug is in HSQLDB - a well known one (any Google search for HSQLDB and that "failed batch"
message would have told you it).
https://sourceforge.net/tracker/?func=detail&atid=378131&aid=1407528&group_id=23316
解决方法 : turn off batching with HSQLDB it doesnt work.
设置<prop key="hibernate.jdbc.batch_size">0</prop>即可
posted @
2008-05-30 18:40 ronghao 阅读(671) |
评论 (1) |
编辑 收藏