通过单一绑定规则注册开放通用装饰器

Registering open-generic decorators through a single binding rule

正在尝试使用 Command/Handler pattern and Aspect Oriented Programming with Simple Injector

我有我的命令和处理程序 类。

ICommandHandler.cs

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

MakeCoffeeCommand.cs

public class MakeCoffeeCommand
{
    public string Flavor { get; set; }
}

MakeCoffeeCommandHandler.cs

internal class MakeCoffeeCommandHandler : ICommandHandler<MakeCofeeCommand>
{
    public void Handle(MakeCoffeeCommand command)
    {
        ...
    }
}

MakeCakeCommand.cs

public class MakeCakeCommand
{
    public float OvenTemperature { get; set; }
}

MakeCakeCommandHandler.cs

internal class MakeCakeCommandHandler : ICommandHandler<MakeCakeCommand>
{
    public void Handle(MakeCakeCommand command)
    {
        ...
    }
}

到目前为止,我可以通过这条规则注入实现。

//this rule automagically ignores possibly existing decorators! :-)
c.Register(typeof(ICommandHandler<>), typeof(ICommandHandler<>).Assembly);

然后,我想注册装饰器来验证命令实例。我的装饰器实现取决于具体的命令类型。我创建了一个所有装饰器都必须继承的接口。

ICommandHandlerValidator.cs

public interface ICommandHandlerValidator<TCommand> : ICommandHandler<TCommand>
{
}

然后,具体的装饰器。

ValidatorMakeCoffeeCommandDecorator.cs

internal class ValidatorMakeCoffeeCommandDecorator 
    : ICommandHandlerValidator<MakeCoffeeCommand>
{
    private readonly ICommandHandler<MakeCoffeeCommand> decoratee;

    public ValidatorMakeCoffeeCommandDecorator(ICommandHandler<MakeCoffeeCommand> decoratee)
    {
        this.decoratee = decoratee;
    }

    public void Handle(MakeCoffeeCommand command)
    {
        ...
    }
}

ValidatorMakeCakeCommandDecorator.cs

internal class ValidatorMakeCakeCommandDecorator 
    : ICommandHandlerValidator<MakeCakeCommand>
{
    private readonly ICommandHandler<MakeCakeCommand> decoratee;

    public ValidatorMakeCakeCommandDecorator(ICommandHandler<MakeCakeCommand> decoratee)
    {
        this.decoratee = decoratee;
    }

    public void Handle(MakeCakeCommand command)
    {
        ...
    }
}

我正在尝试用一行注册这些验证器,就像前面的例子一样。

c.RegisterDecorator(typeof(ICommandHandler<>), typeof(ICommandHandlerValidator<>));

但是我得到这个错误。

The given type ICommandHandlerValidator<TCommand> is not a concrete type. Please use one of the other overloads to register this type.

  1. 这是正确的方法吗?
  2. 如果是,如何消除错误?

注意:我必须为所有横切关注点实现多个装饰器。其中一些依赖于具体命令(即授权、全文索引),有些则不依赖,并且所有命令共享相同的实现(即日志记录、性能分析)。

It this a correct approach?

这个答案可能有点自以为是,但对我来说,这不是正确的方法。您的 ICommandHandlerValidator<T> 接口没有任何作用,您的装饰器可以很容易地直接从 ICommandHandler<T>.

派生

除此之外,您有点 'abusing' 装饰器来实现非常具体的逻辑,而装饰器最适合实现非常通用的横切关注点。

虽然您可能会争辩说验证是非常通用的,但您的实现根本不是通用的,因为每个装饰器都有特定于单个处理程序实现的逻辑。这样就导致你得到的装饰器很多,需要批量注册。

我通常喜欢做的是退后一步,看看设计。在您的架构中,您确定 改变状态的业务逻辑 是一个值得自己抽象的特定工件。你称这个抽象为ICommandHandler<T>。这不仅可以让您清楚地区分这些特定类型的组件与系统中的其他组件,还可以让您批量注册它们并非常有效地应用横切关注点。

然而,在查看您的代码时,在我看来,在命令处理程序执行之前验证命令的逻辑在您的应用程序中具有其自身的重要性。这意味着它应该有自己的抽象。例如,您可以将其命名为 ICommandValidator<T>:

public interface ICommandValidator<TCommand>
{
    IEnumerable<ValidationResult> Validate(TCommand command);
}

注意这个接口与ICommandHandler<TCommand>没有关系。验证组件是一个不同的工件。 ICommandValidator<T> 接口 returns 验证结果,可用于实现。您可能想使用此验证器的最佳设计。

使用此接口,您现在可以定义特定的验证器:

public class MakeCoffeeValidator : ICommandValidator<MakeCoffeeCommand> { ... }
public class MakeCakeValidator : ICommandValidator<MakeCakeCommand> { ... }

除了这些验证器在您的设计中的可见性之外,这个单独的界面还允许您的验证器被批量注册:

c.Collection.Register(typeof(ICommandValidator<>),
    typeof(MakeCakeValidator).Assembly);

这里,假设单个命令可能有零个或多个验证器实现,验证器被注册为集合。如果总是只有一个实现(正如您将在命令处理程序实现中看到的那样),则您应该改为调用 c.Register

然而,这本身并没有多大作用,因为这些验证器不会自行执行。为此,您需要编写一段通用的横切代码,可以应用于系统中的所有命令处理程序。也就是说,你需要写一个装饰器:

public class ValidatingCommandHandlerDecorator<T> : ICommandHandler<T>
{
    private readonly ICommandHandler<T> decoratee;
    private readonly IEnumerable<ICommandValidator<T>> validators;

    public ValidatingCommandHandlerDecorator(
        IEnumerable<ICommandValidator<T>> validators,
        ICommandHandler<T> decoratee)
    {
        this.validators = validators;
        this.decoratee = decoratee;
    }

    public void Handle(T command)
    {
        var validationResults = (
            from validator in this.validators
            from result in validator.Validate(command)
            select result)
            .ToArray();

        if (validationResults.Any())
        {
            throw new ValidationException(validationResults);
        }

        this.decoratee.Handle(command);
    }
}

这个装饰器可以用你已经熟悉的方式注册:

c.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(ValidatingCommandHandlerDecorator<>));

尽管您可以尝试批量注册此装饰器以及系统中的所有装饰器,但通常效果不佳。这是因为执行横切关注点的顺序至关重要。例如,当你实现一个死锁重试装饰器和一个事务装饰器时,你希望用死锁装饰器来包装事务装饰器,否则你可能最终会在事务上下文之外重试死锁操作(因为 SqlTransaction 和 SQL 服务器工作)。同样,您希望在 内部 事务中编写审计跟踪。否则,您最终可能会错过成功操作的审计线索。