I am trying to implement a consumable library that has read/write Application services for each Domain in the context of CQRS. A Command Bus (or Dispatcher, or whatever it can be called in this case) interface may or may not be exposed, but implementations should be abstracted from consumers to encourage programming toward the contracts that the interfaces define. I do not want to require consumers of the library to have to set up the library in their DI framework beyond using standard conventions, so the DI framework used should't matter (requiring convention-based DI is outside of the scope of this problem).
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());
}
}
The main issue is in the fact that I would like internal implementations of ICommandHandler to be auto-registered with the Command Bus somehow, but constructors don't take generics, as in the following implementation:
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);
}
}
Using a DI container (in this case, Ninject), an implementation without allowing for registration might look like this, provided ICommandBus changed a bit:
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);
}
}
I have also read articles like this one on Mark Seeman's blog that describe ways to do it without Service Location or depending on a DI container (via "poor man's DI") however I could not get that sort of solution working for me via Ninject at all (let alone in some way using conventions or without depending on DI to do the work for me), and seems to require a bit more "boiler" code just to wire things up.
Any advice on how to potentially proceed with this without having to explicitly register handlers somewhere? Is my thinking about allowing extensibility with registration of command handlers even valid?
If you don't mind scanning assemblies for command handlers, here is a simple solution that works withou a DI-container.