5

我正在尝试实现一个可消耗的库,该库在 CQRS 的上下文中为每个域提供读/写应用程序服务。命令总线(或调度程序,或在这种情况下可以调用的任何东西)接口可能会或可能不会暴露,但应该从消费者那里抽象出实现,以鼓励对接口定义的合约进行编程。我不想要求图书馆的消费者必须在他们的 DI 框架中设置图书馆,而不是使用标准约定,所以使用的 DI 框架应该无关紧要(要求基于约定的 DI 超出了这个问题的范围) .

internal interface ICommandMessage
{
    Guid Id { get; }
    DateTime DateRequestedUtc { get; }
}
internal class BaseCommandMessage
{
    /*... Implementation of ICommandMessage for common command data ...*/
}

internal class ExampleCommand : BaseCommandMessage
{
    /*... Additional data required for command ...*/
}

internal class AnotherExampleCommand : BaseCommandMessage
{
    /*... Additional data required for command ...*/
}

internal interface ICommandHandler<in TCommand> where TCommand : class, ICommandMessage
{
    Task HandleAsync(TCommand command);
}

internal class ExampleCommandHandler : ICommandHandler<ExampleCommand>, ICommandHandler<AnotherExampleCommand>
{
    Task HandleAsync(ExampleCommand command){/*...*/}
    Task HandleAsync(AnotherExampleCommand command){/*...*/}
}

public interface ICommandBus
{
    void Register<TCommand>(ICommandHandler<TCommand> handler) where TCommand : class, ICommandMessage;
    void Dispatch(ICommandMessage command);
}

public interface IDomainWriteService
{
    void Save(int id);
    /*... some other read methods ...*/
}

internal class SomeDomainWriteService
{
    private readonly ICommandBus _Bus;

    public SomeDomainWriteService(ICommandBus bus)
    {
        _Bus = bus;
    }

    public void Save(int id)
    {
        //This will change depending on how ExampleCommand is implemented, etc
        _Bus.Dispatch(new ExampleCommand());
    }
}

主要问题在于我希望 ICommandHandler 的内部实现以某种方式自动注册到命令总线,但构造函数不采用泛型,如下面的实现:

internal public class DependencyInjectedCommandBus : ICommandBus
{
    private readonly Dictionary<Type, Action<ICommandMessage>> _handlers = new Dictionary<Type, Action<ICommandMessage>>();

    public DependencyInjectedCommandBus(List<ICommandHandler<TCommand>> handlers)
    {
        handlers.forEach(h => Register<TCommand>(h));
    }

    public void Register<TCommand>(ICommandHandler<TCommand> handler) where TCommand : class, ICommandMessage
    {
        var type = typeof (TCommand);
        if (_Handlers.ContainsKey(type))
        {
            throw new InvalidOperationException(string.Format("Handler exists for type {0}.", type));
        }
        _Handlers[type] = async command => await handler.HandleAsync((TCommand)command);
    }

    public void Dispatch(ICommandMessage command)
    {
        var type = command.GetType();
        if(!_Handlers.ContainsKey(type)){ return; }
        var handler = _Handlers[type];
        handler(command);
    }
}

使用 DI 容器(在本例中为 Ninject),如果 ICommandBus 稍作更改,则不允许注册的实现可能如下所示:

internal class NinjectCommandBus : ICommandBus
{
    private readonly IKernel _Kernel;

    public NinjectCommandBus(IKernel kernel)
    {
        _Kernel = kernel;
    }

    public void Register<TCommand>(ICommandHandler<TCommand> handler) where TCommand : class, ICommandMessage
    {
        throw new NotImplementedException();
    }

    public async Task DispatchAsync<TCommand>(TCommand command) where TCommand : class, ICommandMessage
    {
        var handler = _Kernel.Get<ICommandHandler<TCommand>>();
        await handler.HandleAsync(command);
    }
}

我还在Mark Seeman 的博客上阅读过类似这样的文章,这些文章描述了在没有服务位置或依赖 DI 容器(通过“穷人的 DI”)的情况下执行此操作的方法,但是我无法通过 Ninject 获得这种解决方案。全部(更不用说以某种方式使用约定或不依赖 DI 为我完成工作),并且似乎需要更多的“锅炉”代码才能将事情连接起来。

关于如何在不必在某处显式注册处理程序的情况下进行此操作的任何建议?我关于通过注册命令处理程序来允许可扩展性的想法是否有效?

4

1 回答 1

4

如果您不介意为命令处理程序扫描程序集,这里有一个无需 DI 容器即可工作的简单解决方案。

namespace SimpleCqrs {
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Reflection;
  using System.Threading.Tasks;

  public interface ICommandMessage {
    Guid Id { get; }
    DateTime DateRequestedUtc { get; }
  }

  internal abstract class BaseCommandMessage : ICommandMessage {
    protected BaseCommandMessage() {
      DateRequestedUtc = DateTime.UtcNow;
      Id = Guid.NewGuid();
    }

    public DateTime DateRequestedUtc {
      get; private set;
    }

    public Guid Id {
      get; private set;
    }
  }

  internal class ExampleCommand : BaseCommandMessage {
    public string Message { get; set; }
  }

  internal class AnotherExampleCommand : BaseCommandMessage {
    public string Message { get; set; }
  }

  internal interface ICommandHandler<in TCommand> where TCommand : class, ICommandMessage
  {
    Task HandleAsync(TCommand command);
  }

  internal class WriteService : ICommandHandler<ExampleCommand>, ICommandHandler<AnotherExampleCommand>
  {
    public Task HandleAsync(AnotherExampleCommand command) {
      return Task.Run(() => {
        Console.WriteLine(command.Message);
      });
    }

    public Task HandleAsync(ExampleCommand command)
    {
      return Task.Run(() =>
      {
        Console.WriteLine(command.Message);
      });
    }
  }

  public interface ICommandBus
  {
   void Dispatch(ICommandMessage command);
  }

  public class SimpleCommandBus : ICommandBus
  {
    Dictionary<Type, Type> handlers;
    MethodInfo dispatchCommand;

    public SimpleCommandBus()
    {
      this.handlers = RegisterCommandHandlers();
      this.dispatchCommand = GetType().GetMethod("DispatchCommand", BindingFlags.NonPublic | BindingFlags.Instance);
    }

    public void Dispatch(ICommandMessage command)
    {
      var cmdType = command.GetType();
      var handler = Activator.CreateInstance(handlers[cmdType]);
      var genericMethod = dispatchCommand.MakeGenericMethod(cmdType);
      genericMethod.Invoke(this, new object[] { handler, command });
    }

    async void DispatchCommand<T>(ICommandHandler<T> handler, T command) where T : class, ICommandMessage
    {
      await handler.HandleAsync(command);
    }

    Dictionary<Type, Type> RegisterCommandHandlers()
    {
      Func<Type, bool> isCommandHandler = t => 
        t.GetInterfaces()
         .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandler<>));

      Func<Type, IEnumerable<Tuple<Type, Type>>> collect = t =>
        t.GetInterfaces().Select(i =>       
          Tuple.Create(i.GetGenericArguments()[0], t));

      return Assembly.GetCallingAssembly()
                     .GetTypes()
                     .Where(t => !t.IsAbstract && !t.IsGenericType)
                     .Where(isCommandHandler)
                     .SelectMany(collect)
                     .ToDictionary(x => x.Item1, x => x.Item2);
    }
  }


  class Program
  {
    static void Main(string[] args)
    {
      var bus = new SimpleCommandBus();
      bus.Dispatch(new ExampleCommand { Message = "Hello" });
      bus.Dispatch(new AnotherExampleCommand { Message = "World" });
      Console.ReadKey();
    }
  }
}
于 2015-06-27T05:22:30.710 回答