【问题标题】:CQRS Dispatcher Without Service Locator没有服务定位器的 CQRS 调度程序
【发布时间】:2017-07-13 04:17:40
【问题描述】:

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

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

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

我想出一个好方法来替换当前的命令/查询/事件调度程序,而不需要任何 DI 容器依赖项。

这是我的 Dispatcher 类

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 调用。

不使用如何处理?

【问题讨论】:

  • 解决方案是制作一个 infrastructure 组件,它是您注入容器的组合根目录的一部分(替换此 Dispatcher)。有关示例,请参见 the QueryProcessor class here
  • 我喜欢 NightOwl888 的建议。如果您想抽象出服务定位器,则让调度程序显式依赖将用于执行解析的服务提供者 (IServiceProvider)。
  • 由于处理程序基本上是单例,你应该能够在你的处理程序上添加约束,即ICommand作为命令的标记接口,然后命令处理程序将是ICommandHandler&lt;ICommand&gt;。将您的处理程序注册为单例,然后通过IEnumerable&lt;ICommandHandler&lt;ICommand&gt;&gt; 解析并将它们注册到IDictionary&lt;Type, ICommandHandler&lt;ICommand&gt;&gt; 以便于访问。这样,您还可以避免直接使用容器并将调度程序从“应用程序层”移动到核心层之一(服务/域层)

标签: c# asp.net asp.net-core cqrs


【解决方案1】:

我喜欢 cmets 中提供的建议。如果您想抽象出服务定位器,则让调度程序显式依赖将用于执行解析的抽象服务提供者(如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 容器。

【讨论】:

  • 哈,这正是我最终所做的。
【解决方案2】:

我在 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>();
}

【讨论】:

  • 在注册调度器之前不需要构建提供者。 services.AddSingleton&lt;IDispatcher, Dispatcher&gt;(serviceProvider =&gt; new Dispatcher(serviceProvider));
  • @Nkosi,谢谢,我删除并更新了帖子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-23
  • 1970-01-01
相关资源
最近更新 更多