Autofac resolve dependency in CQRS CommandDispatch

2020-02-26 07:46发布

问题:

I'm trying to implement a simple CQRS-application example.

This is a structure of my "Command" part:

public interface ICommand
{
}

//base interface for command handlers
interface ICommandHandler<in TCommand> where TCommand: ICommand
{
    void Execute(TCommand command);
}

// example of the command
public class SimpleCommand: ICommand 
{
   //some properties
}

// example of the SimpleCommand command handler
public class SimpleCommandHandler: ICommandHandler<SimpleCommand>
{
    public void Execute(SimpleCommand command)
    {
       //some logic
    }

}

This is interface ICommandDipatcher. It dispatches a command to its handler.

public interface ICommandDispatcher
{
    void Dispatch<TCommand>(TCommand command) where TCommand : ICommand;
}

This is a default implementation of ICommandDispatcherand the main problem is to obtain the necessary command handler by the type of the command via Autofac.

public class DefaultCommandDispatcher : ICommandDispatcher
{
    public void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
    {
        //How to resolve/get object of the neseccary command handler 
        //by the type of command (TCommand)
        handler.Execute(command);
    }
}

What is the best way to resolve implementation of ICommandHanler by type of the command in that case via Autofac?

Thanks!

回答1:

With Autofac, you need to inject the IComponentContext into the dispatcher. This way you can call back into the container to resolve the required command handler:

public class AutofacCommandDispatcher : ICommandDispatcher
{
    private readonly IComponentContext context;

    public AutofacCommandDispatcher(IComponentContext context)
    {
        this.context = context;
    }

    public void Dispatch<TCommand>(TCommand command)
    {
        var handler = this.context.Resolve<ICommandHandler<TCommand>>();

        void handler.Execute(command);
    }
}

You can register the AutofacCommandDispatcher as follows:

builder.RegisterType<AutofacCommandDispatcher>().As<ICommandDispatcher>();

And you can register all your command handlers in one go as follows:

builder.RegisterAssemblyTypes(myAssembly)
    .AsClosedTypesOf(typeof(ICommandHandler<>));

Two notes though. First of all, you probably defined the ICommandHandler<T> as contravariant (with the in keyword) because Resharper said so, but this is a bad idea for command handlers. There is always a one-to-one mapping between a command and command handler, but defining the in keyword, communicates that there can be multiple implementations.

Second, in my opinion, having a command dispatcher is a bad idea, because this can hide the fact that the consuming classes of command handlers have too many dependencies, which is an indication of a violation of the Single Responsibility Principle. Further more, using such a dispatcher postpones creation of part of the object graph (the part of the command handler) until the command is actually executed (opposed to when the consumer is resolved). This makes it harder to verify the container's configuration. When command handlers are injected directly, you know for sure that the whole object graph can be resolved when the root types in your configuration can be resolved. It's easy to define a command but forget to create the corresponding command handler, so you will need to add unit tests for this to check if each command has a corresponding handler. You can save yourself from having to write such test if you remove the dispatcher all together.



回答2:

Assuming you have ConcreteCommand : IComman and ConcreteCommandHandler : ICommandHandler<ConcreteCommand> use RegisterType method like this:

builder.RegisterType<ConcreteCommandHandler>()
       .As<ICommandHandler<ConcreteCommand>>();

And then inject your handler:

private ICommandHandler<ConcreteCommand> concreteCommandHandler;

Also look at the automatic assembly types registration Autofac abilities.

If you'd like to resolve ICommandHandler by ICommand implementation then factory registration can help. Register Func<Type, ICommandHandler> or define special class that will resolve appropriate command handler.