posts - 193,  comments - 520,  trackbacks - 0
 
关于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)编辑 收藏
仅列出标题
共39页: First 上一页 14 15 16 17 18 19 20 21 22 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

关注工作流和企业业务流程改进。现就职于ThoughtWorks。新浪微博:http://weibo.com/ronghao100

常用链接

留言簿(38)

随笔分类

随笔档案

文章分类

文章档案

常去的网站

搜索

  •  

最新评论

阅读排行榜

评论排行榜