域事件模式单点到队列事件
本文关键字:事件 队列 模式 单点 | 更新日期: 2023-09-27 18:06:35
我在这里提出了一个问题:为多个订阅者提高域事件,答案使我想到以下模式,其中我可以有一个像这样的IEventPublisher:
public interface IEventPublisher<T>
{
void Publish(T data);
}
和像这样的IEventSubscriber:
public interface IEventSubscriber<T>
{
void Handle(T data);
}
这样做的问题是,我需要将每个发布者的实例传递给构造函数,如下所示:
public Service(IEventPublisher<ThingyChangedEvent> publisherThingyChanged)
{
// Set publisher to local variable
}
// then call this in a method
_publisherThingyChanged.Publish(new ThingyChangedEvent { ThingyId = model.Id});
理想情况下,我希望能够做的是有一个通用的出版商,它包含任何的IEventPublishers,所以我可以调用这样的东西:
_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});
我不知道如何做到这一点,虽然我不能传递一个集合的IEventPublisher没有定义T在这个例子中作为ThingyChangedEvent,而我想要的是根据传递给通用出版商的类型来确定出版商。
任何建议都非常感谢。
编辑:OK使用下面的答案和一些信息从这里:http://www.udidahan.com/2009/06/14/domain-events-salvation/我想出了以下,但它不是完全在那里:
public interface IEventManager
{
void Publish<T>(T args) where T : IEvent;
}
公共类EventManager: EventManager{Autofac。ILifetimeScope _container;
public EventManager(Autofac.ILifetimeScope container)
{
_container = container;
}
//Registers a callback for the given domain event
public void Publish<T>(T args) where T : IEvent
{
var subscribersProvider = _container.Resolve<IEventSubscribersProvider<T>>();
foreach (var item in subscribersProvider.GetSubscribersForEvent())
{
item.Handle(args);
}
}
}
我现在可以在autofacc解析的构造函数中获取一个IEventManager eventManager的实例,并按如下方式调用它:
_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
这是我不喜欢这个解决方案的地方:
我不想在构造函数中取一个ILifetimeScope的实例,我想取一个IEventSubscribersProvider的集合但是如果我请求,autoface不会解决这个问题
IEnumerable<IEventSubscribersProvider<IEvent>>
我只能通过将类型传递给Publish并调用:
来解决这个问题Resolve<IEventSubscribersProvider<T>>.
第二个问题不是一个大问题,但如果能够调用publish而不必传递类型就好了,就像这样:
_eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
我想如果有人对如何解决这两个问题有任何建议,特别是问题1,因为我不喜欢在不同的项目中依赖于Autofac。我唯一能想到的是某种类型的管理器类,它显式地接受我需要的东西,如下所示:
public SomeConstructor(
IEventSubscribersProvider<ThingyChangedEvent> thingChangedSubscribeProviders,
IEventSubscribersProvider<SomeOtherEvent> someOtherSubscribeProviders,
etc....)
{
// Maybe take the EventManager as well and add them to it somehow but would be
// far easier to take a collection of these objects somehow?
}
非常感谢您的建议。
编辑2
经过大量的研究和在运行时查看这个Autofac通用服务解决方案后,我不确定我能否实现我想要的。我能想到的最好的解决方案是:
public interface IEventSubscribersProviderFactory : Amico.IDependency
{
IEventSubscribersProvider<T> Resolve<T>() where T : IEvent;
}
public class EventSubscribersProviderFactory : IEventSubscribersProviderFactory
{
Autofac.ILifetimeScope _container;
public EventSubscribersProviderFactory(Autofac.ILifetimeScope container)
{
_container = container;
}
public IEventSubscribersProvider<T> Resolve<T>() where T : IEvent
{
return _container.Resolve<IEventSubscribersProvider<T>>();
}
}
然后让EventManager在构造函数中取IEventSubscribersProviderFactory,以从该项目中移除对Autofac的依赖。
我现在就这么做,但希望能找到一个更好的长期解决方案。
当您必须处理多种类型的事件时,它可能会变得有点复杂。正如您可能已经注意到的那样,您不能只使用派生泛型类型,并期望像使用基泛型一样使用它。NET方差不支持你想要使用它的地方。
你需要一个"base"类型,这将是您作为"事件"接受的最小(或最窄)类型。我通常使用像public interface IEvent{}
这样的标记接口。当然,你可以从Object
推导;但我发现有标记接口很有用,它可以让你订阅或发布一个"事件"显式(这意味着你不能发布任何类型的对象,只能发布"事件")。
从处理多类型方面来看,您需要编写一个类来执行窄化(接受派生类型并"发布")。同一对象转换为派生类型)。即使这样,你也需要通过一个合适的实例来路由你的事件。方差限制)。然后,当然,您可以对同一事件类型拥有多个订阅者;因此,您需要一些东西将事件路由到多个订阅者。这通常被称为多路复用,这将是IEventPublisher
的专业化。每个事件类型需要一个多路复用器——这将使用较窄的。对于给定事件使用哪个多路复用器取决于类型,因此多路复用器集合将由字典管理,以便您可以按类型查找它们。然后,要按类型向多个订阅者发布事件,只需查找多路复用器并调用其IEventPublisher.Publish
方法。管理多路复用器的是一种IEventPublisher
,通常称为Dispatcher(有些人可能称之为路由器)。
public class NarrowingSubscriber<TBase, TDerived> : IEventSubscriber<TBase>
where TDerived : TBase
where TBase : IEvent
{
private IEventSubscriber<TDerived> inner;
public NarrowingSubscriber(IEventSubscriber<TDerived> inner)
{
if (inner == null) throw new ArgumentNullException("inner");
this.inner = inner;
}
public void AttachSubscriber(IEventSubscriber<TDerived> subscriber)
{
inner = subscriber;
}
public void Handle(TBase data)
{
inner.Handle((TDerived)data);
}
}
public class Multiplexor<T> : IEventSubscriber<T> where T : IEvent
{
private readonly List<IEventSubscriber<T>> subscribers =
new List<IEventSubscriber<T>>();
public void AttachSubscriber(IEventSubscriber<T> subscriber)
{
subscribers.Add(subscriber);
}
public void RemoveSubscriber(IEventSubscriber<T> subscriber)
{
subscribers.Remove(subscriber);
}
public void Handle(T data)
{
subscribers.ForEach(x => x.Handle(data));
}
}
public class Dispatcher<TBase> : IEventPublisher<TBase> where TBase : IEvent
{
private readonly Dictionary<Type, Multiplexor<TBase>> subscriptions =
new Dictionary<Type, Multiplexor<TBase>>();
public void Publish(TBase data)
{
Multiplexor<TBase> multiplexor;
if (subscriptions.TryGetValue(data.GetType(), out multiplexor))
{
multiplexor.Handle(data);
}
}
public void Subscribe<TEvent>(IEventSubscriber<TEvent> handler)
where TEvent : TBase
{
Multiplexor<TBase> multiplexor;
if (!subscriptions.TryGetValue(typeof(TEvent), out multiplexor))
{
multiplexor = new Multiplexor<TBase>();
subscriptions.Add(typeof(TEvent), multiplexor);
}
multiplexor.AttachSubscriber(new NarrowingSubscriber<TBase, TEvent>(handler));
}
}
你基本上发布了4次。一次发送给调度器,一次发送给多路复用器,一次发送给窄通道,一次发送给非基础设施订阅者。如果您有两个订阅者(EventOneEventSubscriber
和EventTwoEventSubscriber
)订阅两种类型的事件(EventOne
和EventTwo
),那么您可以创建一个调度程序并像这样发布事件:
var d = new Dispatcher<IEvent>();
var eventTwoSubscriber = new EventTwoEventSubscriber();
d.Subscribe(eventTwoSubscriber);
var eventOneSubscriber = new EventOneEventSubscriber();
d.Subscribe(eventOneSubscriber);
d.Publish(new EventOne());
d.Publish(new EventTwo());
当然,事件将派生自IEvent:
public class EventOne : IEvent
{
}
public class EventTwo : IEvent
{
}
这个特殊的限制没有考虑多次调度事件。例如,我可以为EventOne
设置一个订阅者,为IEvent
设置一个订阅者。如果通过调度程序发布EventOne
对象,则此实现仅将其发布到EventOne
订阅者—它不会发布到IEvent
订阅者。我将把它留给读者作为练习:)
如果您希望做的是自动连接订阅者而不必构造它们(我认为这样做没有多大价值,考虑一个组合根),那么您可以向Dispatcher
(或其他地方,如果需要)添加一个相当简单的方法来订阅所有兼容的订阅者:
public void InitializeSubscribers()
{
foreach (object subscriber in
from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where !type.IsAbstract && type.IsClass && !type.ContainsGenericParameters &&
type.GetInterfaces().Any(t => t.IsGenericType &&
t.GetGenericTypeDefinition() == typeof (IEventSubscriber<>))
select type.GetConstructor(new Type[0])
into constructorInfo
where constructorInfo != null
select constructorInfo.Invoke(new object[0]))
{
Subscribe((dynamic) subscriber);
}
}
,你可以这样使用:
var d = new Dispatcher<IEvent>();
d.InitializeSubscribers();
…如果需要,可以使用容器来解析Dispatcher<T>
对象