生產環境下實踐DDD中的規約模式


最近的開發工作涉及到兩個模塊“任務”和“日周報”。關系是日周報消費任務,因為用戶在寫日周報的時候,需要按一定的規則篩選當前用戶的任務,作為日周報的一部分提交。整個項目采用類似於Orchard那種平台加插件的架構,“任務”和“日周報”是兩個獨立的插件。

“任務”已經由一位同事事先寫好,周報中篩選任務的規則簡單描述如下:

  • 截止日期在周一之前,且未完成的任務(超期或待審核);
  • 截止日期在周一至周日之間的所有任務;
  • 開始日期在周一至周日之間的所有任務;
  •  截止日期在周日之后,且未設置開始日期的所有任務(進行中或待審核)。

看起來貌似挺簡單,敲代碼的時候卻發現下不了手,“任務”的倉儲層對“日周報”是不可見的,想要按照規則查詢任務列表,我只能調用TaskService,但TaskService中並沒有根據上述規則來篩選任務的方法。

怎么辦呢?為TaskService添加個實現上述規則的方法,比如GetTasksForWeeklyReport?想了想,貌似不是一個好的思路,因為是“日周報”在消費“任務”模塊,任務模塊應該是不知道日周報的存在的,直接寫一個只針對周報的方法總覺得心里有點不對勁。而且,也不希望以后日周報的需求更改而影響到任務。

再想想,日報中也有自己篩選任務的規則,按照上面那么搞,還需要為日報添加個方法GetTasksForDailyReport。如果其他的業務模塊也需要按一定的規則篩選任務列表的話,方法還得繼續追加下去。這樣勢必會造成TaskService的無比臃腫,而且其他的模塊的規則已修改,就要同步修改任務模塊。如果任務模塊單獨部署到一台機器上,這種麻煩程度就會更大。

這時候夜壺般的腦袋中閃過一個詞:規約。

規約模式可以簡單理解為條件判斷。就不在此照搬那些費解的概念了,按照現在遇到的問題舉例來說,我希望TaskService中有個這樣的方法:

GetTasksBySpecification(ISpecification specification);

specification是一個描述任務篩選規則的對象,TaskService可以根據這個對象所描述的規則來找出Task集合。對於周報來說,只需要實現ISpecification接口的具體實例,然后調用TaskService的GetTasksBySpecification方法並傳遞規約實例,就可以拿到想要的任務列表。對於日報來說,也一樣,實現自己的規約類就好。以后再有其他業務模塊需要根據自己的規則篩選任務的時候,也只需要實現一個規約類。

這樣就可以保證“任務”模塊的完整性,而且避免了TaskService無限臃腫的顧慮。

有了思想,就剩下具體實現了。主要參考了大神陳晴陽開發的DDD開發框架Apworks,其中提供了規約模式的.Net實現。

最終類圖如下:

 

ISpecification中定義了規約類需要實現的方法,其中IsSatisfiedBy用來判斷一個對象是否滿足改規約,GetExpression用來獲取表示該規約的表達式樹。DailyReportTaskSpecification和WeeklyReportTaskSpecification用來描述篩選規則。有時候查詢需要根據兩個規約以“and”條件進行查詢,所以又有了AndSpecification,用來把兩個規約以and條件組合到一起。

周報中任務篩選規則的規約類代碼大概是:

public class WeeklyReportTaskSpecification : SpecificationBase<TaskEntity>{
    public override Expression<Func<TaskEntity, bool>> GetExpression(){
        return task =>.....;
    }
}

根據用戶Id篩選任務的規約類代碼:

public class UserInChargeTaskSpecification : SpecificationBase<TaskEntity>{
    #region 私有字段
    private readonly long _inchargeUserId;
    #endregion

    #region 構造器
    public UserInChargeTaskSpecification(long inChargeUserId){
        _inchargeUserId = inChargeUserId;
    }
    #endregion

    #region SpecificationBase<TaskEntity> 成員
    public override Expression<Func<TaskEntity, bool>> GetExpression(){
        return task =>task.UserIncharge!=null && task.UserIncharge == _inchargeUserId;
    }
    #endregion
}

TaskService實現規約查詢的方法:

public IEnumerable<TaskEntity> GetTasksBySpecification(ISpecification<TaskEntity> spec){
    return taskRepository.Table.Where(spec.IsSatisfiedBy);
}

周報中通過如下代碼實現對TaskService中規約方法的調用:

public IEnumerable<TaskEntity> GetWeeklyTask(long userId, DateTime currentDateTime){
    var userInChargeTaskSpecification = new UserInChargeTaskSpecification(userId);
    var weeklyReportTaskSpecification = new WeeklyReportTaskSpecification();

    return TaskService.GetTasksBySpecification(userInChargeTaskSpecification.And(weeklyReportTaskSpecification));
}

除了需要根據規則篩選任務列表之外,還需要根據當前用戶的Id過濾,因為當前用戶只關心自己的任務。所以把兩個規約類通過And方法連接到一塊,組成一個規約,傳遞給GetTasksBySpecification方法。

試了下效果,五星好評!!!

補充:

往這篇博客中貼代碼的時候,TaskService中的GetTasksBySpecification中的實現讓我有點不放心。

因為ISpecification的IsSatisfiedBy屬性返回的是表達式樹Compile之后的委托,我直接傳遞給linq一個委托,會不會造成全表掃描?不會把整個表的數據加載到內存,然后挨個用委托過濾吧。這個很好驗證,查看一下最終執行的sql就可以了。

然后在園子里找到了dudu的這篇文章:Func引起的數據庫全表查詢

於是GetTasksBySpecification的代碼修改如下:

public IEnumerable<TaskEntity> GetTasksBySpecification(ISpecification<TaskEntity> spec){
    return taskRepository.Table.Where(spec.GetExpression());
}
 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM