永发信息网

如何基于一个现有的系统进行cqrs改造

答案:1  悬赏:80  手机版
解决时间 2021-01-08 00:05
如何基于一个现有的系统进行cqrs改造
最佳答案
IDDD 实现领域驱动设计-一个简单的 CQRS 示例


学习架构知识,需要有一些功底和经验,要不然你会和我一样吃力,CQRS、EDA、ES、Saga 等等,这些是实践 DDD 所必不可少的架构,所以,如果你不懂这些,是很难看懂上篇所提到的 CQRS Journey 和 ENode 项目,那怎么办呢?我们可以从简单的 Demo 一点一滴开始。



说明:一张很丑陋的图
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.Send(commond);
项目中所有的类型映射都是通过 IoC 进行注入,ICommandBus 接口定义为:void Send(TCommand cmd) where TCommand : ICommand;,CommandBus 的实现主要是在 Send 方法中,解析 ICommandHandler 注入的类型对象,然后调用 ICommandHandler 接口定义的 Handle 方法,传入 TCommand 参数对象。
SendMessageCommond 的定义很简单,主要是一些来自 UI 的参数,所以,我们一般会定义一些属性字段,有时候我们会进行数据验证,可以使用 Validate,方法签名是: IEnumerable Validate(ValidationContext validationContext),不过需要继承 IValidatableObject 接口,这样我们就可以在 MVC View 前端进行输出验证结果,使用起来非常方便,SendMessageCommond 继承一个无实现的 ICommand 接口,主要用来约束类型,SendMessageCommond 对应 SendMessageCommondHandler,实现代码:
public class SendMessageCommondHandler :     ICommandHandler{    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 @event){    var message = this.BuildMessage(@event);    this.sender.Send(message);}private Message BuildMessage(Envelope @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,    IEventHandler{    private IEventBus eventBus;    public void Handle(MessageSentEvent @event)    {        eventBus = IocContainer.Default.Resolve();        new MessageRepository().Save(@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实践(1): 什么是CQRS
CQRS实践(2): Command的实现
CQRS实践(3): Command执行结果的返回
CQRS实践(4): 领域事件
我要举报
如以上问答信息为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
大家都在看
求双色球内部号码一注
株洲到韶山的客运汽车经过株易路口吗?
求《冰雪奇缘》主题曲let it go日语版歌词,
讲讲现在为什么不玩DNF了
一个男生喜欢一个女生时会急于和其他女生撇清
快照(院校街34号附近快照)地址好找么,我有些
汉字里有含山木的字吗
小白一只:ESP8266-07WiFi模块串口无输出,请
两根网线怎么按颜色接起来?急求!
福田祥菱各方面性能如何?
山东省烟台市牟平区高陵镇 名字怎么来的
查询女人想做胸部用什么办法最好?
晚上在十字路口不小心摔跟头了那晚穿的衣服是
达到轻伤二级判多少年?
志达汽车维修中心地址有知道的么?有点事想过
推荐资讯
公制螺纹,牙深怎么算
求鉴定:广东省造光绪元宝库平一两,给出详细
真丝50%麻50%的面料缩水严重吗?
怎样购买248元的windows8正式版?只有农行网
西铁城e8700手表的秒针经常不走是怎么回事
3+6+9+12+…+30求过程
oracle和SQL SERVER以及DB2之间的区别
四川省自贡荣县农村土地补偿标准价格是多少?
请问天津有没有锻造厂?
管道中有水补焊对管道的金属组织有何影响?求
超级兔子魔法设置和超级兔子是一个软件吗?
阜阳有什么煤矿吗?
正方形一边上任一点到这个正方形两条对角线的
阴历怎么看 ?