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,在父类里进行造型。




http://www.blogjava.net/ronghao 荣浩原创,转载请注明出处:)
posted on 2008-07-03 18:23 ronghao 阅读(2609) 评论(2)  编辑  收藏 所属分类: 工作日志

FeedBack:
# re: 从贫血到充血Domain Model
2011-03-14 09:04 | 人在江湖
请教,有句没看懂
WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO");
这个为什么不是注射进来的呢?

觉得你“接下来的做法”和“现在的做法”没啥区别啊,呵呵,就是没有封装private方法,而是单弄了类。

张小庆的故事挺好的,不希望故事结局太灰暗。楼主牛人,给程序员们些念想吧,呵呵。  回复  更多评论
  
# re: 从贫血到充血Domain Model[未登录]
2011-03-14 18:33 | ronghao
@人在江湖
无法注入,WorkItem是不受容器管理的。
谢谢关注张小庆!  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航:
 
<2008年7月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

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

常用链接

留言簿(38)

随笔分类

随笔档案

文章分类

文章档案

常去的网站

搜索

  •  

最新评论

阅读排行榜

评论排行榜