zoukankan      html  css  js  c++  java
  • 生产环境下实践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());
    }
    
    
  • 相关阅读:
    Java的“Goto”与标签
    Integer和int使用==比较的总结
    20175301李锦然《网络对抗技术》Exp9 Web安全基础
    20175301李锦然《网络对抗技术》Exp8 Web基础
    20175301李锦然《网络对抗技术》Exp7 网络欺诈防范
    2019-2020-2 20175301李锦然《网络对抗技术》Exp6 MSF基础应用
    2019-2020-2 20175301李锦然《网络对抗技术》Exp5 信息搜集与漏洞扫描
    2019-2020-2 20175301李锦然《网络对抗技术》Exp4 恶意代码分析
    2019-2020-2 20175301李锦然《网络对抗技术》Exp3 免杀原理与实践
    2019-2020-2 20175301李锦然《网络对抗技术》Exp2 后门原理与实践
  • 原文地址:https://www.cnblogs.com/FuzhePan/p/4088200.html
Copyright © 2011-2022 走看看