域事件模式单点到队列事件

本文关键字:事件 队列 模式 单点 | 更新日期: 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次。一次发送给调度器,一次发送给多路复用器,一次发送给窄通道,一次发送给非基础设施订阅者。如果您有两个订阅者(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
{
}

这个特殊的限制没有考虑多次调度事件。例如,我可以为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>对象