gpt4 book ai didi

c# - 领域事件模式单点队列事件

转载 作者:太空狗 更新时间:2023-10-30 01:06:22 26 4
gpt4 key购买 nike

我在这里提出一个问题:Raising Domain Events For Multiple Subscribers答案让我想到了以下模式,我可以在其中拥有一个像这样的 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});

理想情况下,我希望能够拥有一个包含任何 IEventPublisher 的通用发布者,这样我就可以调用如下内容:

_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});

我不知道该怎么做,因为我无法在不将 T 定义为 ThingyChangedEvent 的情况下传递 IEventPublisher 的集合,而我想要的是根据传递给的类型确定发布者通用发布者。

非常感谢任何建议。

编辑:

确定使用下面的答案和这里的一些信息:http://www.udidahan.com/2009/06/14/domain-events-salvation/我想出了以下内容,但还不够:

public interface IEventManager
{
void Publish<T>(T args) where T : IEvent;
}

公共(public)类 EventManager : IEventManager { 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);
}
}

我现在可以在 autofac 解析的构造函数中获取 IEventManager eventManager 的实例,并按如下方式调用它:

_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });

以下是我不喜欢这个解决方案的地方:

我不想在构造函数中获取 ILifetimeScope 的实例,我希望能够获取 IEventSubscribersProvider 的集合,但是如果我要求的话 autofac 不会解决这个问题:

 IEnumerable<IEventSubscribersProvider<IEvent>> 

如果我将类型传递给发布并调用,我只能解决它:

Resolve<IEventSubscribersProvider<T>>.

第二个问题不是什么大问题,但能够调用发布而不必像这样传递类型会很好:

_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 Generic Service resolution at runtime我不确定我能否实现我想要的。我能想到的最佳解决方案是:

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 变体不支持您想要使用它的地方。

您需要一个“基本”类型,它是您接受为“事件”的最小(或最窄)类型。我通常使用 marker interface喜欢public interface IEvent{} .当然,您可以从 Object 派生;但我发现使用标记接口(interface)来表明您正在订阅或发布“事件”这一事实很有用(这意味着您不能只发布任何类型的对象,而只能发布“事件”)。

从多类型方面的处理来看,您需要编写一个类来缩小范围(采用派生类型并将同一对象“发布”为派生类型)。即使这样,您也需要通过更窄的适当实例来路由您的事件(以“绕过”方差限制)。那么,当然,您可以让多个订阅者订阅同一事件类型;所以你需要一些东西来将事件路由到多个订阅者。这通常称为多路复用器,它是 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 次。一次到调度器,一次到多路复用器,一次到窄器,一次到非基础设施用户。如果您有两个订阅者(EventOneEventSubscriberEventTwoEventSubscriber)订阅了两种类型的事件(EventOneEventTwo),那么您可以创建一个调度程序并像这样发布事件:

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
{
}

此特定限制未考虑多次分派(dispatch)事件。例如,我可以订阅 EventOne和一名订阅者 IEvent .此实现仅发布到 EventOne订户如果 EventOne对象通过调度程序发布——它不会发布到 IEvent订户。我会把它留给读者作为练习:)

更新:

如果您希望做的是自动连接订阅者而无需构建它们(我认为这没有多大值(value),请考虑 Composition Root ),那么您可以向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>对象,如果你愿意的话。

关于c# - 领域事件模式单点队列事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15690396/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com