Регистрация открытых универсальных декораторов с помощью единого правила привязки - PullRequest
0 голосов
/ 01 октября 2018

Попытка использовать Шаблон команд / обработчиков и Аспектно-ориентированное программирование с Простой инжектор .

У меня есть классы команд и обработчиков.

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<>));

Но я получаю эту ошибку.

Данный тип ICommandHandlerValidator<TCommand> не является конкретным типом.Пожалуйста, используйте одну из других перегрузок для регистрации этого типа.

  1. Это правильный подход?
  2. Если да, как можно избавиться от ошибки?

Примечание: мне нужно реализовать несколько декораторов для всех сквозных задач.Некоторые из них зависят от конкретной команды (например, авторизация, полнотекстовое индексирование), некоторые - нет, и имеют одинаковую реализацию, общую для всех команд (например, ведение журнала, анализ производительности).

1 Ответ

0 голосов
/ 01 октября 2018

Это правильный подход?

Этот ответ может быть немного самоуверенным, но для меня это неправильный подход.Ваш ICommandHandlerValidator<T> интерфейс не выполняет никакой функции, и ваши декораторы могут легко получить непосредственно из ICommandHandler<T>.

Кроме того, вы несколько «злоупотребляете» декораторами для реализации очень специфической логики, в то время как декораторы являются лучшимиподходит для реализации очень общих сквозных задач.

Хотя вы можете утверждать, что проверка очень универсальна, ваши реализации вообще не являются общими, поскольку каждый декоратор имеет логику, специфичную для реализации отдельного обработчика.Это приводит к тому, что вы получаете много декораторов, и вам необходимо их зарегистрировать в пакетном режиме.

Что мне обычно нравится делать, так это сделать шаг назад и посмотреть на дизайн.В вашей архитектуре вы определили, что бизнес-логика , которая изменяет состояние , является неким артефактом, который заслуживает отдельной абстракции.Вы называете эту абстракцию ICommandHandler<T>.Это не только позволяет вам четко отличать эти конкретные типы компонентов от других компонентов в системе, но и позволяет эффективно их регистрировать в пакетном режиме и применять сквозные задачи.

Однако, глядя на ваш кодМне кажется, что логика, которая проверяет команды перед их выполнением их обработчиком команд, имеет свою собственную важность в вашем приложении.Это означает, что оно заслуживает собственной абстракции.Например, вы можете назвать его ICommandValidator<T>:

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

Обратите внимание, что этот интерфейс не имеет отношения к ICommandHandler<TCommand>.Компоненты проверки - это другой артефакт.Интерфейс ICommandValidator<T> возвращает результаты проверки, что может быть полезно для реализации.Возможно, вы захотите поиграть с лучшим дизайном для этого валидатора.

Используя этот интерфейс, вы теперь можете определять конкретные валидаторы:

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 сервер работают).Точно так же вы хотите написать контрольный журнал внутри транзакции.В противном случае вы можете пропустить контрольный журнал для успешной операции.

...