首页新闻找找看学习计划

学习CQRS 中C端使用MQ的一些问题?

0
悬赏园豆:120 [已解决问题] 解决于 2016-12-06 08:47
 public async Task HandleAsync(UpdateUserCommand message)
        {
            var user = await domainRepository.GetByKeyAsync<Guid, User>(message.UserId);
            bool updated = false;
            if (!string.IsNullOrEmpty(message.DisplayName) && user.DisplayName != message.DisplayName)
            {
                user.ChangeDisplayName(message.DisplayName);
                updated = true;
            }
            if (!string.IsNullOrEmpty(message.Email) && user.Email != message.Email)
            {
                user.ChangeEmail(message.Email);
                updated = true;
            }
            if (updated)
            {
                await domainRepository.SaveAsync<Guid, User>(user);
            }
        }

如上代码,我在聚合根内根据条件挂载上事件,在调用saveasync时对事件进行消费,

      public async Task SaveAsync<TKey, TAggregateRoot>(TAggregateRoot aggregateRoot, bool purge = true)
            where TKey : IEquatable<TKey>
            where TAggregateRoot : class, IAggregateRoot<TKey>, new()
        {
            // When doing a CQRS architecture with Event Sourcing (ES), this step should be going
            // to save the events occurred within the aggregate. In this example, we simply save
            // the entire aggregate root to avoid handling the snapshots.

            await this.SaveAggregateAsync<TKey, TAggregateRoot>(aggregateRoot);
            foreach(var evnt in aggregateRoot.UncommittedEvents)
            {
                messagePublisher.Publish(evnt);
            }

            if (purge)
            {
                ((IPurgeable)aggregateRoot).Purge();
            }
        }
messagePublisher.Publish(evnt);是发送事件到mq。
现在的问题是,我如何判断某个环节的状态呢?例如更新用户名称失败了,就不应该继续去更新用户邮箱,包括对
请求端的返回状态信息如何处理呢?简单理解就是MQ发送任务到消费者端,消费者发生异常如何通知请求端呢?

以上代码摘自dax.net的WeText 项目
_tom的主页 _tom | 初学一级 | 园豆:129
提问于:2016-12-03 17:02
< >
分享
最佳答案
0

1.你说到的用户名更新和邮箱更新问题,这个属于数据一致性问题,应用端只需要发送消息,说我这边有个event,要把这些信息更新成什么什么。后面自己来保证一致性。

2.mq本身是没办法通知的,因为你这里已经异步了。简单的做法有两种:服务端消费消息后给发送方一个callback(比如一个http调用,告知你这个消息我已经怎么处理了);应用方对处理结果做轮询(在单位时间内查询理应变化的状态的值是否出现变更),从而得知服务端消费结果。

收获园豆:120
Daniel Cai | 专家六级 |园豆:10374 | 2016-12-05 14:27

我明白您的意思,假设一个银行转账的例子,甲乙两个人,甲转账给乙,需要先从甲扣除余额,钱到银行交易流水中心,银行交易流水中心转账给乙,如上三步骤,按照ddd CQRS方式,我把事件的预处理操作都放到了DomainEventList当中,当提交这个Command进行事件挨个消费,我现在不理解的就是,假设第一步,我本身余额不足,应该是失败的,但是这一步我已经异步处理了。接下来的到银行交易流水中心,和转账给乙的两个步骤都是应该不往下进行的,使用MQ就只能类似http调用形式重新发给客户端么,但是这中间在客户端我的线程状态就要一直等待了,感觉设计很不合理,之前因为没有详细接触过CQRS架构,所以这块一直想不明白别人是怎么处理的。请您指教!

_tom | 园豆:129 (初学一级) | 2016-12-05 17:20

@_tom: 没在实际场景中用过ddd实现这种逻辑。

你提到的转账,我觉得整个时序应该是

1.发起转账消息

2.检查并扣减转出账户转账金额(如失败则发送失败消息,由后续服务完成冲正流程),由后续消费者继续消费

3.给目标账户添加对应金额(如失败则同上),继续由后续消费者消费

4.写入转账记录(由业务端保证一定成功)

(实际财务中可能出现中间账户做中转,这里省略)

 

我觉得你的想法在这里存在一个误区,在宏观上看转账应该保证至少最终一致性,如果你在最前面就按照command方式拆细了,那么后面就出现你碰到的这种问题:消息出去了,但是又很难在技术上同步感知到消息处理的结果(用于后续流程)。

抛开ddd而言,转账这个过程作为使用方而言不应该知道其细节(可能某些ddd的书上为了讲解,特别是混合了unit of work后提到了转账这个例子),所以其应该只发出转账一个很单纯的指令,而后续指令是如何消费则应该交由不同环节处理,当一个环节处理成功后再往后面发送后续消息,这样整个流程才能比较好的顺下去(实现上也容易些)。

再说你这个例子,我觉得不太好,因为转账这玩意需要保证事务性,虽然从ddd的出发点看就是一个account的加一个account的减,但很容易被转账实际业务复杂性给硬生生的把考虑范围给扩大了。

另外说下cqrs,虽然这玩意有着很吸引人的特点,而且特别迎合线性思维,加上这玩意提到的什么消息重放啥的,但感觉在一般业务场景中很难用上(自己感觉),可能自己水平有限。

我记得ms有些这些demo,在github上翻下吧

Daniel Cai | 园豆:10374 (专家六级) | 2016-12-05 18:00

@Daniel Cai: 嗯... 大致明白了,可能是因为CQRS模式,而有点过度设计了,所以思想上有些误区,感谢感谢。

_tom | 园豆:129 (初学一级) | 2016-12-06 08:46
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册