CQRS 示例
CQRS 示例
上一篇:《IDDD 实现领域驱动设计-CQRS(命令查询职责分离)和 EDA(事件驱动架构)》
学习架构知识,需要有一些功底和经验,要不然你会和我一样吃力,CQRS、EDA、ES、Saga 等等,这些是实践 DDD 所必不可少的架构,所以,如果你不懂这些,是很难看懂上篇所提到的 CQRS Journey 和 ENode 项目,那怎么办呢?我们可以从简单的 Demo 一点一滴开始。
代码地址:https://github.com/yuezhongxin/CQRS.Sample


说明:一张很丑陋的图
CQRS.Sample 所描述的一个流程是 SendMessage(发消息),也就是之前 MessageManager 中的一个业务示例,这个业务流程用到 CQRS 示例中,可能会有些不太准确,或者是有些牵强,但我主要的目的是想做一次 Commond->Event 的过程,熟悉它们到底是怎么交互的?所以,你查看代码的时候,尽量忽略这个业务流程,当然,如果你有针对这个业务流程更好的具体应用,我是非常欢迎交流。
首先,我们根据上面的流程图一步一步进行,UI 创建一个 Commond,然后交给 CommandBus 进行 Send(发送),也就是下面这段代码:
var commond = new SendMessageCommond()
{
Title = "this is title",
Content = "this is content",
SenderLoginName = "this is senderLoginName",
RecipientDisplayName = "this is recipientDisplayName"
};
var commandBus = IocContainer.Default.Resolve<CommandBus>();
commandBus.Send(commond);
项目中所有的类型映射都是通过 IoC 进行注入,ICommandBus 接口定义为:void Send<TCommand>(TCommand cmd) where TCommand : ICommand;,CommandBus 的实现主要是在 Send 方法中,解析 ICommandHandler<TCommand> 注入的类型对象,然后调用 ICommandHandler 接口定义的 Handle 方法,传入 TCommand 参数对象。
SendMessageCommond 的定义很简单,主要是一些来自 UI 的参数,所以,我们一般会定义一些属性字段,有时候我们会进行数据验证,可以使用 Validate,方法签名是: IEnumerable<ValidationResult> Validate(ValidationContext validationContext),不过需要继承 IValidatableObject 接口,这样我们就可以在 MVC View 前端进行输出验证结果,使用起来非常方便,SendMessageCommond 继承一个无实现的 ICommand 接口,主要用来约束类型,SendMessageCommond 对应 SendMessageCommondHandler,实现代码:
public class SendMessageCommondHandler :
ICommandHandler<SendMessageCommond>
{
public void Handle(SendMessageCommond command)
{
var sender = VerifySenderService.Verify(command.SenderLoginName);
var receiver = VerifyReceiverService.Verify(command.RecipientDisplayName);
var message = new Message(command.Title, command.Content, sender, receiver);
message.Send();
}
}
CommondHandler 的功能有点类似于经典分层架构中的 Application(应用层),从它的具体实现中,我们就可以看出领域到底在做哪些工作,它的主要工作就是协调这些工作的流程,领域中的代码我就不贴了,这里我简单说明一下,在之前的 SendMessage 实现中,设计了一个 SendMessageService 领域服务,里面主要进行的工作是验证收发件人,以及消息是否符合规则,后来我就想,SendMessageService 和它实际进行的工作不相符,发消息所蕴含的实际业务意义,我也一直没有想明白,但是在具体实现中,发消息所做的工作是验证,那验证是不是发消息真正的业务含义呢?所以,稀里糊涂就有了上面的代码,VerifySenderService 和 VerifyReceiverService 是用来验证收发件人信息的,成功的话就返回一个 Contact 对象,具体的验证逻辑可以查看下实现代码。
下面说一下 message.Send();,这个可能有很大的问题,在 CQRS Journey 项目中,有很多类似于这样的操作,就是在 CommondHandler 中,创建一个聚合根对象,然后执行聚合根中的一个行为,比如我搜刮的 order.Confirm(),订单可以提交自己吗?消息可以发送自己吗?这样做的含义是什么?其实我也搞不太清楚,在 Message 聚合根中的 Send 方法中,主要是事件的发布,
public void Send()
{
eventBus.Publish(new MessageSentEvent(this));
}
先抛开 Send 的合理性,看下 EventBus 是如何 Publish 的?我的实现中和 CommondBus 很相似,但我觉得可能有些问题,Commond 和 CommondHandler 是一一对应关系,我们知道事件发布和事件订阅是一对多关系,也就是说一个事件可能有很对的订阅者,这些订阅者所处理的过程可能会有些不同,比如用户注册的一个事件,可能会有邮件通知订阅者,也可能会有统计数据更新订阅者,在 CQRS Journey 项目中,EventBus 的 Publish 大概是这样实现的:
public void Publish(Envelope<IEvent> @event)
{
var message = this.BuildMessage(@event);
this.sender.Send(message);
}
private Message BuildMessage(Envelope<IEvent> @event)
{
using (var payloadWriter = new StringWriter())
{
this.serializer.Serialize(payloadWriter, @event.Body);
return new Message(payloadWriter.ToString(), correlationId: @event.CorrelationId);
}
}
而我的实现则是和 CommondBus 一样,都是用 IoC 注入的,所以肯定有问题,我们来看下 MessageSentEventHandler 中的代码:
public class MessageSentEventHandler :
IEventHandler<MessageSentEvent>,
IEventHandler<MailSentEvent>
{
private IEventBus eventBus;
public void Handle(MessageSentEvent @event)
{
eventBus = IocContainer.Default.Resolve<IEventBus>();
new MessageRepository().Save<Message>(@event.Message);
eventBus.Publish(new MailSentEvent
{
Title = @event.Message.Title,
Content = @event.Message.Title,
});
}
public void Handle(MailSentEvent @event)
{
var mailMessage = new System.Net.Mail.MailMessage();
mailMessage.Subject = @event.Title;
mailMessage.Body = @event.Content;
mailMessage.IsBodyHtml = true;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
mailMessage.Priority = System.Net.Mail.MailPriority.Normal;
System.Net.Mail.SmtpClient smtpClient = new System.Net.Mail.SmtpClient();
Task.Run(() => { smtpClient.SendAsync(mailMessage, mailMessage.Body); });
}
}
消息发送之后,进行持久化操作,然后再进行邮件通知,这样一整个 SendMessage 的流程就走完了。
这个流程中,只有 CQRS 的 Commond,并没有 Query,也没有 ES、Saga 的概念,主要是它们太深奥了,我搞不来。CQRS.Sample 只是一个简单的示例,发消息的业务含义所表达的可能不是很准确,本来是想用用户注册、订单提交业务示例,但还是想想用发消息进行尝试下,除去 EventBus 的实现有问题,CQRS 的简化版基本上都能表现出来了。
另外,从简单分层架构改造成 CQRS 确实有很多挑战,但有时候想想,领域模型都设计的有问题,那用什么架构实现都毫无意义,如果在现有的项目中,你发现经典分层架构实现起来有很多别扭的地方,比如 Domain 引用了 DTO,你可以尝试先把 Repositories 进行分离下,创建一个 Query 项目,把一些无业务逻辑的查询发到里面(主要是应用层调用的),这样使 Repositories 更加简化,如果可以简化成只有一个 GetById 方法,那么就达到 CQRS 的标准了,因为 Repositories 的接口定义在领域层,因为 Query 项目的分离,所以,Domain 就可以去除 DTO 的引用了,应用层也就直接调用 Query,这只是一个中和方案。
领域模型需要一点一滴设计,架构也需要一点一滴设计,但后者需要建立在前者的基础上。
一个对我非常有帮助的 CQRS 系列(初级):
CQRS 示例的更多相关文章
- IDDD 实现领域驱动设计-一个简单的 CQRS 示例
上一篇:<IDDD 实现领域驱动设计-CQRS(命令查询职责分离)和 EDA(事件驱动架构)> 学习架构知识,需要有一些功底和经验,要不然你会和我一样吃力,CQRS.EDA.ES.Saga ...
- CQRS微服务架构模式
什么是微服务? 这是维基百科里面的定义:“微服务是面向服务架构(SOA)架构风格的一种变体,它将应用程序构建为一系列松散耦合的服务.在微服务体系结构中,服务应该是细粒度的,协议应该是轻量级的.将应用 ...
- [译]CQRS介绍
以下内容均为看完原文后自己的理解.并非一字一句翻译,会尽量保持原文意思. 什么是 CQRS: CQRS 意思就是命令查询职责分离(Command Query Responsibility Segreg ...
- CQRS与Event Sourcing之浅见
引言 DDD是近年软件设计的热门.CQRS与Event Sourcing作为实施DDD的一种选择,也逐步进入人们的视野.围绕这两个主题,软件开发的大咖[Martin Fowler].[Greg You ...
- Domain Driven Development相关概念
Entity 与 Value Object1,Entity有唯一的身份标识,是可变的对象.Value Object是immutable,创建了就不能改变.2,Value Object可以在多个领域之间 ...
- 【Other】最近在研究的, Java/Springboot/RPC/JPA等
我的Springboot框架,欢迎关注: https://github.com/junneyang/common-web-starter Dubbo-大波-服务化框架 dubbo_百度搜索 Dubbo ...
- 你一定看得懂的 DDD+CQRS+EDA+ES 核心思想与极简可运行代码示例
前言 随着分布式架构微服务的兴起,DDD(领域驱动设计).CQRS(命令查询职责分离).EDA(事件驱动架构).ES(事件溯源)等概念也一并成为时下的火热概念,我也在早些时候阅读了一些大佬的分析文,学 ...
- CQRS框架:AxonFramework 之 Hello World
Command Query Responsibility Segregation,CQRS 这个架构好象最近博客园里讨论得比较多,有几篇园友的文章很有深度,推荐阅读: CQRS架构简介 浅谈命令查询职 ...
- 通过一组RESTful API暴露CQRS系统功能
命令和查询责任分离(CQRS)是由Greg Young提出的一种将系统的读(查询).写(命令)操作分离为两种独立子系统的架构模式.命令通常是异步执行的,并存储在一个事务型数据库中,而读操作则通常是最终 ...
随机推荐
- 返璞归真 asp.net mvc (8) - asp.net mvc 3.0 新特性之 Model
原文:返璞归真 asp.net mvc (8) - asp.net mvc 3.0 新特性之 Model [索引页][源码下载] 返璞归真 asp.net mvc (8) - asp.net mvc ...
- UWP开发的一些思考
UWP开发的一些思考 领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中 ...
- 实现BUG自动检测 - ASP.NET Core依赖注入
我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...
- vs2015web工程中的html引用压缩后css后无法智能提示的问题解决
环境:win10x64 vs2015企业版 项目:空白web项目(.net framework4) 问题:html页面加入压缩后的css(eg:bootstrap.min.css),编码的时候无法智能 ...
- HDU 4125 Moles 段树+KMP
意甲冠军: 特定n, 下面是一个1-n该装置. 下面的二进制字符串. 按给定的建立二叉树安排. 然后遍历树(根->左子树->根->右子树->根) 当遍历节点 如果右值为奇数入栈 ...
- SharePoint 2013 母版页修改后,无法添加应用程序
原文:SharePoint 2013 母版页修改后,无法添加应用程序 问题描述:前一段时间尝试了一下将HTML文件转换为母版页,但是,用着用着又发现新的问题,我们转换的母版页,设置成默认母版页以后,无 ...
- Linux Kernel系列 - 黄牛X内核代码凝视
Hanks.Wang - 专注于操作系统与移动安全研究.Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM Mail - byhank ...
- 自制Https证书并在Spring Boot和Nginx中使用(转)
白话Https一文中, 介绍了Https存在的目的和工作原理,但多是偏向于原理性的介绍,本文介绍如何一步一步自制一个能够通过浏览器认证的Https证书,并讲解在Spring Boot环境和Nginx环 ...
- 5、VS2010+ASP.NET MVC4+EF4+JqueryEasyUI+Oracle该项目的开发——使用datagrid做报表
来点需要:我使用的数据库访问EF框架,但是,因为使用一个动态表来的统计报告中.单独是每天产生基于数据表,它是很难使用EF加盟前.所以我包装在两组数据库存取层的框内,一个是EF,一种是传统的ADO.NE ...
- HTML5分析实战WebSockets一个简短的引论
HTML5 WebSockets规范定义了API,同意web页面使用WebSockets与远程主机协议的双向通信. 介绍WebSocket接口,并限定了全双工通信信道,通过套接字网络. HTML5 W ...