關于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.tkk7.com/ronghao 榮浩原創,轉載請注明出處:)
posted on 2008-07-03 18:23
ronghao 閱讀(2617)
評論(2) 編輯 收藏 所屬分類:
工作日志