当前位置: 首页 > 编程日记 > 正文

CQRS实践(3): Command执行结果的返回

上篇随笔讨论了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

相关文章:

iOS学习——核心动画之Layer基础

iOS学习——核心动画之Layer基础 1、CALayer是什么&#xff1f; CALayer我们又称它叫做层。在每个UIView内部都有一个layer这样一个属性&#xff0c;UIView之所以能够显示&#xff0c;就是因为它里面有这个layer才具有显示的功能。我们可以通过操作CALayer对象&#xff0c;可以…

linux valgrind memCheck ---内存检查工具的可视化方法valkyrie

linux valgrind memCheck —内存检查工具的可视化方法valkyrie linux valgrind Memcheck–内存检查工具 1、安装valgrind valgrind 安装 安装过程没这么复杂。 直接命令行: sudo apt-get install valgrind2、安装valkyrie valkyrie下载连接: https://launchpad.net/ubuntu/…

屏幕为什么要正负压供电_负压变换器的设计

目前在工业、汽车电子系统中有诸如温度、压力、位置、重量和流量等物理参数的精确测量&#xff0c;这些信号中的一些传感器和前置放大器需要正负电压源驱动或供电&#xff0c;以提供足够宽的动态范围和抗干扰性。这些电子系统通常使用3.3V、5V、12V或24V中的某一电压的直流电源…

DataCleaner 3.1.1 发布,数据质量分析管理

DataCleaner 3.1.1 扩展了日期和时间相关的分析&#xff1b;增加周、月、年的分布分析&#xff1b;数值分析和日期时间分析增加了描述统计的选项&#xff1b;新增用于生成 UUID 和时间戳的转换器等等。 DataCleaner 是一个数据质量分析&#xff0c;比较&#xff0c;验证和监督的…

IIS负载均衡-Application Request Route详解第三篇:使用ARR进行Http请求的负载均衡(上)...

IIS负载均衡-Application Request Route详解第三篇&#xff1a;使用ARR进行Http请求的负载均衡&#xff08;上&#xff09; 在前两篇文章中&#xff0c;我们已经讲述如何配置与安装ARR&#xff0c;从本篇文章开始&#xff0c;我们将重点的来讲述如何在使用ARR进行负载均衡。 本…

云主机启动提示Booting from Hard Disk GRUB

版本&#xff1a;Openstack ocata 系统&#xff1a;centos7.3 环境&#xff1a;VMware workstation12 解决方法&#xff1a; 或者 转载于:https://www.cnblogs.com/fcing/p/9374855.html

函数 tostring_Kotlin实战之Fuel的高阶函数

Fuel 是一个用 Kotlin 写的网络库&#xff0c;与 OkHttp 相比较&#xff0c;它的代码结构比较简单&#xff0c;但是它的巧妙之处在于充分利用了 Kotlin 的语言特性&#xff0c;所以代码看上去干净利落。OkHttp 使用了一个 interceptor chain 来实现拦截器的串联调用&#xff0c…

linux valgrind 安装和使用

linux valgrind 安装和使用 安装过程没这么复杂。 直接命令行: sudo apt-get install valgrind Valgrind 是个开源的工具&#xff0c;功能很多。例如检查内存泄漏工具—memcheck。 Valgrind 安装&#xff1a; sudo apt-get install valgrind Valgrind 命令介绍&#xff…

UIPopoverController在ARC环境下用法注意

在ARC环境下如果便用以下代码&#xff1a; [cpp] view plaincopyprint?UIViewController *viewTwo; viewTwo [[ViewTwo alloc] initWithNibName:"ViewTwo" bundle:nil]; UIPopoverController *popover; popover [[UIPopoverController alloc] initWithConten…

CPLD的分频语言

分频器在FPGA/CPLD设计中是不可缺少的一部分&#xff0c;这就包括分频系数是奇数和偶数的&#xff08;我们称为奇分频和偶分频&#xff09;&#xff0c;而对于偶分频来说还有不同的分频方法&#xff0c;下面将给出具体的方法&#xff1a; 1、占空比不为50%的偶分频 占空比&…

彻底解决web开发中遇到的路径问题(上)

注&#xff1a;本文部分引用了网络上的文章&#xff0c;以及动力节点老师的讲解内容&#xff0c;感谢老师&#xff0c;嘻嘻。 为了举例方便&#xff0c;我新建了pathTest项目&#xff1a; 关于tomcat的配置&#xff0c;eclipse访问项目的路径一般是localhost:8080/projectName,…

关于Page翻页效果--Page View Controller

Page View Controllers你使用一个page view controller用page by page的方式来展示内容。一个page view controller管理一个self-contained视图架构。这个架构的父视图由page View controller管理&#xff0c;并且子视图由你提供的view Controllers管理。一&#xff0c;解析Pag…

linux平台下QtCreator中集成Valgrind系列工具

linux平台下QtCreator中集成Valgrind系列工具 ###1、valgrind 安装 valgrind 安装 2、打开QtCreator >> Analyze 你就会发现 这里已经有valgrind的相关选项了 如果没有的话&#xff0c; 在help >> about plugin >> C 中勾选 如图: 点击则可以直接运行…

python输入参数改变图形_Python基于Tensor FLow的图像处理操作详解

本文实例讲述了Python基于Tensor FLow的图像处理操作。分享给大家供大家参考&#xff0c;具体如下&#xff1a;在对图像进行深度学习时&#xff0c;有时可能图片的数量不足&#xff0c;或者希望网络进行更多的学习&#xff0c;这时可以对现有的图片数据进行处理使其变成一张新的…

CSS层叠样式

为了让网页元素的样式更加丰富&#xff0c;也为了让网页的内容和样式能拆分开&#xff0c;CSS由此思想而诞生&#xff0c;CSS是 Cascading Style Sheets 的首字母缩写&#xff0c;意思是层叠样式表。有了CSS&#xff0c;html中大部分表现样式的标签就废弃不用了&#xff0c;htm…

windows下 Source Monitor代码度量工具的使用

windows下 Source Monitor代码度量工具的使用 引用链接: https://www.cnblogs.com/xuehanyu/p/4520965.html 1.总体介绍 SourceMonitor是一款免费的软件&#xff0c;运行在Windows平台下。它可对多种语言写就的代码进行度量&#xff0c;包括C、C、C#、Java、VB、Delphi和HT…

MVVM 数据绑定

一、在 XAML 中创建绑定 定义源对象。 C# public class Dog {public string DogName { get; set; } }在 XAML 中创建对源对象的命名空间的引用。 XAML <UserControl x:Class"BindingXAML.Page" xmlns"http://schemas.microsoft.com/winfx/2006/xaml/pres…

linux配置文件怎么把某行后几个字符替换_vim(Linux运维)

一、vim使用介绍 介绍在linux系统中&#xff0c;大部分配置文件都是ASCII的纯文本形式存放的&#xff0c;所以我们在修改系统设置的时候使用简单的文本编辑软件就可以实现了&#xff0c;如果你使用过windows当中的word的话&#xff0c;那么你可能会感觉linux字符界面的文本编辑…

Debian 6.0 安装过程 及中文乱码

2019独角兽企业重金招聘Python工程师标准>>> Debian 6.0 安装过程 Debian 6.0 安装过程 转(一个别人自录的安装过程录相) http://v.youku.com/v_show/id_XMjUyMzY1OTIw.html 转(别人写的一个过程) http://hi.baidu.com/ljx_freebsd/blog/item/88d60c09da379da22edd…

git 提交丢失Warning, you are leaving 2 commits behind,

早上在自己的一个版本代码上编辑&#xff0c;提交commint&#xff0c;但是checkout到其他分支再checkout回来发现该的东西不见了&#xff0c; 幸好terminal还没有关掉&#xff0c;回看日志&#xff1a; Warning: you are leaving 2 commits behind, not connected toany of you…

一台支持vlan管理的交换机_关于交换机的VLAN技术你了解多少?

VLAN&#xff08;虚拟局域网&#xff09;是对连接到的第二层交换机端口的网络用户的逻辑分段&#xff0c;不受网络用户的物理位置限制而根据用户需求进行网络分段。一个VLAN可以在一个交换机或者跨交换机实现。VLAN可以根据网络用户的位置、作用、部门或者根据网络用户所使用的…

需要反射时使用dynamic

//使用dynamic的写法 dynamic fileExplorerData _currentFolder.FileExplorerData; var data fileExplorerData.InsertFromPath(newPath);//使用反射的写法 MethodInfo InsertMethod _currentFolder.FileExplorerData.GetType().GetMethod("InsertFromPath"); var…

Linux平台下QtCreator集成代码静态分析工具clang-tidy和Clazy

Linux平台下QtCreator集成代码静态分析工具clang-tidy和Clazy 原文连接: https://blog.csdn.net/wsj18808050/article/details/79824619 内容&#xff1a; QtCreator在前几天发布了4.6.0的版本&#xff0c;增加了两个非常棒的新功能&#xff0c;分别是Clang-Tidy和Clazy 官方…

JAVA swing初级教程(四)

附加的swing小部件(下) JOptionPane JOptionPane 是在 Swing 中类似“快捷方式”的东西。通常&#xff0c;作为 UI 开发人员&#xff0c;您需要向用户呈现快速信息&#xff0c;让用户了解错误和信息。甚至可能想得到一些快速数据&#xff0c;例如名称或数字。在 Swing 中&#…

Akka源码分析-Remote-发消息

上一篇博客我们介绍了remote模式下Actor的创建&#xff0c;其实与local的创建并没有太大区别&#xff0c;一般情况下还是使用LocalActorRef创建了Actor。那么发消息是否意味着也是相同的呢&#xff1f; 既然actorOf还是委托给了LocalActorRef&#xff0c;那么在本地创建的Actor…

用sed 给文本文件加行号

看例子&#xff1a; [rootlocalhost tmp]# sed test.txt 1 tsttst tsttsttst 2 west gao 3 west abces [rootlocalhost tmp]# sed test.txt | sed N;s/\n/\t/ 1 tsttst tsttsttst 2 west gao 3 west abces [rootlocalhost tmp]# N的解释&#xff1a; N&am…

Qt 编译一直死循环问题

Qt 编译一直死循环问题 有时候Qt编译项目时&#xff0c; 一直编不过&#xff0c;查看一下编译窗口&#xff0c;发现一直在循环 输出如下: C:\soft\Qt5.11\5.11.1\mingw53_32\bin\qmake.exe -o Makefile ..\..\..\..\PalmQtLib\PalmQtLib\PalmQtLib.pro -spec win32-g "…

路由器运行python脚本_写个Python脚本来登录小米路由器

这个脚本写起来难度并不是很大&#xff0c;博主还是一步步的分析下&#xff0c;这样思路会比较清晰&#xff0c;下次遇到类似系统脚本写起来也更快速。好了&#xff0c;一起来分析分析。首先看下小米路由器的登录界面可以看到只需要输入密码即可登录&#xff0c;博主这里为了演…

PHP定时执行任务的实现

2019独角兽企业重金招聘Python工程师标准>>> ignore_user_abort();//关掉浏览器&#xff0c;PHP脚本也可以继续执行. set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去 $interval60*30;// 每隔半小时运行 do{//这里是你要执行的代码 sleep($i…

Spring事务管理 与 SpringAOP

1&#xff0c;Spring事务的核心接口 Spring事务管理的实现有许多细节&#xff0c;如果对整个接口框架有个大体了解会非常有利于我们理解事务&#xff0c;下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。   Spring事务管理涉及的接口的联系如下&#xff1a; 1.…