Я использую SimpleInjector
с MediatR
и подключаю мои INotifications
и INotificationHandlers<INotification>
с атрибутами к моим классам реализации - так мы можем сопоставить сообщения шины сообщений (или уведомления) обработчикам уведомлений:
- Хост-приложение получает сообщение от шины
- Сообщение анализируется в уведомлении POCO
- Контейнер используется для поиска обработчиков уведомлений
- Уведомление отправляется каждому обработчику
Традиционно можно зарегистрировать все INotificationHandler<>
с Container.GetTypesToRegister()
и Container.Collection.Register()
, и все будет хорошо, так как все обработчики будут возвращены контейнером для этого специфика c INotification
.
В этом случае я sh получу конкретные c конкретные экземпляры, которые реализуют специфицируемый c INotificationHandler<SpecificEvent>
для вызова. Мы решаем, что вызывать с атрибутами:
[MessageBusSubscription("v1/alerts/fire"]
class FirstClass : INotificationHandler<Aggregate>
{
public Task Handle(Aggregate notification, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
[MessageBusSubscription("v1/alerts/fire"]
class SecondClass : INotificationHandler<Aggregate>
{
public Task Handle(Aggregate notification, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
[MessageBusSubscription("v1/alerts/leak"]
class OtherClass : INotificationHandler<Aggregate>
{
public Task Handle(Aggregate notification, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
Я знаю, что могу получить и преобразовать в интерфейс (это приведение будет безопасным, так как при регистрации инфраструктура bootstrap будет принудительно проверять, что тип реализует интерфейс ):
Container Bootstrap()
{
var container = new Container();
// we cannot register the below with Container.Collection.Register as we wont be finding the registration
// by INotificationHandler<INotification> but rather by the specific class (we could auto-wire by reflection)
container.Register<FirstClass>();
container.Register<OtherClass>();
// other bootstrap code, including registration of all other INotificationHandler<> that do not have
// MessageBusSubscriptionAttribute in the cases where we want to publish to all implementations
// that are not scoped to a specific topic subscription or message type
return container;
}
IEnumerable<INotificationHandler<TNotification>>> GetConcreteTypeThatImplementsNotificationHandlers<TNotification>(string topic)
where TNotification : INotification
{
// TopicRegistrations consists of the following:
// { "v1/alerts/fire", [] { typeof(FirstClass), typeof(SecondClass) }
// { "v1/alerts/leak", [] { typeof(OtherClass) }
// based on notification type and topic string, get concrete implementation
var concreteTypes = TopicRegistrations.GetTypeForTopic(topic);
foreach(var type in concreteTypes)
{
var instance = Container.GetInstance(type);
yield return (INotificationHandler<TNotification>>)instance;
}
}
Затем я мог бы вызвать вышеупомянутое, как если бы это было INotificationHandler
для этого определенного c типа уведомления и только для конкретных c конкретных реализаций, основанных на метаданных атрибута.
Однако как я могу гарантировать, что любые декораторы (например, ведение журналов или обработчики ошибок и т. Д. c) будут по-прежнему применяться к NotificationHandler
, выбранному при использовании вышеуказанного подхода к коду?
В конечном итоге я ищу функцию контейнера, аналогичную приведенной ниже, которая бы возвращала INotificationHandler
только для указанного типа бетона и применяла бы декораторы (если таковые имеются):
var notficationHandlerType = typeof(INotificationHandler<>).MakeGenericType(notificationFromBus.GetType());
Container.GetRegistrationOnlyForConcreateType(notficationHandlerType, concreteType);
Что-нибудь, что можно сделать, чтобы сделать это чище или пропустить какие-либо риски, которые могут взорваться или пахнет кодом?
Обновление
Спасибо Стивену за его замечательный код, спасибо, что вдохновили нас на лучший код. Я немного адаптировал его так, чтобы он принимал тип и обработчики Speci c, чтобы зарегистрироваться для этого типа:
public class TestNotification : INotification { }
public class DifferentTestNotification : INotification { }
public class OtherClass : INotificationHandler<TestNotification>, INotificationHandler<DifferentTestNotification>
{
public Task Handle(TestNotification notification, CancellationToken cancellationToken)
{ throw new NotImplementedException(); }
public Task Handle(DifferentTestNotification notification, CancellationToken cancellationToken)
{ throw new NotImplementedException(); }
}
/* repeat the exact same for AmazingClass and AllStars giving 3 identical classes for which we can selectively register different handlers later */
И передать словарь обработчиков, которые я sh зарегистрирую:
registrationTypes = new Dictionary<Type, Type[]>()
{
{ typeof(OtherClass), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(TestNotification)) } },
{ typeof(AmazingClass), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(DifferentTestNotification)) } },
{ typeof(AllStars), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(TestNotification)), typeof(INotificationHandler<>).MakeGenericType(typeof(DifferentTestNotification)) } }
};
В следующий класс регистрации:
public class NotifcationProducerRegistrations
{
readonly ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>> handlerProducers
= new ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>>();
public void AddProducer(Type notificationType, Type concreteType, InstanceProducer producer)
{
this.handlerProducers.AddOrUpdate(
notificationType,
(key) => new Dictionary<Type, InstanceProducer> { { concreteType, producer } },
(key, dictionary) => { dictionary.Add(concreteType, producer); return dictionary; });
}
public IEnumerable<InstanceProducer> GetRegistrations(Type notificationType)
{
if(this.handlerProducers.TryGetValue(notificationType, out var dict))
{
foreach (var kvp in dict)
yield return kvp.Value;
}
}
}
И зарегистрируйтесь следующим образом:
public static NotifcationProducerRegistrations GetNotificationHandlerProducersTest(this Container container, Dictionary<Type, Type[]> registrationTypes, Lifestyle lifestyle)
{
var registrations = new NotifcationProducerRegistrations();
foreach (var registration in registrationTypes)
{
var concreteType = registration.Key;
var notificationHandlerTypes = registration.Value;
var interfaceTypes = concreteType.GetClosedTypesOf(typeof(INotificationHandler<>));
foreach(var filteredInterfaceType in interfaceTypes.Intersect(notificationHandlerTypes))
{
registrations.AddProducer(
filteredInterfaceType,
concreteType,
lifestyle.CreateProducer(filteredInterfaceType, concreteType, container));
}
}
return registrations;
}
Со ссылкой на мой комментарий, с вышеупомянутым у меня есть Соотношение 1: 1 между производителем для определенного типа конкретного типа c, в массиве других конкретных типов для этого типа уведомления.
Как уже упоминалось, в настоящее время я вижу массив, тогда как я считаю, что (и может быть не прав), что мне нужно получить только одного производителя, как показано на карте ниже: