You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

消费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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 21:06:02