上一篇:《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 系列(初级):

IDDD 实现领域驱动设计-一个简单的 CQRS 示例的更多相关文章

  1. IDDD 实现领域驱动设计-一个简单业务用例的回顾和理解

    上一篇:<IDDD 实现领域驱动设计-由贫血导致的失忆症> 这篇博文是对<实现领域驱动设计>第一章后半部分内容的理解. Domain Experts-领域专家 这节点内容是昨天 ...

  2. IDDD 实现领域驱动设计-理解领域和子域

    上一篇:<IDDD 实现领域驱动设计-一个简单业务用例的回顾和理解> 在<实现领域驱动设计>第二章的前半部分内容中,提到领域和子域的概念,并且作者把这两者又进行了细致的区分,其 ...

  3. IDDD 实现领域驱动设计-理解限界上下文

    上一篇:<IDDD 实现领域驱动设计-理解领域和子域> <实现领域驱动设计>前两章内容,基本上读完了,和<领域驱动设计>不同的是,它把很多的概念都放在前面进行讲述了 ...

  4. IDDD 实现领域驱动设计-上下文映射图及其相关概念

    上一篇:<IDDD 实现领域驱动设计-理解限界上下文> 距离上一篇有几天时间了,<实现领域驱动设计>第三章的内容都是围绕一个词-上下文映射图,我大概断断续续看了几天,总共看了两 ...

  5. IDDD 实现领域驱动设计-CQRS(命令查询职责分离)和 EDA(事件驱动架构)

    上一篇:<IDDD 实现领域驱动设计-SOA.REST 和六边形架构> 阅读目录: CQRS-命令查询职责分离 EDA-事件驱动架构 Domin Event-领域事件 Long-Runni ...

  6. IDDD 实现领域驱动设计-架构之经典分层

    上一篇:<IDDD 实现领域驱动设计-上下文映射图及其相关概念> 在<实现领域驱动设计>书中,分层的概念作者讲述的很少,也就几页的内容,但对于我来说,有很多的感触需要诉说.之前 ...

  7. IDDD 实现领域驱动设计-SOA、REST 和六边形架构

    上一篇:<IDDD 实现领域驱动设计-架构之经典分层> 阅读目录: SOA-面向服务架构 REST 与 RESTful 资源(Resources) 状态(State) 六边形架构 DDD ...

  8. IDDD 实现领域驱动设计-由贫血导致的失忆症

    啰嗦几句 年前的时候,在和 netfocus 兄,以及对 DDD 感兴趣园友的探讨过程中,我发现自己有很多不足的地方,对 DDD 的了解也只是皮毛而已,代码写的少,DDD 的基本概念也不是很清楚,空有 ...

  9. 领域驱动设计系列 (六):CQRS

    CQRS是Command Query Responsibility Seperation(命令查询职责分离)的缩写. 世上很多事情都比较复杂,但是我们只要进行一些简单的分类后,那么事情就简单了很多,比 ...

随机推荐

  1. iOS中push视图的时候,屏幕中间会出现一条灰色的粗线的解决方案

    因为执行动画需要时间,所以在动画未完成又执行另一个切换动画,容易产生异常.所以在一个方法连续push不同的VC是没有问题的,但是不小心push同一个VC,并且animated为YES,可能多次跳转了 ...

  2. 印象笔记 剪藏(Evernote WebClipper) bug 记录

    问题记录: Chrome版的 webclipper不知为何新装的时候切换到中国版印象笔记登陆的时候闪退,然后之后就无法进入中国区的登录页面:international版确认可以登录. cookies是 ...

  3. Spark 应用程序调优

    对于很多刚接触Spark的人来说,可能主要关心数据处理的逻辑,而对于如何高效运行Spark应用程序了解较少.由于Spark是一种分布式内存计算框架,其性能往往受限于CPU.内存.网络等多方面的因素,对 ...

  4. 关于learntorank http://qiita.com/rockhopper/items/bb3d46f01df5f6499123

    一.数据转换 如何对于训练数据做pairwise的transform,比如你原始数据是要么点击要么不点击,如何对这些样本数据做pairwise的transform? 下面的方法主要是做组合的方法,就是 ...

  5. 深入super,看Python如何解决钻石继承难题 【转】

    原文地址 http://www.cnblogs.com/testview/p/4651198.html 1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通 ...

  6. MYSQL 5.0 USING BTREE 错误

    今天遇到个错误,导入数据库的时候报错,最后发现是php版本的问题导致包含 KEY `uniacid` (`uniacid`) USING BTREE 的SQL命令报错 5.1之前的写法: KEY `u ...

  7. android中webview调用拨号盘

    wv.setWebViewClient(new WebViewClient(){              public boolean shouldOverrideUrlLoading(WebVie ...

  8. Independent Components Analysis:独立成分分析

    一.引言 ICA主要用于解决盲源分离问题.需要假设源信号之间是统计独立的.而在实际问题中,独立性假设基本是合理的. 二.随机变量独立性的概念 对于任意两个随机变量X和Y,如果从Y中得不到任何关于X的信 ...

  9. 面试题系列——OSI七层模型

    OSI(开放式系统互联模型)是一个开放性的通行系统互连参考模型,是一个协议规范.它把网络协议从逻辑上分了七层,每一层都有对应的物理设备. OSI七层模型是一种框架性的设计方法,设计的主要目的是为了解决 ...

  10. Expert 诊断优化系列------------------透过等待看系统

    上一篇我们简单的介绍了,语句优化的三板斧,大部分语句三板斧过后,就算不成为法拉利也能是个宝马了.为了方便阅读给出系列文章的导读链接: SQL SERVER全面优化-------Expert for S ...