IDDD 实现领域驱动设计-一个简单的 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 系列(初级):

时间: 2024-08-26 15:12:49

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

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

http://www.italki.com/question/293028http://www.italki.com/question/293027http://www.italki.com/question/293026http://www.italki.com/question/293025http://www.italki.com/question/293024http://www.italki.com/question/293023http://www.italki.com/questi

[.NET领域驱动设计实战系列]专题十:DDD扩展内容:全面剖析CQRS模式实现

一.引言 前面介绍的所有专题都是基于经典的领域驱动实现的,然而,领域驱动除了经典的实现外,还可以基于CQRS模式来进行实现.本专题将全面剖析如何基于CQRS模式(Command Query Responsibility Segregation,命令查询职责分离)来实现领域驱动设计. 二.CQRS是什么? 在介绍具体的实现之前,对于之前不了解CQRS的朋友来说,首先第一个问题应该是:什么是CQRS啊?你倒是详细介绍完CQRS后再介绍具体实现啊?既然大家会有这样的问题,所以本专题首先全面介绍下什么是

领域驱动设计战略原则

之前发在别的地方了,据说标题不好,改个标题... 去年就打算总结一下,结果新换的工作特别忙,就迟迟没有认真动手.主要内容是很多初学DDD甚至于学习很长时间的同学没有弄明白DDD是什么,适合什么情况.这世界上没有银弹,抛开了适合的场景孤立的去研究DDD,在学习过程中还可以,但是应用到实际项目时就会遇到各种坑,到头来各种妥协,我看到很多同学遇到这种情况,最后怪DDD,说DDD不实用云云.另有一些都没细了解过就抨击反对的,事实上,要想否定个东西,你总要了解了才有发言权. 一.误区 综合起来主要有一下几

我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践

写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听你,清风吹送,田野短笛:第一次看你,半弯新湖,鱼跃翠堤:第一次念你,燕飞巢冷,释怀记忆:第一次梦你,云翔海岛,轮渡迤逦:第一次认你,怨江别续,草桥知己:第一次怕你,命悬一线,遗憾禁忌:第一次悟你,千年菩提,生死一起. 人生有很多的第一次:小时候第一次牙牙学语.第一次学蹒跚学步...长大后第一次上课.第一次逃课.第一次骑自行车.第一次懂事.第一次和喜

DDD领域驱动设计基本理论知识总结

领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD.领域驱动设计分为两个阶段: 以一种领域专家.设计人员.开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然后将这些概念设计成一个领域模型:由领域模型驱动软件设计,用代码来实现该领域模型:

(转载)浅谈我对DDD领域驱动设计的理解

原文地址:http://www.cnblogs.com/netfocus/p/5548025.html 从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品.所以,自然而然就想到要做一个普通电商系统,用于实现在线销售自己企业产品的目的. 再比如,我是一家互联网公司,公司有很多系统对外提供服务,面向很多客户端设备.但是最近由于各种原因,导致服务经常出故

WCF客户端配置以及代理-----基于DDD领域驱动设计的WCF+EF+WPF分层框架(4)

写在最前面:转载请注明出处 目录置顶: 关于项目--------------------基于DDD领域驱动设计的WCF+EF+WPF分层框架(1) 架构搭建--------------------基于DDD领域驱动设计的WCF+EF+WPF分层框架(2) WCF服务端具体实现---------基于DDD领域驱动设计的WCF+EF+WPF分层框架(3) WCF客户端配置以及代理-----基于DDD领域驱动设计的WCF+EF+WPF分层框架(4) Domain具体实现------------基于DD

领域驱动设计的面向服务架构

[.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店 一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这对于一些刚刚接触领域驱动设计的朋友可能会非常迷茫,从而觉得领域驱动设计很难,很复杂,因为学习中要消化一个整个案例的知识,这样未免很多人消化不了就打退堂鼓,就不继续研究下去了,所以这样也不利于DDD的推广.然而本系列

.NET领域驱动设计—实践(穿过迷雾走向光明)

阅读目录 开篇介绍 1.1示例介绍 (OnlineExamination在线考试系统介绍) 1.2分析.建模 (对真实业务进行分析.模型化) 1.2.1 用例分析 (提取系统的所有功能需求) 1.3系统设计.建模 (技术化业务模型) 1.3.1 枚举类型的使用 (别让枚举类型成为数值型对象) 1.3.2 基础数据.业务数据 (显示实体和隐式过程) 1.3.3 模型在数据库中的主外键关联问题 (面向对象模型与关系模型的天然抗阻) 1.3.4 角色.类型 (区分类型与面向对象概念) 1.3.5 名词