上篇随笔讨论了CQRS中Command的一种基本实现。

面对UI中的各种命令,Controller会创建相应的Command对象,然后将其交给CommandBus,由CommandBus统一派发到相应的CommandExecutor中去执行,我们的ICommandBus的接口声明如下:

public interface ICommandBus{void Send<TCommand>(TCommand cmd) where TCommand : ICommand;}

当在实际项目中应用CQRS时,我们会发现上面的做法存在一个问题:有时候我们希望Command在执行完后返回一些结果,但上面的Send方法返回void,也就意味着我们没有办法得到执行结果。我们以一个用户注册的例子来说明。

数据库中用户表(User)的定义为User (Id, Email, NickName, Password),括号中的为字段,其中Id为varchar(36)的Guid字符串。

注册用户的RegisterCommand代码如下:

public class RegisterCommand : ICommand{public string Email { get; set; }

public string NickName { get; set; }

public string Password { get; set; }

public string ConfirmPassword { get; set; }

public RegisterCommand()    {    }}

假设在注册后需要将新注册用户的Id存到Session中,那Controller的实现就变得有点纠结:

[HttpPost]public ActionResult Register(RegisterCommand command){    CommandBus.Send(command);

    Session["CurrentUserId"] = ???

return Redirect("/");}

上面的???处要怎么写?我们无法直接得到新注册用户的Id,因为Id只有在Command执行时才会生成。

所以只能在CommandBus.Send(command)的下一行添加一个查询:

[HttpPost]public ActionResult Register(RegisterCommand command){    CommandBus.Send(command);

var newUserId = Query<User>().OrderByDescending(u => u.Id).Select(u => u.Id).First();

    Session["CurrentUserId"] = newUserId;

return Redirect("/");}

这样虽可以勉强解决问题,但比较繁琐,而且也无法保证在Send之后查询之前的这一小片时间里不会有其它新用户产生。于是,我们开始反思Command的设计......

解决方案1

这个方案也正是Sharp Architecture所采用的,即添加一个带两个泛型参数的ICommandExecutor<TCommand, TResult>接口,这样我们就有了两个ICommandExecutor接口:

public interface ICommandExecutor<TCommand>where TCommand : ICommand{void Execute(TCommand cmd);}

// 这是新添加的带有两个泛型参数的接口public interface ICommandExecutor<TCommand, TResult>where TCommand : ICommand{// Execute方法返回值变成TResult    TResult Execute(TCommand cmd);}

为了适应这种变化,我们也需要相应修改ICommandBus的接口:

public interface ICommandBus{void Send<TCommand>(TCommand cmd) where TCommand : ICommand;

// 这个Send方法的返回值是TResult    TResult Send<TCommand, TResult>(TCommand cmd) where TCommand : ICommand;}

看起来不错,现在对于注册用户的例子,我们只要调用第二个Send方法即可:

[HttpPost]public ActionResult Register(RegisterCommand command){var newUserId = CommandBus.Send<RegisterCommand, string>(command);

    Session["CurrentUserId"] = newUserId;

return Redirect("/");}

但如果我们仔细看看,会发现这是一个非常糟糕的设计!Controller的开发人员怎么知道RegisterCommand执行完会返回结果?怎么知道返回的是string而不是int?Controller中可以写成CommandBus.Send(command),也可以写成CommandBus.Send<RegisterCommand, int>(command),也可以写成CommandBus.Send<RegisterCommand, string>(command),同样是发送RegisterCommand命令,这三种调用全都可以编译通过,但是只有第三个才不会在运行时出现问题。

渐渐的,我们就会变成每调用一次CommandBus.Send()方法就要去查看对应的CommandExecutor是怎么实现的,这就让Command和CommandExecutor相分离的设计变得一点意义都没有。所以我们需要寻求其它的解决方案。

PS: Sharp Architecture中的ICommandHandler对应本文中的ICommandExecutor,ICommandProcessor对应本文中的ICommandBus,但我觉得它的ICommandProcessor的取名也太容易让人误解了,单从名字上看,谁能分清楚ICommandHandler和ICommandProcessor的区别?

解决方案2

其实这个方案非常简单:在Command对象中添加一个ExecutionResult的属性(这个属性要放在具体的Command类中,不要放于ICommand接口中)。如上面的用户注册的例子,我们可以添加一个RegisterCommandResult的类,然后将RegisterCommand改成如下所示:

public class RegisterCommand : ICommand{public string Email { get; set; }

public string NickName { get; set; }

public string Password { get; set; }

public string ConfirmPassword { get; set; }

// 亮点在这里    public RegisterCommandResult ExecutionResult { get; set; }

public RegisterCommand()    {    }}

// 亮点在这里public class RegisterCommandResult{public string GeneratedUserId { get; set; }}

在调用CommandBus.Send()之前,我们完全不用理会这个ExecutionResult属性,对于Controller的开发人员来说,他只要知道在Command执行完后,ExecutionResult的值就会被赋上,如果没有,那就是CommandExecutor的bug。

而我们的RegisterCommandExecutor就可以改成(User类的构造函数会调用Id = Guid.NewGuid().ToString()对自己的Id进行赋值):

class RegisterCommandExecutor : ICommandExecutor<RegisterCommand>{public IRepository<User> _repository;

public RegisterCommandExecutor(IRepository<User> repository)    {        _repository = repository;    }

public void Execute(RegisterCommand cmd)    {var service = new RegistrationService(_repository);var user = service.Register(cmd.Email, cmd.NickName, cmd.Password);

// 亮点在这里        cmd.ExecutionResult = new RegisterCommandResult        {            GeneratedUserId = user.Id        };    }}

然后Controller就很简单了:

[HttpPost]public ActionResult Register(RegisterCommand command){    CommandBus.Send(command);

// 亮点在这里    Session["CurrentUserId"] = command.ExecutionResult.GeneratedUserId;

return Redirect("/");}

这个方案和第一个方案的关键区别就在于,RegisterCommand中定义的ExecutionResult属性可以让开发人员清楚的知道这个属性会在Command执行完后被赋上合适的值。对于一个Command,如果开发人员在其中找到类似ExecutionResult这样的属性,他就知道这个Command执行完后会返回执行结果,并且结果是以赋值的形式赋给Command中的ExecutionResult属性,若Command中没有发现ExecutionResult这样的属性,那开发人员便知道这个Command执行完不会返回执行结果。

PS: 因为本例中User.Id采用的是Guid字符串,它可以在创建User对象时立刻生成,所以下载中的代码可以跑得还不错,但如果User.Id是使用SQL Server的自增长int类型,那就跑不了了,因为UnitOfWork是在Command执行完后才Commit的,所以,要处理自增Id的情况,我们需要稍微修改相应代码,比如将IUnitOfWork实例作为参数传给ICommandExecutor.Execute()方法,并把IUnitOfWork的提交转交给CommandExecutor负责,然后在对RegisterCommand.ExecutionResult属性赋值前先调用IUnitOfWork.Commit()方法,这样便可以解决问题。

到目前为止,我们所讨论的Command都是同步执行的,如果Command被设计为异步执行,那本文所讨论的内容便可以直接忽略。

如果系统的性能可以满足需求,同步Command无疑是最好的。

代码下载

http://files.cnblogs.com/mouhong-lin/CQRS-3.zip

转载于:https://www.cnblogs.com/mouhong-lin/archive/2012/03/29/cqrs-command-execution-result.html

CQRS实践(3): Command执行结果的返回相关推荐

  1. CQRS实践(1): 什么是CQRS

    什么是CQRS? 这个问题网上可以找到很多资料,未接触过的童鞋请先查看Udi Dahan, Grey Young, Rinat Abdullin,园子里dax.net,以及Jdon社区上的相关文章. ...

  2. python执行linux命令返回结果_Python中调用Linux命令并获取返回值

    方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256/512表示未找到,该方法适用于she ...

  3. 关于ExecuteNonQuery执行存储过程的返回值 、、实例讲解存储过程的返回值与传出参数、、、C#获取存储过程的 Return返回值和Output输出参数值...

    关于ExecuteNonQuery执行存储过程的返回值 用到过ExecuteNonQuery()函数的朋友们在开发的时候肯定这么用过. if(cmd.ExecuteNonQuery("xxx ...

  4. Java调用linux指令工具类,直接执行cmd,执行grep指令返回结果,执行sed追加指令,hdfs下载指令,获取文件行数

    Java调用linux指令工具类,直接执行cmd,执行grep指令返回结果,执行sed追加指令,hdfs下载指令,获取文件行数 问题背景 LinuxUtils工具类 Lyric:梦想挟带眼泪 问题背景 ...

  5. SQLHelper通用类执行一条返回结果集的SqlCommand命令 使用方法

    SQLHelper.cs 通用类 /// /// 执行一条返回结果集的SqlCommand命令,通过专用的连接字符串. /// 使用参数数组提供参数 /// /// /// 使用示例: /// Sql ...

  6. java 熔断机制_Hystrix Command执行以及熔断机制整理

    这几天在看Hystrix的一些实现,里面大量运用了rxjava的原理,将代码简化到了极致,对于有rxjava基础的同学,相信看懂Hystrix代码并不是一件难事.我这篇文章主要是针对Hystrix C ...

  7. 细琢磨,try catch finally 执行顺序与返回值

    try catch finally 常见格式如下: try{//应用代码}catch(Exception e){//异常捕捉处理}finally{//资源释放.流关闭等等 } 通常执行顺序: try有 ...

  8. java调用sql返回list_Hibernate执行原生SQL返回ListMap类型结果集

    我是学java出身的,web是我主要一块: 在做项目的时候最让人别扭的就是hibernate查询大都是查询出List(T指代对应实体类)类型 如果这时候我用的联合查询,那么返回都就是List , 这样 ...

  9. python中字典不自动排序/删除指定类型文件/执行可执行文件的返回值

    1.python 字典的用法 from collections import OrderedDict dict =OrderedDict() dict['foo']=3 dcit['aol']=1 2 ...

最新文章

  1. 管理者应该如何进行精益管理呢?
  2. [你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考
  3. 新版本springboot-springboot与springcloud理解误区
  4. sublime安装Codecs33
  5. 我的世界服务器虚拟菜单插件,[综合|娱乐|管理][比赛]VirtualMenu——支持多种类型GUI的虚拟菜单[1.7.10~1.14]...
  6. ios开发ocr识别_传统图像处理技术,ocr识别技术算法
  7. Python的可变长度参数*和**,传参序列解包,isinstance的使用
  8. 指针(Pointer)
  9. 100m光纤测速多少正常_100m光纤测速多少正常 所以100M宽带最大下载速度
  10. DICOM图像像素值、灰度值与CT值
  11. 计算机术语中bug指的是,你知道电脑漏洞为什么叫bug吗?
  12. Java:基础 :集合和迭代器
  13. 微服务守护神-Sentinel-概念
  14. 激活win10专业版最简单的方法
  15. linux最上层目录是什么,Linux基础知识之--目录组成结构,当前目录及上层目录表示方法,目录访问权限...
  16. C# 舒特二次开发采集考勤记录并同步设备时间
  17. SAP中销售订单计划行类别中请求/装配对物料需求计划的影响测试
  18. html5字体在线代码,网络字体@font-face 如何处理网页中的特殊字体
  19. 考研三年,做了同传,迎娶白富美,实现财务自由。这是真鸡汤!
  20. 人脸识别数据集整理以及下载

热门文章

  1. Redis初学:11(Redis的配置文件)
  2. 网站更换服务器ip地教程,由于服务器更换IP地址,服务器不更换。需要如何操作使网站正常运行呢?,POSCMS,CodeIgniter技术文档,PHP开发文档,迅睿CMS框架官方教程...
  3. wps临时文件不自动删除_win10系统下wps残留文件无法删除如何解决
  4. CSS之布局(轮廓和圆角)
  5. js 获取当前时间 随记
  6. OC 的反射机制以及使用场景
  7. 【iOS】日历行程的增删改查(完整)
  8. iOS 模仿支付宝支付到账推送,播报钱数
  9. 不用任何第三方,写一个RTMP直播推流器
  10. UNIX网络编程--ioctl操作(十七)