信用卡创建失败事件发布方案:通用事件or多特定事件选型咨询
现有方案评估
先直接说你提到的三个方案的优劣:
- 方案1:扩展通用
FailedToCreateCard兼容所有服务商错误。这个方案的问题本质是把异构的服务商错误强耦合到单一事件结构中,每新增一个服务商就要修改事件定义,所有消费该事件的下游都要兼容变更,可扩展性极差,除非你能确定未来不会新增服务商,否则不建议选。 - 方案2:每个catch分支同时发布专属事件+通用事件。重复发布事件其实不是核心痛点,真正的问题是事件发布逻辑散落在各个异常分支中,后续新增服务商还要修改Consumer的catch逻辑,违反开闭原则,很容易出现漏发事件的问题,维护成本偏高。
- 方案3:服务商内部服务发专属事件,Consumer统一发通用事件。这个思路已经接近合理,唯一要注意的是要保证专属事件携带CardId这类全局业务标识,避免下游消费时无法关联业务上下文,排查问题还要跨事件做关联,增加运维成本。
行业通用最优方案
我们一般会用事件多态+责任拆分的思路来解决这类问题,兼顾扩展性和易用性:
1. 事件结构设计
把事件拆成通用基类和服务商专属子类:
- 通用基类
FailedToCreateCard:存放所有下游都需要的通用字段,包括CardId、请求ID、失败时间、服务商编码、通用错误描述,足够满足只需要感知「卡创建失败」结果的下游消费,不需要感知服务商差异。 - 服务商专属事件:继承
FailedToCreateCard,比如FailedToCreateProvider1Card、FailedToCreateProvider2Card,仅扩展对应服务商特有的错误字段,只供给需要获取错误详情的消费者(比如运维告警系统、服务商对账系统)使用。
2. 发布逻辑设计
把服务商调用逻辑封装成统一接口的实现类,不要把各个服务商的异常抛到Consumer层,由Provider实现类自行处理异常并返回标准化的调用结果,Consumer只做两层判断:
- 调用失败后如果有服务商专属错误结构,先发布对应的专属事件
- 无论是否有专属错误,都统一发布通用的基类事件
这样后续新增服务商时,只需要新增对应的Provider实现和专属事件类,不需要修改Consumer的核心逻辑,完全符合开闭原则。
如果你的事件总线支持多态消费(比如MassTransit、Kafka配合Schema Registry都支持),甚至不需要两次发布:直接发布子类的专属事件,订阅了基类事件的下游会自动收到消息,完全避免重复发布的问题。如果不支持多态,两次发布的成本也极低,远低于维护混乱事件结构的成本,完全可以接受。
改造后代码示例
// 通用失败基类事件 public class FailedToCreateCard { public Guid CardId { get; set; } public string ProviderCode { get; set; } public string CommonErrorMsg { get; set; } public DateTime FailTime { get; set; } } // Provider1专属失败事件 public class FailedToCreateProvider1Card : FailedToCreateCard { public string Provider1UniqueErrCode { get; set; } public string Provider1RequestTraceId { get; set; } // 其他Provider1特有的错误字段 } // Provider2专属失败事件 public class FailedToCreateProvider2Card : FailedToCreateCard { public int Provider2ErrorCode { get; set; } public bool CanRetry { get; set; } // 其他Provider2特有的错误字段 } public class CreateCardConsumer : IConsumer<CreateCard> { private readonly ICardProviderFactory _providerFactory; public async Task Consume(ConsumeContext<CreateVirtualCard> context) { try { var cardProvider = _providerFactory.GetByType(context.Message.ProviderType); var createResult = await cardProvider.Create(context.Message); if (!createResult.IsSuccess) { // 存在服务商专属错误则发布对应事件 if (createResult.ProviderSpecificEvent != null) { await context.Publish(createResult.ProviderSpecificEvent); } // 统一发布通用失败事件 await context.Publish<FailedToCreateCard>(new FailedToCreateCard { CardId = context.Message.CardId, ProviderCode = cardProvider.Code, CommonErrorMsg = createResult.ErrorMessage, FailTime = DateTime.UtcNow }); } } catch (Exception ex) { // 兜底未捕获的异常,仅发布通用事件 await context.Publish<FailedToCreateCard>(new FailedToCreateCard { CardId = context.Message.CardId, CommonErrorMsg = ex.Message, FailTime = DateTime.UtcNow }); } } }
内容的提问来源于stack exchange,提问作者emirkljucanin
相关产品推荐
相关产品推荐

