Как сделать открытую универсальную цепочку декораторов с Unity + UnityAutoRegistration

Ушел сегодня по интересной теме после прочтения этой статьи об оформлении командного обработчика. Я хотел посмотреть, смогу ли я реализовать шаблон, используя Unity вместо https://simpleinjector.org/, и до сих пор это оказалось чрезвычайно сложно.

Первое, что мне нужно было сделать, это установить UnityAutoRegistration, чтобы разрешить открытый универсальный ICommandHandler<TCommand> интерфейс. Текущее решение для этого аспекта заключается в следующем:

Container = new UnityContainer().LoadConfiguration();

Container.ConfigureAutoRegistration()
    .ExcludeSystemAssemblies()
    .Include(type => type.ImplementsOpenGeneric(typeof(ICommandHandler<>)),
        (t, c) => c.RegisterType(typeof(ICommandHandler<>), t)
    )
    .ApplyAutoRegistration()
;

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

Поиск в Google привел меня в первую очередь к этой статье, которая объясняет, как это сделать, используя метод грубой силы. Я также нашел эти связанные страницы, но ни одна из них не является полным решением моей проблемы (и я слишком невежествен, чтобы понять это для себя).

Затем я нашел эту статью, в которой, похоже, рассматриваются мои же проблемы. Однако решение Бифи не учитывает открытые дженерики. В настоящее время большинство наших зависимостей загружаются из раздела единицы в файле.config, поэтому я не хочу писать тонну скомпилированного кода для каждого обработчика или декоратора. Кажется, что наличие какого-то UnityContainerExtension и DecoratorBuildStrategy - правильный путь, но я не могу понять это. Я немного поигрался с кодом Бифи и совершенно застрял. Мои попытки изменить его код для учета непатентованных средств привели к появлению BadImageFormatExceptions (была предпринята попытка загрузить программу с неверным форматом. (Исключение из HRESULT: 0x8007000B)).

Мне нравится идея сделать это для реализации цепочки декоратора, потому что она короткая, и есть только 1 строка для каждой проблемы:

var container = new Container();

// Go look in all assemblies and register all implementations
// of ICommandHandler<T> by their closed interface:
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Decorate each returned ICommandHandler<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(TransactionCommandHandlerDecorator<>));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(DeadlockRetryCommandHandlerDecorator<>));

... но я не хочу менять свой контейнер с Unity на SimpleInjector, если мне не нужно.

Так что мой вопрос: как я могу реализовать открытую универсальную цепочку декоратора с помощью Unity (плюс UnityAutoRegistration)?

2 ответа

Это будет эквивалентно в Unity:

// Go look in all assemblies and register all implementa-
// tions of ICommandHandler<T> by their closed interface:
var container = new UnityContainer();

var handlerRegistrations =
    from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from implementation in assembly.GetExportedTypes()
    where !implementation.IsAbstract
    where !implementation.ContainsGenericParameters
    let services =
        from iface in implementation.GetInterfaces()
        where iface.IsGenericType
        where iface.GetGenericTypeDefinition() == 
            typeof(ICommandHandler<>)
        select iface
    from service in services
    select new { service, implementation };

foreach (var registration in handlerRegistrations)
{
    container.RegisterType(registration.service, 
        registration.implementation, "Inner1");
}

// Decorate each returned ICommandHandler<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(TransactionCommandHandlerDecorator<>),
    "Inner2",
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner1")));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(DeadlockRetryCommandHandlerDecorator<>), 
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner2")));

Я надеюсь, что я правильно понимаю проблему, и мне было любопытно попытаться заставить это работать, и я ни в коем случае не эксперт по Unity, но я думал о решении, которое немного легче реализовать, а также было бы легче сделать с другим контейнером. Казалось бы, единственный способ поддержать открытый универсальный и другие типы - это иметь 2 отдельных контейнера (1 для открытого универсального) и один для ваших командных обработчиков, это может быть не лучшим способом, но это работает с Unity и я предполагаю, что будет также легче с другими.

Итак, я придумал это:

Я создал контейнеры следующим образом (вы можете использовать ваш обычный подход, все еще не уверенный для контейнера обработчика)

var container = new UnityContainer();

var container2 = new UnityContainer();

container2.RegisterType(typeof(ICommandHandler<QueryCommand>),
    typeof(QueryCommandHandler));
container.RegisterInstance("Handlers", container2);
container.RegisterInstance(container);
container.RegisterType(typeof(ICommandHandler<>),
    typeof(DecoratedHandler<>));

Вы видите контейнер 2, содержащий обработчики как именованный экземпляр.

Затем я просто создал базовый базовый класс декоратора в соответствии с требованием шаблона:

public class DecoratorCommandHandler<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> inner;
    public DecoratorCommandHandler(
        ICommandHandler<TCommand> inner)
    {
        this.inner = inner;
    }

    public virtual void Handle(TCommand command)
    {
         this.inner.Handle(command);
    }
}

Во-вторых, я создал еще один универсальный обработчик, который обернул бы все декорации, которые вы хотите сделать для вашего решения, здесь вы добавите декорирование для TryCatch/Caching/Transactions или все, что вы хотите применить к каждому обработчику команд:

public class DecoratedHandler<TCommand>
    : DecoratorCommandHandler<TCommand>
{
    public DecoratedHandler(UnityContainer container)
        : base(BuildInner(container))
    {
    }

    private static ICommandHandler<TCommand> BuildInner(
        UnityContainer container)
    {
         var handlerContainer =
             container.Resolve<UnityContainer>("Handlers");
         var commandHandler =
             handlerContainer.Resolve<ICommandHandler<TCommand>>();

         return new TryCatchCommandHandler<TCommand>(commandHandler);
    }
}

Вы заметите, что первый внутренний разрешает фактический обработчик команды в соответствии с тем, который вы запросили, как QueryCommandHandler, UpdateCommandHandler, ExecuteCommandHandler или что-то еще, имеющее дело со спецификой. А затем обертываются всеми декораторами, которые вы хотите использовать для всех них.

Тогда я смог определить правильный обработчик, оформленный в правильном виде:

ICommandHandler<QueryCommand> handler =
    container.Resolve<ICommandHandler<QueryCommand>>();

var cmd = new QueryCommand();

handler.Handle(cmd);

Надеюсь это поможет

Другие вопросы по тегам