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

CQRS架构下如何避免用Switch Case调用命令处理器?

替代Azure Function中Switch-Case的CQRS命令处理器方案

这是个非常典型的CQRS场景——当命令处理器数量超过10个后,switch-case会快速变得臃肿、难以维护,每次新增命令都要修改核心逻辑,完全违反了开闭原则。我给你推荐两种适配Azure Function环境的优雅替代方案,核心思路都是依赖注入+策略模式:

方案一:依赖注入(DI)+ 动态类型解析(推荐)

如果你的Azure Function使用的是.NET Core 3.1+版本(支持内置DI),这是最优解。它能让你新增命令处理器时,只需要实现接口并注册,完全不需要修改Function的核心代码。

步骤1:注册所有命令处理器到DI容器

首先创建Startup.cs配置DI(如果还没有的话):

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(YourAppNamespace.Startup))]
namespace YourAppNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            // 注册所有命令处理器(示例)
            builder.Services.AddScoped<ICommandHandler<CardBlockCommand>, CardBlockCommandHandler>();
            builder.Services.AddScoped<ICommandHandler<CardCancelledCommand>, CardCancelledCommandHandler>();
            builder.Services.AddScoped<ICommandHandler<CardUnBlockCommand>, CardUnBlockCommandHandler>();
            // 剩下的12+处理器依次注册...

            // 注册处理器解析服务
            builder.Services.AddScoped<ICommandHandlerResolver, CommandHandlerResolver>();
        }
    }
}

步骤2:实现处理器解析器

创建一个通用的解析器,用来根据命令类型动态获取对应的处理器:

public interface ICommandHandlerResolver
{
    // 动态处理任意命令
    void HandleCommand(ICommand command);
}

public class CommandHandlerResolver : ICommandHandlerResolver
{
    private readonly IServiceProvider _serviceProvider;
    // 缓存命令类型与处理器类型的映射,避免重复反射
    private static readonly Dictionary<Type, Type> _handlerTypeCache = new();

    public CommandHandlerResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void HandleCommand(ICommand command)
    {
        var commandType = command.GetType();
        
        // 从缓存获取或构建处理器类型
        if (!_handlerTypeCache.TryGetValue(commandType, out var handlerType))
        {
            handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
            _handlerTypeCache[commandType] = handlerType;
        }

        // 从DI获取处理器实例并调用Handle方法
        var handler = _serviceProvider.GetRequiredService(handlerType);
        var handleMethod = handlerType.GetMethod(nameof(ICommandHandler<ICommand>.Handle));
        handleMethod?.Invoke(handler, new[] { command });
    }
}

步骤3:修改Azure Function代码替换Switch-Case

现在只需要注入解析器,一行代码就能处理所有命令:

[FunctionName("ReceiveEvent")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log,
    // 注入处理器解析器
    ICommandHandlerResolver handlerResolver)
{
    IActionResult actionResult = null;
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    var command = await _commandMapper.Map(requestBody);

    if (_commandValidator.Validate(req, command, ref actionResult))
    {
        // 替换所有switch-case的核心代码
        handlerResolver.HandleCommand(command);
        
        return actionResult;
    }

    return actionResult ?? new BadRequestResult();
}

方案二:预构建处理器字典(兼容旧版Azure Function)

如果你的Azure Function版本不支持DI,或者你需要更简单的过渡方案,可以用字典来维护事件类型与处理器的映射:

// 在Function类中静态初始化字典(只执行一次)
private static readonly Dictionary<EventType, Action<ICommand>> _commandHandlerMap = new()
{
    { EventType.CARD_BLOCK, cmd => _cardBlockCommandHandler.Handle(cmd as CardBlockCommand) },
    { EventType.CARD_CANCEL, cmd => _cardCancelledCommandHandler.Handle(cmd as CardCancelledCommand) },
    { EventType.CARD_UNBLOCK, cmd => _cardUnBlockHandler.Handle(cmd as CardUnBlockCommand) },
    // 其他处理器依次添加...
};

// 在Function方法中调用
if (_commandHandlerMap.TryGetValue(command.EventType, out var handler))
{
    handler(command);
}

这种方式比switch-case更易维护,但新增处理器时仍需手动更新字典,适合临时过渡。

方案优势对比

特性DI+动态解析方案字典映射方案
符合开闭原则✅ 完全符合❌ 需要修改字典
扩展性(新增处理器)✅ 只需实现+注册❌ 需修改字典
类型安全性✅ 反射保证类型匹配⚠️ 需要手动强转
维护成本极低中等

额外优化建议

  • 如果你希望Handle方法支持异步,可以修改ICommandHandler接口为异步版本:
    public interface ICommandHandler<TCommand> where TCommand : ICommand
    {
        Task Handle(TCommand command);
    }
    
    然后在解析器中调用异步方法即可。
  • 可以添加全局异常处理,在解析器中捕获处理器的异常并统一日志记录。

内容的提问来源于stack exchange,提问作者Rakesh Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 17:12:30