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 项目
1.你说到的用户名更新和邮箱更新问题,这个属于数据一致性问题,应用端只需要发送消息,说我这边有个event,要把这些信息更新成什么什么。后面自己来保证一致性。
2.mq本身是没办法通知的,因为你这里已经异步了。简单的做法有两种:服务端消费消息后给发送方一个callback(比如一个http调用,告知你这个消息我已经怎么处理了);应用方对处理结果做轮询(在单位时间内查询理应变化的状态的值是否出现变更),从而得知服务端消费结果。
我明白您的意思,假设一个银行转账的例子,甲乙两个人,甲转账给乙,需要先从甲扣除余额,钱到银行交易流水中心,银行交易流水中心转账给乙,如上三步骤,按照ddd CQRS方式,我把事件的预处理操作都放到了DomainEventList当中,当提交这个Command进行事件挨个消费,我现在不理解的就是,假设第一步,我本身余额不足,应该是失败的,但是这一步我已经异步处理了。接下来的到银行交易流水中心,和转账给乙的两个步骤都是应该不往下进行的,使用MQ就只能类似http调用形式重新发给客户端么,但是这中间在客户端我的线程状态就要一直等待了,感觉设计很不合理,之前因为没有详细接触过CQRS架构,所以这块一直想不明白别人是怎么处理的。请您指教!
@_tom: 没在实际场景中用过ddd实现这种逻辑。
你提到的转账,我觉得整个时序应该是
1.发起转账消息
2.检查并扣减转出账户转账金额(如失败则发送失败消息,由后续服务完成冲正流程),由后续消费者继续消费
3.给目标账户添加对应金额(如失败则同上),继续由后续消费者消费
4.写入转账记录(由业务端保证一定成功)
(实际财务中可能出现中间账户做中转,这里省略)
我觉得你的想法在这里存在一个误区,在宏观上看转账应该保证至少最终一致性,如果你在最前面就按照command方式拆细了,那么后面就出现你碰到的这种问题:消息出去了,但是又很难在技术上同步感知到消息处理的结果(用于后续流程)。
抛开ddd而言,转账这个过程作为使用方而言不应该知道其细节(可能某些ddd的书上为了讲解,特别是混合了unit of work后提到了转账这个例子),所以其应该只发出转账一个很单纯的指令,而后续指令是如何消费则应该交由不同环节处理,当一个环节处理成功后再往后面发送后续消息,这样整个流程才能比较好的顺下去(实现上也容易些)。
再说你这个例子,我觉得不太好,因为转账这玩意需要保证事务性,虽然从ddd的出发点看就是一个account的加一个account的减,但很容易被转账实际业务复杂性给硬生生的把考虑范围给扩大了。
另外说下cqrs,虽然这玩意有着很吸引人的特点,而且特别迎合线性思维,加上这玩意提到的什么消息重放啥的,但感觉在一般业务场景中很难用上(自己感觉),可能自己水平有限。
我记得ms有些这些demo,在github上翻下吧
@Daniel Cai: 嗯... 大致明白了,可能是因为CQRS模式,而有点过度设计了,所以思想上有些误区,感谢感谢。