Как сделать открытую универсальную цепочку декораторов с 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);
Надеюсь это поможет