在Saga与消息传递场景中使用MessageData及Claim Check模式的正确方式
在Saga与消息传递场景中使用Claim Check模式的最佳实践
当在Saga中使用Claim Check模式,或者需要将消息数据传递至其他消息时,以下是对两种可选方案的分析,以及普通消费者传递数据场景的最佳实践建议:
Saga场景下的方案分析
方案1:存储完整数据副本
- 实现逻辑:将消息数据反序列化后完整存入Saga状态,后续传递数据时通过
Put方法在消息数据仓库创建新的数据副本。 - 代码示例:
// 消息发送端 Content = await _messageDataRepository.PutString(content), // Saga接收消息时 context.Saga.Content = await context.Message.Content.Value; // 后续在Saga中传递该值时,再次调用PutString Content = await _messageDataRepository.PutString(content), - 优缺点:安全性高,数据独立存储,原数据删除不会影响Saga流程推进;但会造成Saga状态与消息数据仓库中大量数据重复,增加存储成本。
方案2:存储数据地址
- 实现逻辑:仅存储消息数据仓库中已有数据的Uri地址,后续传递时通过地址直接获取数据。由于MongoDB无法将
MessageData<T>序列化为Bson格式,因此只存储地址是可行的折中方案。 - 代码示例:
// 消息发送端 Content = await _messageDataRepository.PutString(content), // Saga接收消息时 context.Saga.ContentAddress = context.Message.Content.Address; // 后续在Saga中传递该值时 Content = new GetMessageData<T>(address, _messageDataRepository, new StringMessageDataConverter(), cancellationToken); - 优缺点:避免数据重复,大幅降低存储开销;但依赖原数据的可用性,若原数据在Saga流程结束前被清理,会导致流程中断,且看似偏离了Claim Check模式封装数据访问的设计初衷。
普通消费者传递数据的场景分析
消费者接收消息后发布新事件时,同样面临两种选择:
public async Task Consume(ConsumeContext<MyMessage> context) { var content = await context.Message.Content.Value; // 方式1:直接复用原消息的Content引用 await context.Publish<MyOtherMessage>(new { Content = context.Message.Content; }); // 方式2:创建新的数据副本 await context.Publish<MyOtherMessage>(new { Content = await _messageDataRepository.PutString(content); }); }
最佳实践建议
优先采用存储数据地址的方案,配套数据生命周期管理
Claim Check模式的核心目标是缩减消息体大小,而非强制数据副本。只要保证数据在Saga流程或消息传递链路的生命周期内可用,存储地址就是合理选择。可以为消息数据仓库设置与Saga超时时间匹配的TTL(生存时间),确保流程结束后数据自动清理,避免存储冗余。仅在特定场景下使用数据副本方案
如果Saga流程中需要修改原始数据,或者业务场景对数据一致性、可用性有极高要求(如金融交易),则必须存储完整数据副本,确保流程不受外部存储的影响。普通消费者场景的选择原则
- 若只是转发未修改的原始数据,直接复用原
Content的地址即可,无需创建新副本,减少不必要的存储消耗。 - 若需要修改数据后再传递,必须创建新的数据副本存入消息数据仓库,避免修改操作影响原始数据的完整性。
- 若只是转发未修改的原始数据,直接复用原
内容的提问来源于stack exchange,提问作者Robert Davey
相关产品推荐
相关产品推荐

