消费OneOf多类型返回REST API 如何设计可扩展的消息处理方案
解决方案
核心采用「自定义多态反序列化转换器 + 策略模式」实现,完全满足可扩展要求,新增消息类型无需改动原有核心逻辑。
1. 定义公共消息接口
首先提取所有消息的公共属性,定义统一接口约束所有消息类型:
public interface IBrokerMessage { string MessageId { get; set; } string DocumentId { get; set; } } // 具体消息类实现接口即可 public class SentDocumentStatusChangedMessage : IBrokerMessage { public string DocumentId { get; set; } public string MessageId { get; set; } public string Status { get; set; } } public class DocumentReceivedMessage : IBrokerMessage { public string DocumentId { get; set; } public string DocumentType { get; set; } public BusinessValidationReport BusinessValidationReport { get; set; } public string MessageId { get; set; } }
2. 实现自定义JsonConverter处理多类型反序列化
不需要把所有消息属性都塞到统一的BrokerMessage类里,通过自定义转换器自动识别JSON根节点的消息类型,直接反序列化为对应实现类:
public class BrokerMessageJsonConverter : JsonConverter<IBrokerMessage> { // 存储JSON根键与对应CLR类型的映射关系 private readonly Dictionary<string, Type> _messageTypeMap = new() { ["sentDocumentStatusChangedMessage"] = typeof(SentDocumentStatusChangedMessage), ["documentReceivedMessage"] = typeof(DocumentReceivedMessage) }; public override IBrokerMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var jsonDoc = JsonDocument.ParseValue(ref reader); var root = jsonDoc.RootElement; // 取JSON第一层唯一属性名作为消息类型标识 var messageTypeProperty = root.EnumerateObject().First(); var messageKey = messageTypeProperty.Name; if (!_messageTypeMap.TryGetValue(messageKey, out var messageClrType)) { // 可自定义未知消息类型的处理逻辑 throw new NotSupportedException($"未适配的消息类型:{messageKey}"); } // 直接反序列化属性值为对应消息类型 return messageTypeProperty.Value.Deserialize(messageClrType, options) as IBrokerMessage; } public override void Write(Utf8JsonWriter writer, IBrokerMessage value, JsonSerializerOptions options) { // 序列化逻辑按需实现即可 throw new NotImplementedException(); } }
3. 反序列化时注册转换器
调用反序列化方法时传入配置了自定义转换器的参数即可:
var serializeOptions = new JsonSerializerOptions { Converters = { new BrokerMessageJsonConverter() }, PropertyNameCaseInsensitive = true }; // 直接反序列化为IBrokerMessage接口类型 var message = await response.Content.ReadFromJsonAsync<IBrokerMessage>(serializeOptions);
4. 用策略模式实现不同消息的业务逻辑分发
定义统一的消息处理接口,每种消息对应独立的处理器,逻辑完全解耦:
public interface IMessageHandler { Task HandleAsync(IBrokerMessage message); } // 每种消息对应独立的处理器实现 public class SentDocumentStatusChangedHandler : IMessageHandler { public async Task HandleAsync(IBrokerMessage message) { var typedMsg = message as SentDocumentStatusChangedMessage; // 执行该类型消息对应的业务逻辑 } } public class DocumentReceivedHandler : IMessageHandler { public async Task HandleAsync(IBrokerMessage message) { var typedMsg = message as DocumentReceivedMessage; // 执行该类型消息对应的业务逻辑 } }
注册消息类型与处理器的映射关系,拿到反序列化后的消息后自动匹配对应处理器执行:
// 映射关系也可以通过依赖注入自动扫描程序集注册,无需手动维护 private readonly Dictionary<Type, Type> _handlerMap = new() { [typeof(SentDocumentStatusChangedMessage)] = typeof(SentDocumentStatusChangedHandler), [typeof(DocumentReceivedMessage)] = typeof(DocumentReceivedHandler) }; // 消息分发逻辑 if (_handlerMap.TryGetValue(message.GetType(), out var handlerType)) { var handler = serviceProvider.GetRequiredService(handlerType) as IMessageHandler; await handler.HandleAsync(message); }
后续扩展方式
新增消息类型仅需做3步,完全符合开闭原则:
- 新增实现
IBrokerMessage的具体消息类 - 在
BrokerMessageJsonConverter的_messageTypeMap中新增JSON根键与类的映射 - 新增对应
IMessageHandler的实现类,新增处理器映射
如果配合依赖注入的程序集扫描能力,可以完全不用修改原有代码,自动完成新增消息和处理器的注册。
内容的提问来源于stack exchange,提问作者Grzegorz
相关产品推荐
相关产品推荐

