最近的开发工作涉及到两个模块“任务”和“日周报”。关系是日周报消费任务,因为用户在写日周报的时候,需要按一定的规则筛选当前用户的任务,作为日周报的一部分提交。整个项目采用类似于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());
}
												

生产环境下实践DDD中的规约模式的更多相关文章

  1. 四步法分析定位生产环境下MySQL上千条SQL中的问题所在

    第一步:通过以下两种方式之一来打开慢查询功能 (1)方式一:通过修改mysql的my.cnf文件 如果是5.0或5.1等版本需要增加以下选项: log-slow-queries="mysql ...

  2. 生产环境下Flask项目目录构建

    接触Flask已经有大半年了,本篇博客主要来探讨如何规范化生产环境下Flask的项目目录结构.虽然目录结构见仁见智,个人有个人的看法和习惯,但总的来说,经过很多人的实践和总结,还是有很多共同的意见和想 ...

  3. Mysql迁移工具在生产环境下的使用

    在产品迭代开发发布过程中,由于业务需求的增加,数据库难免会有结构调整等操作. 在每个版本发布过程中怎么控制每个版本server端程序与数据库版本保持一致,以及数 据库升级.回滚等操作. 本博文宅鸟将向 ...

  4. 生产环境下JAVA进程高CPU占用故障排查

    问题描述:生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高. 问题分析:1,程序属于CPU密集型,和开发沟通过, ...

  5. 一次生产环境下MongoDB备份还原数据

    最近开发一个版本的功能当中用到了MongoDB分页,懒于造数据,于是就研究了下从生产环境上导出数据到本地来进行测试. 研究了一下,发现MongoDB的备份还原和MySQL语法还挺类似,下面请看详细介绍 ...

  6. centos7生产环境下openssh升级

    由于生产环境ssh版本太低,导致使用安全软件扫描时提示系统处于异常不安全的状态,主要原因是ssh漏洞.推荐通过升级ssh版本修复漏洞 因为是生产环境,所以有很多问题需要注意.为了保险起见,在生产环境下 ...

  7. iptables 生产环境下基础设置

    iptables 生产环境下基础设置 生成环境需求:防火墙需要让内网的Ip全部通过,外网IP添加到白名单,其他一切拒绝.安装在linux系统中安装yum install iptables-servic ...

  8. 生产环境下JAVA进程高CPU占用故障排查---temp

    问题描述:生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高. 问题分析:1,程序属于CPU密集型,和开发沟通过, ...

  9. 生产环境下lnmp的权限说明

    https://www.cnblogs.com/zrp2013/p/4183546.html 有关权限说明:-rwxrw-r‐-1 root root 1213 Feb 2 09:39 50.html ...

随机推荐

  1. sql 通过表名获取所有列名

    因为要做数据迁移,也就是业务数据库的数据要迁移到历史数据库,这两个数据库理论上表结构是一样的,但因为时间原因,可能业务库升级了表结构,但历史库没有升级,且加字段的顺序不一样,导致 insert int ...

  2. java实现调用ORACLE中的游标和包

    今天把oracle中的包和游标学习了下,不废话,网上的的有些代码是错误的,抄来抄去,就自己实践了下,做个记录.直接上图,上代码 通过plsql创建自己的的包,包分为包头和包体. 1.包头如下: CRE ...

  3. Flask备注二(Configurations, Signals)

    Flask备注二(Configuration, Signals) Flask是一个使用python开发Web程序的框架.依赖于Werkzeug提供完整的WSGI支持,以及Jinja2提供templat ...

  4. Linux新建用户无法使用tab补全的修改办法

    原因: Root用的是/bin/bash 新增用户默认用的是/bin/sh,用ls -l /bin/sh发现 ->dash,修改下连接即可正常使用:

  5. HTML颜色代码表

      #000000   #2F0000   #600030   #460046   #28004D   #272727   #4D0000   #820041   #5E005E   #3A006F ...

  6. Web程序的运行原理及流程(二)

    其实WEB服务器和WEB应用服务器这两个概念特别容易混淆  可以理解为装了不同软件(服务)的两台计算机(服务器)吧 先对两个概念做一个简单介绍 了解了基本的概念 我们再用两个典型的例子做一下比较(建立 ...

  7. Android 图片添加水印图片或者文字

    给图片添加水印的基本思路都是载入原图,添加文字或者载入水印图片,保存图片这三个部分 添加水印图片: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

  8. iptables示例

    [root@iZ23um2lv3tZ ~]# more /etc/sysconfig/iptables # Generated by iptables-save v1. :: *filter :INP ...

  9. python day2 列表的常用操作方法

    #创建列表方法一li = ['aa','bb','cc']方法二li = list(['aa','bb','cc'])print(li)返回 ['aa', 'bb', 'cc']----------- ...

  10. ADO.net操作数据库

    今天整理硬盘,发现2年前开始着手开始学习C#的学习日记.陆续整理,一是自己的知识梳理梳理,二是希望与大家多多交流,能给初学者带来一定帮助,当然是更高兴的啦. 断线对象 另一类是与数据源无关的断线对象, ...