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.aygfsteel.com/ronghao 榮浩原創,轉載請注明出處:)
          posted on 2008-07-03 18:23 ronghao 閱讀(2621) 評論(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是不受容器管理的。
          謝謝關注張小慶!  回復  更多評論
            
          <2011年3月>
          272812345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          關注工作流和企業業務流程改進。現就職于ThoughtWorks。新浪微博:http://weibo.com/ronghao100

          常用鏈接

          留言簿(38)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          常去的網站

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 莒南县| 滨州市| 苗栗市| 团风县| 清河县| 黄大仙区| 津南区| 通山县| 台南县| 蒲江县| 山阴县| 会宁县| 呼玛县| 浏阳市| 错那县| 河北省| 沽源县| 乌拉特中旗| 司法| 灵石县| 台南市| 昆明市| 安丘市| 淅川县| 广饶县| 武强县| 澄城县| 宁德市| 阜宁县| 类乌齐县| 黄浦区| 霍城县| 华亭县| 姜堰市| 昆明市| 高台县| 云霄县| 北辰区| 进贤县| 竹山县| 克拉玛依市|