没有服务定位器的 CQRS 调度程序

CQRS Dispatcher Without Service Locator

我正在基于现有 Asp.Net 4.5 解决方案创建新的 Asp.Net 核心解决方案。

当前解决方案使用 Microsoft Unity Container 并且基础结构引用了服务定位器。

我想摆脱服务定位器并避免在我的新基础架构中引用特定的 DI 容器。

我遇到了一个问题,想出一个很好的方法来替换当前的 Command/Query/Event 没有任何 DI 容器依赖项的 Dispatcher。

这是我的调度程序class

public class Dispatcher : IDispatcher
{
    private const string HandleMethodName = "Handle";

    public TResponse Request<TResponse>(IQuery<TResponse> query)
    {
        Type queryType = query.GetType();

        // used for when OperationResult<object> was used
        Type operationResultTrueReturnType = typeof(TResponse);
        if (operationResultTrueReturnType == typeof(object))
        {
            operationResultTrueReturnType = queryType.GetInterface(typeof(IQuery<>).Name).GenericTypeArguments[0];
        }

        Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), operationResultTrueReturnType);
        return ExecuteHandler<TResponse>(handlerType, query, queryType);
    }

    public OperationResult Submit(ICommand command)
    {
        Type commandType = command.GetType();

        var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
        if (baseTypeAttribute != null)
            commandType = baseTypeAttribute.BaseType;

        try
        {
            Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
            return ExecuteHandler<OperationResult>(handlerType, command, commandType);
        }
        catch (InvalidOperationException ex)
        {
            return new OperationResult(OperationResultStatus.Failure, ex.Message);
        }
    }

    public OperationResult<TResult> Submit<TResult>(ICommand<TResult> command)
    {
        Type commandType = command.GetType();

        var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
        if (baseTypeAttribute != null)
            commandType = baseTypeAttribute.BaseType;

        try
        {
            Type handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, typeof(TResult));
            return ExecuteHandler<OperationResult<TResult>>(handlerType, command, commandType);
        }
        catch (InvalidOperationException ex)
        {
            return new OperationResult<TResult>(OperationResultStatus.Failure, default(TResult), ex.Message);
        }
    }

    public void Raise(IDomainEvent domainEvent)
    {
        Type domainEventType = domainEvent.GetType();

        try
        {
            Type handlerType = typeof(ICommandHandler<>).MakeGenericType(domainEventType);
            ExecuteHandler(handlerType, domainEvent, domainEventType);
        }
        catch (InvalidOperationException)
        {

        }
    }

    private static void ExecuteHandler(Type handlerType, object argument, Type argumentType)
    {
        object handler = ServiceLocator.Current.GetInstance(handlerType);

        if (handler == null)
            throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);

        try
        {
            MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
            handleMethod.Invoke(handler, new[] { argument });
        }
        catch (TargetInvocationException ex)
        {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
    }

    private static TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType)
    {
        object handler = ServiceLocator.Current.GetInstance(handlerType);

        if (handler == null)
            throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);

        try
        {
            MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
            return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
        }
        catch (TargetInvocationException ex)
        {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
    }
}

ExecuteHandler 调用了 ServiceLocator。

不使用它怎么处理?

我喜欢评论中提供的建议。如果您想抽象掉服务定位器,那么让调度程序明确依赖于将用于执行解析的抽象服务提供者(如 IServiceProvider)。

public class Dispatcher : IDispatcher {
    private readonly IServiceProvider serviceProvider;

    public Dispatcher (IServiceProvider serviceProvider) {
        this.serviceProvider = serviceProvider;
    }

    //...other code removed for brevity

    private object GetService(Type serviceType) {
        return serviceProvider.GetService(serviceType);
    }

    private void ExecuteHandler(Type handlerType, object argument, Type argumentType) {
        object handler = GetService(handlerType);

        if (handler == null)
            throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);

        try {
            MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
            handleMethod.Invoke(handler, new[] { argument });
        } catch (TargetInvocationException ex) {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
    }

    private TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType) {
        object handler = GetService(handlerType);

        if (handler == null)
            throw new ConfigurationErrorsException("Handler not registered for type " + argumentType.Name);

        try {
            MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
            return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
        } catch (TargetInvocationException ex) {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
    }    
}

调度程序现在不再与服务定位器反模式紧密耦合,并允许使用任何派生的提供程序。这使您可以避免引用特定的 DI 容器。

我在 Dispatcher 的构造函数中添加了一个 IServiceProvider

public class Dispatcher : IDispatcher
{
    private const string HandleMethodName = "Handle";

    private readonly IServiceProvider _serviceProvider;

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

    public TResponse Request<TResponse>(IQuery<TResponse> query)
    {
        Type queryType = query.GetType();

        // used for when OperationResult<object> was used
        Type operationResultTrueReturnType = typeof(TResponse);
        if (operationResultTrueReturnType == typeof(object))
        {
            operationResultTrueReturnType = queryType.GetInterface(typeof(IQuery<>).Name).GenericTypeArguments[0];
        }

        Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), operationResultTrueReturnType);
        return ExecuteHandler<TResponse>(handlerType, query, queryType);
    }

    public OperationResult Submit(ICommand command)
    {
        Type commandType = command.GetType();

        var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
        if (baseTypeAttribute != null)
            commandType = baseTypeAttribute.BaseType;

        try
        {
            Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
            return ExecuteHandler<OperationResult>(handlerType, command, commandType);
        }
        catch (InvalidOperationException ex)
        {
            return new OperationResult(OperationResultStatus.Failure, ex.Message);
        }
    }

    public OperationResult<TResult> Submit<TResult>(ICommand<TResult> command)
    {
        Type commandType = command.GetType();

        var baseTypeAttribute = (CommandBaseTypeAttribute)commandType.GetCustomAttributes(typeof(CommandBaseTypeAttribute), false).FirstOrDefault();
        if (baseTypeAttribute != null)
            commandType = baseTypeAttribute.BaseType;

        try
        {
            Type handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, typeof(TResult));
            return ExecuteHandler<OperationResult<TResult>>(handlerType, command, commandType);
        }
        catch (InvalidOperationException ex)
        {
            return new OperationResult<TResult>(OperationResultStatus.Failure, default(TResult), ex.Message);
        }
    }

    private TReturnValue ExecuteHandler<TReturnValue>(Type handlerType, object argument, Type argumentType)
    {
        object handler = _serviceProvider.GetService(handlerType);

        if (handler == null)
            throw new ArgumentException("Handler not registered for type " + argumentType.Name);

        try
        {
            MethodInfo handleMethod = handlerType.GetMethod(HandleMethodName, new[] { argumentType });
            return (TReturnValue)handleMethod.Invoke(handler, new[] { argument });
        }
        catch (TargetInvocationException ex)
        {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
    }
}

然后,在客户端的Startup中注入。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ResolutionDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();

    // Domain Event Handlers
    services.AddTransient<IEventHandler<RequestCreatedEvent>, RequestCreatedHandler>();

    // Domain Event Dispatcher
    services.AddSingleton<IDomainEventDispatcher, DomainEventDispatcher>();

    // Units of Work
    services.AddTransient<IResolutionUnitOfWork, ResolutionUnitOfWork>();

    // Commands and Queries
    services.AddTransient<ICommandHandler<CreateRequestCommand, Guid>, CreateRequestHandler>();

    // Command and Query Dispatcher
    services.AddSingleton<IDispatcher, Dispatcher>();
}